diff --git a/.gitattributes b/.gitattributes index 31c759707..41595620b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,6 @@ # Preserve the SDK's exact native payload bytes and their recorded integrity hashes. backends/**/native/**/Native/Generated/** -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol + +# Preserve the upstream supplemental-font license content identity on every host. +backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/LICENSE -text diff --git a/.github/workflows/rendering-ci.yml b/.github/workflows/rendering-ci.yml index fc6d75719..3a5d08dce 100644 --- a/.github/workflows/rendering-ci.yml +++ b/.github/workflows/rendering-ci.yml @@ -251,13 +251,23 @@ jobs: Tee-Object -FilePath 'artifacts/acceptance/ci-build/source-debug.log' if ($LASTEXITCODE -ne 0) { throw 'SourceDebug build failed.' } - - name: Enforce source and compiled public API architecture boundaries + - name: Build selected Player contract for repository checks + id: player_contract if: ${{ !cancelled() && steps.source.outcome == 'success' }} shell: pwsh + run: | + dotnet build '${{ matrix.player_project }}' --configuration Debug --disable-build-servers -m:1 -nr:false ` + '-bl:artifacts/acceptance/ci-build/wiki-player.binlog' 2>&1 | + Tee-Object -FilePath 'artifacts/acceptance/ci-build/wiki-player.log' + if ($LASTEXITCODE -ne 0) { throw 'The current Wiki target reference closure could not be built.' } + + - name: Enforce source and compiled public API architecture boundaries + if: ${{ !cancelled() && steps.source.outcome == 'success' && steps.player_contract.outcome == 'success' }} + shell: pwsh run: dotnet run --project "entry/InnoEngine.Build/InnoEngine.Build.csproj" --no-build -- verify . - name: Run complete test suite - if: ${{ !cancelled() && steps.source.outcome == 'success' }} + if: ${{ !cancelled() && steps.source.outcome == 'success' && steps.player_contract.outcome == 'success' }} shell: pwsh run: | $properties = Join-Path $PWD '${{ matrix.product_properties }}' @@ -306,13 +316,9 @@ jobs: } - name: Validate current Wiki signatures and compiled examples - if: ${{ !cancelled() && steps.source.outcome == 'success' }} + if: ${{ !cancelled() && steps.source.outcome == 'success' && steps.player_contract.outcome == 'success' }} shell: pwsh run: | - dotnet build '${{ matrix.player_project }}' --configuration Debug --disable-build-servers -m:1 -nr:false ` - '-bl:artifacts/acceptance/ci-build/wiki-player.binlog' 2>&1 | - Tee-Object -FilePath 'artifacts/acceptance/ci-build/wiki-player.log' - if ($LASTEXITCODE -ne 0) { throw 'The current Wiki target reference closure could not be built.' } $project = Join-Path $env:RUNNER_TEMP 'InnoWikiProject' New-Item -ItemType Directory -Force (Join-Path $project 'Assets') | Out-Null 'public sealed class WikiProbe { }' | Set-Content (Join-Path $project 'Assets/WikiProbe.cs') -Encoding utf8 diff --git a/AGENTS.md b/AGENTS.md index d8d950633..509cd4a82 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,6 +115,7 @@ - 保存、打开、选择等纯工作流操作默认不进入数据 Undo;它们只有在确实修改项目数据时才记录对应的数据部分。 - 跨启动的项目语义状态直接属于 Module/Panel;它们使用 Attribute 中必填、稳定且全局唯一的 ID,并通过 protected `Capture(EditorState)` / `Restore(EditorState)` hooks 参与持久化。扩展只调用参数对象的 `Get` / `Set`,不得在公开或 protected API 中暴露 JSON 实现。未 override Capture 的类型不进入状态 IO;不得恢复独立 Workspace interface、reader/writer 或第二个状态 ID。持久值只允许可重新解析的中立数据。 - `editor.ini` 是统一且可读的项目级 Editor settings 文档:标准 ImGui section 保存 layout;每个 Module/Panel 分别使用 `[InnoEditor][Module.]` / `[InnoEditor][Panel.]`;Panel 开关使用 `[InnoEditor][Panels]`。禁止用 Base64 或单一 opaque payload 包装全部 Workspace。Undo 栈、dirty Scene 内容、runtime 引用和编译中间态不得持久化。 +- 新项目的默认 layout 由标准 Host 的嵌入资源提供,Core 只接收中立初始文档并复用 Core IO 原子写入。仅文件不存在时初始化,不能覆盖已有或刻意留空的 `editor.ini`;模板不得带入示例场景、Asset ID、插件状态、Selection、History 或临时弹窗。 - Editor Selection 是当前 session 的瞬时交互状态,不得写入 `editor.ini`。Workspace 可以保存可独立解释的导航位置、已打开文档和 active document,但启动后不得自动恢复 Asset、Scene、GameObject、Component 或 System selection。 - Editor 正常退出时必须先捕获全部有状态的 Module/Panel,再捕获最新 ImGui layout,最后在 Module 停止和 Scene 卸载前强制原子写入一次完整 `editor.ini`。运行期间仍可节流保存,但不能把它当作退出保存的替代品。 - Editor Scene 修改统一进入 `Inno.Editor.Scene.SceneEdits`。普通属性只保存单 property bytes;Component/System 保存 element identity/type/index/state;GameObject 删除保存最小 subtree;层级只保存受影响 placement;禁止为小修改序列化或恢复完整 Scene。 @@ -192,6 +193,7 @@ - UI 与插件事件的订阅、分发及生命周期统一复用 `Inno.Core.Events`。领域可以定义自己的事件类型及适配入口,但不能另建队列、监听器注册表或事件总线来取代核心事件管理。 ## 23. 源码可读性与排版 +- 同一文件针对同一类型只保留一个语义清楚的别名,避免多个 facade 别名交替表达同一调用。XML 文档必须对应当前真实成员,一个成员只保留一个有意义的 summary,不保留已删除成员的说明。 - 可读性是所有手写源码的持续要求,覆盖 InnoEngine、Canvas 与 Rendering2D;生成的 Bindings 和第三方 `extern` 源码不纳入手工排版。改进排版时不得改变执行顺序、初始化顺序、公开契约、序列化数据、诊断或用户体验。 - 优先让读者一眼看清声明和意图:字段、属性、无参数或单参数函数、简单表达式在合理行宽内能完整写成一行时,保持一行;不得仅因机械行宽规则把名称、箭头表达式或成员修饰符随意拆散。 - 所有参数超过一个的函数声明(包括方法、构造函数、局部函数、委托、运算符、主构造函数和 Lambda)必须在 `(` 后换行,每个参数独占一行,最后一个参数后换行,并让 `)` 与声明起始行对齐。紧随参数列表的函数体左花括号与 `)` 同行,写成 `) {`;表达式体、约束和初始化器则在 `)` 后接其语法所需内容。无参数及单参数函数不强制展开。新增和修改源码均执行此规则;自动格式化不能覆盖该规则。 @@ -296,6 +298,7 @@ public void AAA( - 冷 CMake producer 通过共同 executor 固定 recipe 声明的工作区输入;Browser 聚合的组件 include、bridge 与参数必须解析到同一复制树。热缓存命中不复制,外部 SDK 与输出保持自己的位置和 owner,不用字符串替换整个仓库根来误重定向输出。 - Task runtime 只在 IBuildEngine4 Build 生命周期、同一完整请求身份内复用成功不可变 publication;registry 只存 BCL 数据,不持有 SDK owner、lease 或私有 ALC 类型。每个实际载入边界仍有独立 owner;失败/取消不缓存。共享 restore 写入由 Core.IO lease 协调,等待时 Yield/Reacquire,保留锁文件。 - 独立并发托管构建必须通过 SDK 的明确 ArtifactsPath 使用各自 bin/obj owner;共同规则不得覆盖该输出选择。NuGet Runtime 资产按产品真实 Restore 图选择,Native 内容寻址缓存仍通过自身 lease 共享。普通 IDE bin/obj 仅属于一个活动构建,不能把两个独立进程同时写同一路径视为受支持的并发模式。 +- 同一 SDK 图中的中立与产品请求也必须隔离实际输出;使用 ArtifactsPath 时,在 SDK 建立默认路径前选择包含产品输入的 ArtifactsProjectName,保留 SDK configuration/TFM/RID pivots 和显式调用方设置。开发 Editor 入口通过现有 owned restore 准备所选产品的完整引用闭包后再构建,不能依赖平台子项目稍后还原来满足入口的直接或 SDK 添加的传递引用。 - Owned NuGet Restore 必须使用标准 `ExcludeRestorePackageImports=true`,编译使用包含已恢复 imports 的独立求值配置。不得让同一缓存 ProjectInstance 把旧 package content items 带入新程序集输出,也不得通过包名特例、补写 NuGet imports 或删除缓存掩盖混装。 - NuGet 依赖使用正常 `.csproj` 的 `PackageReference` 与 SDK Restore,不启用、生成或提交依赖锁文件,不维护逐工程、宿主、RID、AOT 或裁剪的锁清单与命名规则。包版本由正式声明维护,BGCS 包族继续使用统一精确版本;消费者读取 SDK 实际求值的 assets 和包内容。CI 使用普通还原并验证构建、包身份和源码卫生;不得以自制依赖解析器、清空真实 SDK 属性或 ignore 规则代替正确流程。用于并发读写、进程所有权和原子发布的 Core IO lease 不属于依赖锁定,必须保留。 @@ -308,6 +311,7 @@ public void AAA( - 检查新增或修改的基础能力是否能够直接复用 Core IO、Events、Identity、Serialization 等既有契约;在不破坏领域边界的前提下消除重复协议、平行管理器和重复实现。 - 通过真实调用方、可替换边界及适用验证确认扩展能力。新增 backend 或平台时需要修改哪些 owner,必须能够从实际代码说明;不得用未执行的测试或抽象声明代替证据。 - 每次最终交付必须简要说明上述自查结果、实际验证证据及尚未解决或无法执行的事项。没有完成验证时明确说明,不得宣称已经达到完美或全部通过。 +- 每次准备结束执行前,逐项复核本轮 issue 的实际解决条件、获批计划的功能与验收、架构清晰度、基础能力复用及新增回归。仍有可推进的工作时必须更新剩余执行计划并自动继续,不重复请求批准;仅在全部适用工作完成或明确记录无法独立解除的外部阻塞后交付,不能以阶段性进展或 CI 通过代替整个计划完成。 ## 31. Issue 与 Pull Request 交付规范 @@ -316,3 +320,20 @@ public void AAA( - Pull Request 正文必须根据目标分支的完整差异说明问题、最终行为与实际验证,并明确未执行的检查;创建前检索已有 PR,避免重复,不得将部分验收描述为完整矩阵通过。 - Git 提交的 author 和 committer 均使用 `Aaron Liao `;不得使用 Codex/bot 身份或添加任何 `Co-authored-by` 署名。Pull Request 使用用户授权的 GitHub 账号创建。 - BGCS 缺陷必须通过公开 API 独立复现并明确标记;不得修改 BGCS 源码、包、二进制或手工修改生成物来绕过缺陷。 + +## 32. Editor UI ownership and acceptance + +- Domain editing in `features` must remain independent from native UI, Panels and Workbench. Panels compose feature APIs; they do not reference other Panels. Reusable controls belong to the session-owned ImGui adapter, and generic Graph presentation cannot depend on Shader semantics. +- Every interactive host owns one `EditorUiSession`. Transient widget/menu state belongs to its session and stable presentation owner. Native drawing borrows cannot survive their frame, window, context or owner thread. `current` is only a synchronous scoped accessor, never a global state owner. +- Continuous Inspector and viewport edits use one `EditorEditGesture` over `EditorInteractions.history`. Each sample records immediately; Escape rolls back the same transaction. Save, commands and generation changes finish the gesture before their own operation. No feature may delay History publication behind a separate UI-active flag. +- Selection, deferred commands and cross-frame tools resolve identities through Core Identity. Batch operations validate the complete selection, normalize overlapping roots and roll back atomically. Mixed, Missing, read-only and unassigned states remain distinct. +- Final delivery must evaluate architecture, organization, state/lifetime cleanliness, abstraction, reuse, visual consistency, extensibility, interaction, Undo/Redo, menu clarity and component granularity individually. Each evaluation must identify actual evidence, limitations and remaining improvements; directory changes and interface counts alone are insufficient. +- Reassess completion against the original approved plan, including every functional and quantitative acceptance requirement. Separate missing implementation from missing evidence; green CI and focused benchmarks cannot establish untested scale, visual, reload or lifecycle guarantees. Correct stale completion statements and issue ledgers when later review finds a gap. +- UI-neutral name formatting and Action/Menu queries must work without a native context or drawing session. Keep pure label formatting in Editor Core; only drawing and native borrows require an active UI scope. +- Temporary presentation worlds register their borrowed Core allocator through the existing selection coordinator's routing lease. Restore Edit selection before releasing that lease; failed retirement must preserve its owner. The routing table never becomes a second live-object authority. +- Boxed value types are not automatically neutral: a host struct may contain extension objects. Cross-callback targets retain only known neutral scalars strongly; all other non-identity objects use weak references, including boxed structs. Selection, focus and snapshots must not prolong their referenced objects' lifetime. +- The formal script SDK host references the actual exported API owners directly, including reusable presentation libraries when required. It must not use concrete Panels as transitive dependency carriers for asset, shader or graph authoring APIs. +- Session-owned zoom and density requests commit through the presentation preparation phase after selecting the owned context and display metrics, before native frame creation. Repeated preparation during the same drawing frame cannot publish a new metric snapshot. Standalone metrics remain usable without native UI. +- Presentation preparation, drawing and publication form one owned operation. Reentry and retirement during that operation must fail before releasing resources. A callback failure ends only its owned native frame, preserves the original failure and never publishes partial draw data; it must not clear another context's stacks or lower native assertions. +- Native presentation fixtures must create and enter an `EditorUiSession`, explicitly compose their feature/presentation owners, and resolve the exact service under test. Asynchronous shutdown fixtures must retry the public Pending retirement protocol until owned cleanup completes; they cannot rely on continuation timing or remove resource-release assertions. +- Presentation input snapshots expose physical Core modifier semantics. Reverse native shortcut normalization at the presentation boundary using the owned context configuration; do not interpret normalized Control/Super flags as physical keys or add shared-layer OS detection. Native input fixtures explicitly select their conventions and cover both modifier policies and native modes. diff --git a/Directory.Build.props b/Directory.Build.props index 1dad40cee..6e8cefb51 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,6 +17,12 @@ $(DefaultItemExcludes);$(MSBuildProjectDirectory)/obj/**;$(MSBuildProjectDirectory)/bin/** + + + <_InnoManagedProductPropertiesPath Condition="'$(InnoProductBuildProperties)' != ''">$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', '$(InnoProductBuildProperties)')) + $(MSBuildProjectName).$([MSBuild]::StableStringHash('$(InnoNativeTarget)|$(InnoToolTarget)|$(_InnoManagedProductPropertiesPath)|$(InnoNativeBuildFingerprint)', 'Sha256')) + + obj/$(InnoNativeTarget)/ bin/$(InnoNativeTarget)/ diff --git a/Directory.Build.targets b/Directory.Build.targets index b452cc3b9..12a634a91 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -76,6 +76,25 @@ + + + + + + + + <_InnoCompilerToolPropertyPattern>(?i)(^|;)(?:$([System.String]::Copy('$(InnoCompilerToolExcludedProperties);BuildProjectReferences;InnoTaskBootstrapOwner;BuildingInsideVisualStudio').Replace(';', '|')))=[^;]* + + + <_MSBuildProjectReferenceExistent Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(_MSBuildProjectReferenceExistent.AdditionalPropertiesFromProject)', '<IsRoslynComponent>\s*(?i:true)\s*</IsRoslynComponent>'))"> + true + $([System.Text.RegularExpressions.Regex]::Replace('%(_MSBuildProjectReferenceExistent.AdditionalProperties)', '$(_InnoCompilerToolPropertyPattern)', '')) + %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);$(InnoCompilerToolExcludedProperties);BuildProjectReferences;InnoTaskBootstrapOwner;BuildingInsideVisualStudio + + + + true @@ -206,14 +225,18 @@ + + $(MSBuildProjectFullPath) + + + Condition="'$(InnoProductRestoreProject)' != '' and '$(DesignTimeBuild)' != 'true' and '$(InnoProductRestore)' != 'false'"> diff --git a/InnoEngine.sln b/InnoEngine.sln index 518746974..d7b7e434f 100644 --- a/InnoEngine.sln +++ b/InnoEngine.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -111,7 +111,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Panel.FileBrows EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Panel.GameView", "src\composition\editor\panels\Inno.Editor.Panel.GameView\Inno.Editor.Panel.GameView.csproj", "{7EF925D1-9D9C-44D4-9B41-9D8A07A000BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Panel.Global", "src\composition\editor\panels\Inno.Editor.Panel.Global\Inno.Editor.Panel.Global.csproj", "{A32DDE6E-BA0D-40AA-BED3-1A10049D59BC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Workbench", "src\composition\editor\presentation\Inno.Editor.Workbench\Inno.Editor.Workbench.csproj", "{A32DDE6E-BA0D-40AA-BED3-1A10049D59BC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Panel.Hierarchy", "src\composition\editor\panels\Inno.Editor.Panel.Hierarchy\Inno.Editor.Panel.Hierarchy.csproj", "{720483C6-D06B-481D-8899-E1D9BD654EDA}" EndProject @@ -767,6 +767,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Integration.MacOSArm64 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Integration.BrowserWasm32.Build", "platforms\BrowserWasm32\integrations\Inno.Integration.BrowserWasm32.Build\Inno.Integration.BrowserWasm32.Build.csproj", "{1848B25F-5C18-41DD-8B7D-56E17CBDA88C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Assets.ImGui", "src\composition\editor\presentation\Inno.Editor.Assets.ImGui\Inno.Editor.Assets.ImGui.csproj", "{7CF807CF-4294-5C9F-9207-FEF4887D4164}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Graph.ImGui", "src\composition\editor\presentation\Inno.Editor.Graph.ImGui\Inno.Editor.Graph.ImGui.csproj", "{802F03CC-512F-59F1-9A3D-F7F7339CEE8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inno.Editor.Shaders.ImGui", "src\composition\editor\presentation\Inno.Editor.Shaders.ImGui\Inno.Editor.Shaders.ImGui.csproj", "{691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -783,6 +789,78 @@ Global SourceRelease|x86 = SourceRelease|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|x64.ActiveCfg = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|x64.Build.0 = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|x86.ActiveCfg = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Debug|x86.Build.0 = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|Any CPU.Build.0 = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|x64.ActiveCfg = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|x64.Build.0 = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|x86.ActiveCfg = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.Release|x86.Build.0 = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|Any CPU.ActiveCfg = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|Any CPU.Build.0 = Debug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|x64.ActiveCfg = SourceDebug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|x64.Build.0 = SourceDebug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|x86.ActiveCfg = SourceDebug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceDebug|x86.Build.0 = SourceDebug|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|Any CPU.ActiveCfg = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|Any CPU.Build.0 = Release|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|x64.ActiveCfg = SourceRelease|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|x64.Build.0 = SourceRelease|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|x86.ActiveCfg = SourceRelease|Any CPU + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1}.SourceRelease|x86.Build.0 = SourceRelease|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|x64.ActiveCfg = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|x64.Build.0 = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|x86.ActiveCfg = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Debug|x86.Build.0 = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|Any CPU.Build.0 = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|x64.ActiveCfg = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|x64.Build.0 = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|x86.ActiveCfg = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.Release|x86.Build.0 = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|Any CPU.ActiveCfg = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|Any CPU.Build.0 = Debug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|x64.ActiveCfg = SourceDebug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|x64.Build.0 = SourceDebug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|x86.ActiveCfg = SourceDebug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceDebug|x86.Build.0 = SourceDebug|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|Any CPU.ActiveCfg = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|Any CPU.Build.0 = Release|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|x64.ActiveCfg = SourceRelease|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|x64.Build.0 = SourceRelease|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|x86.ActiveCfg = SourceRelease|Any CPU + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C}.SourceRelease|x86.Build.0 = SourceRelease|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|x64.Build.0 = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Debug|x86.Build.0 = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|Any CPU.Build.0 = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|x64.ActiveCfg = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|x64.Build.0 = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|x86.ActiveCfg = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.Release|x86.Build.0 = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|Any CPU.ActiveCfg = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|Any CPU.Build.0 = Debug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|x64.ActiveCfg = SourceDebug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|x64.Build.0 = SourceDebug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|x86.ActiveCfg = SourceDebug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceDebug|x86.Build.0 = SourceDebug|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|Any CPU.ActiveCfg = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|Any CPU.Build.0 = Release|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|x64.ActiveCfg = SourceRelease|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|x64.Build.0 = SourceRelease|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|x86.ActiveCfg = SourceRelease|Any CPU + {7CF807CF-4294-5C9F-9207-FEF4887D4164}.SourceRelease|x86.Build.0 = SourceRelease|Any CPU {E05AA6C5-47EA-46E2-AB87-AAB36667880D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E05AA6C5-47EA-46E2-AB87-AAB36667880D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E05AA6C5-47EA-46E2-AB87-AAB36667880D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -6478,6 +6556,9 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {691683C5-2477-5FA0-AFCA-9A56E3ADDAD1} = {DB68A96A-C62D-504F-BD0B-71520E141558} + {802F03CC-512F-59F1-9A3D-F7F7339CEE8C} = {DB68A96A-C62D-504F-BD0B-71520E141558} + {7CF807CF-4294-5C9F-9207-FEF4887D4164} = {DB68A96A-C62D-504F-BD0B-71520E141558} {E05AA6C5-47EA-46E2-AB87-AAB36667880D} = {FB6B5AAC-8FC0-40B6-993E-9B06BD070F2B} {6B47D935-1188-4474-BFC8-032732BD2276} = {FB6B5AAC-8FC0-40B6-993E-9B06BD070F2B} {F96214FE-3779-5DE2-891C-9F770F0035F6} = {B4620EEB-BE09-5C67-9437-083242A0513D} @@ -6530,7 +6611,7 @@ Global {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} = {8B7360D5-B8A7-5423-BD4A-8D41C720C3E6} {3F3E5448-3C50-455E-A18A-8057D5153231} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} {7EF925D1-9D9C-44D4-9B41-9D8A07A000BA} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} - {A32DDE6E-BA0D-40AA-BED3-1A10049D59BC} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} + {A32DDE6E-BA0D-40AA-BED3-1A10049D59BC} = {DB68A96A-C62D-504F-BD0B-71520E141558} {720483C6-D06B-481D-8899-E1D9BD654EDA} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} {37EFCF00-19A6-497D-8E6B-49876C9E1985} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} {860DF6A9-D1D5-49E3-9CE5-D2DE347CEED0} = {C88FEFB4-C927-523A-A2CE-3A8BD4D591D7} diff --git a/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/Inno.Build.Toolchains.Bgfx.Shaders.csproj b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/Inno.Build.Toolchains.Bgfx.Shaders.csproj index e3c8e2e4a..ed8788e20 100644 --- a/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/Inno.Build.Toolchains.Bgfx.Shaders.csproj +++ b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/Inno.Build.Toolchains.Bgfx.Shaders.csproj @@ -16,6 +16,7 @@ + diff --git a/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs index 22d70225f..fb1ea281f 100644 --- a/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs +++ b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs @@ -28,8 +28,11 @@ public static class ShaderArtifactBuilder /// /// Imports and compiles one shader graph for an explicitly selected graphics target. /// - /// - /// The directory containing shader authoring sources. + /// + /// The operation's fixed source and permanent import-setting identity, copied before import. + /// + /// + /// A fresh caller-owned operation directory retained until compilation returns. The caller releases it after all consumers finish. /// /// /// The source-local shader graph path. @@ -47,7 +50,16 @@ public static class ShaderArtifactBuilder /// Import or shader compilation fails. /// /// - /// A required authoring path or output path is blank. + /// A required shader path or output path is blank. + /// + /// + /// The fixed input identity or workspace is null. + /// + /// + /// The borrowed workspace has been released or is not empty. + /// + /// + /// Import, copying, verification or compilation was canceled before publication. /// /// /// The host-selected executable distribution, or null for the application's deployed tools. @@ -56,7 +68,8 @@ public static class ShaderArtifactBuilder /// Cancels import and native compilation before replacing the destination artifact. /// public static void Compile( - string assetRoot, + ShaderBuildInputs inputs, + ProcessOwnedDirectory workspace, string shaderPath, BgfxShaderTargetProfile platform, GraphicsApi backend, @@ -65,16 +78,20 @@ public static void Compile( CancellationToken cancellationToken = default ) { ArgumentException.ThrowIfNullOrWhiteSpace(outputFile); - RenderShaderArtifact artifact = CompileArtifact(assetRoot, shaderPath, platform, backend, tools, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - AtomicFile.WriteAllBytes(Path.GetFullPath(outputFile), RenderShaderArtifactCodec.Encode(artifact)); + RenderShaderArtifact artifact = CompileArtifact(inputs, workspace, shaderPath, platform, backend, tools, cancellationToken); + byte[] payload = RenderShaderArtifactCodec.Encode(artifact); + inputs.VerifyUnchanged(cancellationToken); + AtomicFile.WriteAllBytes(Path.GetFullPath(outputFile), payload); } /// /// Compiles one raster graph into device-only program facts for an embedded adapter distribution. /// - /// - /// The directory containing the graph and its source dependencies. + /// + /// The operation's fixed source and permanent import-setting identity, copied before import. + /// + /// + /// A fresh caller-owned operation directory retained until compilation returns. The caller releases it after all consumers finish. /// /// /// The source-local graph whose sole raster pass becomes the device program. @@ -89,11 +106,20 @@ public static void Compile( /// The device artifact destination, atomically replaced after successful compilation. /// /// - /// A required authoring or output path is blank. + /// A required shader path or output path is blank. /// /// /// Compilation fails or the graph does not define exactly one raster program. /// + /// + /// The fixed input identity or workspace is null. + /// + /// + /// The borrowed workspace has been released or is not empty. + /// + /// + /// Import, copying, verification or compilation was canceled before publication. + /// /// /// The host-selected executable distribution, or null for the application's deployed tools. /// @@ -101,7 +127,8 @@ public static void Compile( /// Cancels import and native compilation before replacing the destination artifact. /// public static void CompileGraphicsProgram( - string assetRoot, + ShaderBuildInputs inputs, + ProcessOwnedDirectory workspace, string shaderPath, BgfxShaderTargetProfile platform, GraphicsApi backend, @@ -110,7 +137,7 @@ public static void CompileGraphicsProgram( CancellationToken cancellationToken = default ) { ArgumentException.ThrowIfNullOrWhiteSpace(outputFile); - RenderShaderArtifact artifact = CompileArtifact(assetRoot, shaderPath, platform, backend, tools, cancellationToken); + RenderShaderArtifact artifact = CompileArtifact(inputs, workspace, shaderPath, platform, backend, tools, cancellationToken); if (artifact.passes.Count != 1 || artifact.passes[0].programKind != ShaderProgramKind.Raster) throw new InvalidDataException("A device graphics program requires exactly one raster pass."); RenderShaderPassArtifact pass = artifact.passes[0]; @@ -133,56 +160,55 @@ public static void CompileGraphicsProgram( pass.stages.Single(static stage => stage.stage == ShaderStage.Vertex).bytes.Span, pass.stages.Single(static stage => stage.stage == ShaderStage.Fragment).bytes.Span, bindings, null, pass.rasterState); - cancellationToken.ThrowIfCancellationRequested(); - AtomicFile.WriteAllBytes(Path.GetFullPath(outputFile), GraphicsProgramArtifactCodec.Encode(descriptor)); + byte[] payload = GraphicsProgramArtifactCodec.Encode(descriptor); + inputs.VerifyUnchanged(cancellationToken); + AtomicFile.WriteAllBytes(Path.GetFullPath(outputFile), payload); } private static RenderShaderArtifact CompileArtifact( - string assetRoot, + ShaderBuildInputs inputs, + ProcessOwnedDirectory workspace, string shaderPath, BgfxShaderTargetProfile platform, GraphicsApi backend, ToolRunner? tools, CancellationToken cancellationToken ) { - ArgumentException.ThrowIfNullOrWhiteSpace(assetRoot); + ArgumentNullException.ThrowIfNull(inputs); + ArgumentNullException.ThrowIfNull(workspace); ArgumentException.ThrowIfNullOrWhiteSpace(shaderPath); - string scratch = Path.Combine(Path.GetTempPath(), "InnoShaderBuild", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(scratch); - try - { - var identities = new IdentityAllocator(); - using IDisposable identityScope = identities.EnterScope(); - var diagnostics = new DiagnosticHub(); - using IDisposable diagnosticScope = diagnostics.EnterScope(); - diagnostics.RegisterSink(new DiagnosticOutput()); - using var logs = new LogRouter(); - using var modules = new ModuleHost(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ShaderArtifactBuilder).Assembly)}); - using var types = new TypeCatalog(modules, new ReflectionTypeCatalogSource()); - using var serialization = new SerializationRegistry(types, new ReflectionSerializationMetadataSource()); - using var assets = new AssetPipeline(modules, types, serialization, identities, diagnostics, logs, - new() { assetRoot = Path.GetFullPath(assetRoot), libraryRoot = Path.Combine(scratch, "Library") }); - AssetPath path = AssetPath.Project(shaderPath); - // A fresh source import must succeed. An earlier build artifact never masks current source errors. - if (!assets.Import(path)) - throw new InvalidDataException($"Shader source '{path}' failed to import."); - ShaderAsset shader = assets.Load(path); - GraphicsCapabilities capabilities = platform.Resolve(backend).capabilities; - var compiler = new ShaderCompiler(new BgfxShadercToolchain(platform, tools)); - ShaderCompileTarget target = compiler.CreateTarget(capabilities); - ShaderCompilationResult result = compiler.CompileGraphAsync(shader, target, RenderShaderVariant.empty, - types, serialization, AssetSerializationContext.Create(assets), assets, cancellationToken).AsTask().GetAwaiter().GetResult(); - foreach (ShaderDiagnostic diagnostic in result.diagnostics) - Console.Error.WriteLine($"{diagnostic.code}: {diagnostic.message}"); - if (!result.succeeded || result.artifact is null) - throw new InvalidDataException("Shader compilation failed; the output was not replaced."); - Console.WriteLine($"Compiled {path}: {result.artifact.passes.Count} passes, {target.key}"); - return result.artifact.CreateRuntimeArtifact(); - } - finally - { - Directory.Delete(scratch, recursive: true); - } + string scratch = workspace.directory; + if (!Directory.Exists(scratch) || Directory.EnumerateFileSystemEntries(scratch).Any()) + throw new InvalidOperationException("Shader compilation requires a fresh, retained operation directory."); + string assetRoot = inputs.Materialize(scratch, cancellationToken); + var identities = new IdentityAllocator(); + using IDisposable identityScope = identities.EnterScope(); + var diagnostics = new DiagnosticHub(); + using IDisposable diagnosticScope = diagnostics.EnterScope(); + diagnostics.RegisterSink(new DiagnosticOutput()); + using var logs = new LogRouter(); + using var modules = new ModuleHost(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ShaderArtifactBuilder).Assembly)}); + using var types = new TypeCatalog(modules, new ReflectionTypeCatalogSource()); + using var serialization = new SerializationRegistry(types, new ReflectionSerializationMetadataSource()); + using var assets = new AssetPipeline(modules, types, serialization, identities, diagnostics, logs, + new() { assetRoot = Path.GetFullPath(assetRoot), libraryRoot = Path.Combine(scratch, "Library") }); + AssetPath path = AssetPath.Project(shaderPath); + // A fresh source import must succeed. An earlier build artifact never masks current source errors. + if (!assets.Import(path)) + throw new InvalidDataException($"Shader source '{path}' failed to import."); + ShaderAsset shader = assets.Load(path); + GraphicsCapabilities capabilities = platform.Resolve(backend).capabilities; + var compiler = new ShaderCompiler(new BgfxShadercToolchain(platform, tools)); + ShaderCompileTarget target = compiler.CreateTarget(capabilities); + ShaderCompilationResult result = compiler.CompileGraphAsync(shader, target, RenderShaderVariant.empty, + types, serialization, AssetSerializationContext.Create(assets), assets, cancellationToken).AsTask().GetAwaiter().GetResult(); + foreach (ShaderDiagnostic diagnostic in result.diagnostics) + Console.Error.WriteLine($"{diagnostic.code}: {diagnostic.message}"); + if (!result.succeeded || result.artifact is null) + throw new InvalidDataException("Shader compilation failed; the output was not replaced."); + Console.WriteLine($"Compiled {path}: {result.artifact.passes.Count} passes, {target.key}"); + inputs.VerifyUnchanged(cancellationToken); + return result.artifact.CreateRuntimeArtifact(); } private sealed class DiagnosticOutput : IDiagnosticSink diff --git a/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs new file mode 100644 index 000000000..96eecf5cc --- /dev/null +++ b/backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Inno.Assets.Pipeline; +using Inno.Build.Toolchains; +using Inno.Core.IO; + +namespace Inno.Build.Toolchains.Bgfx.Shaders; + +/// +/// Fixes the complete authored shader file inventory and content identity for one compilation operation. +/// +/// +/// Permanent import metadata participates in the identity; derived artifacts, source noise and atomic +/// publication candidates do not. Compilation copies these inputs into its owned source tree. This +/// value neither locks the authoring source nor authorizes its modification or cleanup. +/// +public sealed class ShaderBuildInputs +{ + private readonly string m_sourceRoot; + private readonly NativeBuildInput[] m_files; + private readonly string[] m_directories; + + private ShaderBuildInputs( + string sourceRoot, + Inventory inventory, + CancellationToken cancellationToken + ) { + m_sourceRoot = sourceRoot; + m_files = inventory.files; + m_directories = inventory.directories; + fingerprint = ComputeFingerprint(inventory, cancellationToken); + } + + /// + /// Gets the complete logical directory, file and content identity used by the enclosing build. + /// + public string fingerprint { get; } + + /// + /// Captures permanent authoring files and their import metadata without consuming temporary publications. + /// + /// + /// The existing physical authoring root, containing no source links. + /// + /// + /// Cancels discovery and complete content hashing. + /// + /// + /// A fixed input inventory and content identity; physical source files remain owned by their author. + /// + /// + /// The source root is blank. + /// + /// + /// A source cannot be read, does not exist, or crosses a link. + /// + /// + /// Capture was canceled. + /// + public static ShaderBuildInputs Capture( + string assetRoot, + CancellationToken cancellationToken = default + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(assetRoot); + string root = Path.TrimEndingDirectorySeparator(Path.GetFullPath(assetRoot)); + return new ShaderBuildInputs(root, Discover(root, cancellationToken), cancellationToken); + } + + /// + /// Rechecks the complete current inventory and bytes against this operation's captured identity. + /// + /// + /// Cancels discovery and complete content verification. + /// + /// + /// A source or permanent metadata file was added, removed or changed. + /// + /// + /// The source tree cannot be safely inspected or read. + /// + /// + /// Verification was canceled. + /// + public void VerifyUnchanged(CancellationToken cancellationToken = default) + { + string actual = ComputeFingerprint(Discover(m_sourceRoot, cancellationToken), cancellationToken); + if (!string.Equals(actual, fingerprint, StringComparison.Ordinal)) + throw new InvalidDataException("Shader authoring inputs changed during compilation; the candidate cannot be published."); + } + + internal string Materialize( + string operationDirectory, + CancellationToken cancellationToken + ) { + string destination = PathBoundary.Resolve(operationDirectory, "Source"); + Directory.CreateDirectory(destination); + foreach (string relative in m_directories) + { + cancellationToken.ThrowIfCancellationRequested(); + Directory.CreateDirectory(PathBoundary.RequireUnlinkedPath(destination, PathBoundary.Resolve(destination, relative))); + } + List copies = []; + foreach (NativeBuildInput input in m_files) + { + cancellationToken.ThrowIfCancellationRequested(); + string relative = Path.GetRelativePath(m_sourceRoot, input.physicalPath); + string source = PathBoundary.RequireUnlinkedPath(m_sourceRoot, input.physicalPath); + string copy = PathBoundary.RequireUnlinkedPath(destination, PathBoundary.Resolve(destination, relative)); + Directory.CreateDirectory(Path.GetDirectoryName(copy)!); + using (var reading = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var writing = new FileStream(copy, FileMode.CreateNew, FileAccess.Write, FileShare.None)) + reading.CopyToAsync(writing, cancellationToken).GetAwaiter().GetResult(); + copies.Add(new NativeBuildInput(input.logicalPath, copy)); + } + if (ComputeFingerprint(new Inventory([.. copies], m_directories), cancellationToken) != fingerprint) + throw new InvalidDataException("Shader inputs changed while copying the frozen compilation tree."); + VerifyUnchanged(cancellationToken); + return destination; + } + + private static string ComputeFingerprint( + Inventory inventory, + CancellationToken cancellationToken + ) => NativeBuildFingerprint.Create( + inventory.directories.Select(static path => "shader-directory/" + path), inventory.files, cancellationToken); + + private static Inventory Discover( + string root, + CancellationToken cancellationToken + ) { + if (!Directory.Exists(root)) + throw new DirectoryNotFoundException($"Shader authoring root '{root}' does not exist."); + var pending = new Stack(); + List files = []; + List directories = []; + pending.Push(root); + AssetSourcePolicy policy = AssetSourcePolicy.defaultPolicy; + while (pending.TryPop(out string? directory)) + { + cancellationToken.ThrowIfCancellationRequested(); + PathBoundary.RequireUnlinkedPath(root, directory); + directories.Add(directory == root ? string.Empty : Path.GetRelativePath(root, directory).Replace('\\', '/')); + foreach (string child in Directory.EnumerateDirectories(directory)) + { + if (!policy.IsIgnored(Path.GetRelativePath(root, child), isDirectory: true)) + pending.Push(child); + } + foreach (string path in Directory.EnumerateFiles(directory)) + { + cancellationToken.ThrowIfCancellationRequested(); + string relative = Path.GetRelativePath(root, path).Replace('\\', '/'); + if (AtomicFile.IsCandidatePath(relative)) + continue; + bool metadata = relative.EndsWith(".imeta", StringComparison.OrdinalIgnoreCase); + if (!metadata && policy.IsIgnored(relative, isDirectory: false)) + continue; + PathBoundary.RequireUnlinkedPath(root, path); + files.Add(new NativeBuildInput("shader-source/" + relative, path)); + } + } + return new Inventory(files.OrderBy(static input => input.logicalPath, StringComparer.Ordinal).ToArray(), + directories.Order(StringComparer.Ordinal).ToArray()); + } + + private sealed record Inventory( + NativeBuildInput[] files, + string[] directories + ); +} diff --git a/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/Discovery/ReflectionStructMetadata.cs b/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/Discovery/ReflectionStructMetadata.cs index 35cf4ed58..ebcb8b18e 100644 --- a/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/Discovery/ReflectionStructMetadata.cs +++ b/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/Discovery/ReflectionStructMetadata.cs @@ -20,6 +20,24 @@ internal static SerializationMemberMetadata[] GetMembers(Type structType) return S_CACHE.GetValue(structType, static type => new MembersBox(BuildMembers(type))).members; } + internal static bool HasPureFieldAccess( + Type type, + SerializationTypeMetadata metadata + ) { + if (!type.IsValueType || metadata.requiresConverter || metadata.collection is not null || metadata.members.Count == 0) + return false; + foreach (SerializationMemberMetadata member in metadata.members) + { + if ((member.visibility & PropertyVisibility.Serialize) == 0) continue; + FieldInfo? field = type.GetField(member.name, C_MEMBERS); + if (field is null || field.FieldType != member.type || + !(field.FieldType.IsPrimitive && field.FieldType != typeof(IntPtr) && field.FieldType != typeof(UIntPtr) && field.FieldType != typeof(char) + || field.FieldType == typeof(decimal) || field.FieldType == typeof(System.Guid) || field.FieldType == typeof(string))) + return false; + } + return true; + } + private static SerializationMemberMetadata[] BuildMembers(Type structType) { var members = new List(); diff --git a/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/ReflectionSerializationMetadataSource.cs b/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/ReflectionSerializationMetadataSource.cs index 6e2b0b1b6..b9223c45c 100644 --- a/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/ReflectionSerializationMetadataSource.cs +++ b/backends/DotNet/runtime/Inno.Adapter.Serialization.DotNet/ReflectionSerializationMetadataSource.cs @@ -43,9 +43,38 @@ private static SerializationTypeMetadata BuildMetadata(Type type) Func? factory = !type.IsAbstract && !type.IsInterface && !type.ContainsGenericParameters && (type.IsValueType || type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, Type.EmptyTypes, modifiers: null) is not null) ? () => CreateInstance(type) : null; - return new SerializationTypeMetadata(type, members, factory, + var metadata = new SerializationTypeMetadata(type, members, factory, typeof(ISerializable).IsAssignableFrom(type) ? ReflectionObjectMetadata.CreateRestoreHandler(type) : null, collection, type.IsDefined(typeof(RequiresSerializationConverterAttribute), inherit: true)); + if (ReflectionStructMetadata.HasPureFieldAccess(type, metadata)) + metadata = metadata.WithPureFieldAccess(); + SerializationRevisionAttribute? revision = type.GetCustomAttribute(inherit: false); + if (revision is null) + return metadata; + foreach (SerializationMemberMetadata member in metadata.members) + if (string.Equals(member.name, revision.memberName, StringComparison.Ordinal) && + (member.visibility & PropertyVisibility.Serialize) != 0) + { + throw new InvalidOperationException( + $"Capture revision '{type.FullName}.{revision.memberName}' must not be serialized."); + } + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + FieldInfo? field = type.GetField(revision.memberName, flags); + if (field is not null && !field.IsStatic && (field.FieldType == typeof(ulong) || field.FieldType == typeof(ulong?))) + { + Func readField = field.GetValue; + return metadata.WithCaptureRevision(value => ReadCaptureRevision(value, readField)); + } + PropertyInfo? property = type.GetProperty(revision.memberName, flags); + if (property is not null && property.GetIndexParameters().Length == 0 && + property.GetGetMethod(true) is { IsStatic: false } + && (property.PropertyType == typeof(ulong) || property.PropertyType == typeof(ulong?))) + { + Func readProperty = property.GetValue; + return metadata.WithCaptureRevision(value => ReadCaptureRevision(value, readProperty)); + } + throw new InvalidOperationException( + $"Capture revision '{type.FullName}.{revision.memberName}' must be a readable ulong or nullable ulong instance member."); } private static object CreateInstance(Type type) @@ -61,4 +90,19 @@ private static object CreateInstance(Type type) throw; } } + + private static ulong? ReadCaptureRevision( + object value, + Func reader + ) { + try + { + return (ulong?)reader(value); + } + catch (TargetInvocationException exception) when (exception.InnerException is not null) + { + ExceptionDispatchInfo.Capture(exception.InnerException).Throw(); + throw; + } + } } diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs index 01a27eba5..9bee115a5 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs @@ -104,6 +104,9 @@ GraphicsPipelineDescriptor pipelineDescriptor /// public bool supportsViewports => m_bgfxDevice.supportsAdditionalSurfaces; + /// + public bool supportsVertexOffsets => true; + /// /// Queues a compiled shader replacement that commits atomically at a frame safety point. /// diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationContext.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationContext.cs index f08bd66f0..25dfe8b2e 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationContext.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationContext.cs @@ -60,10 +60,12 @@ bool force ) => m_context.TryCaptureIniSettings(out settings, force); - void IPresentationContext.RenderFrame(Action drawFrame) - { + void IPresentationContext.RenderFrame( + Action drawFrame, + Action? prepareFrame + ) { ArgumentNullException.ThrowIfNull(drawFrame); - _ = m_context.RenderFrame(drawFrame); + _ = m_context.RenderFrame(drawFrame, prepareFrame); } PresentationTextureHandle IPresentationContext.RegisterTexture(PersistentTextureHandle texture) diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs index 3fcae50fd..77fba39da 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs @@ -87,6 +87,11 @@ int height /// public interface IPlatformImGuiRenderer : IDisposable { + /// + /// Gets whether every draw-data consumer applies per-command vertex offsets to 16-bit indices. + /// + bool supportsVertexOffsets { get; } + /// /// Gets whether detached viewport windows are rendered by this backend. /// diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs index 9b4d976f5..7e3a0ab59 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs @@ -94,10 +94,25 @@ public partial bool TryCaptureIniSettings( /// /// Draw callback that issues ImGui commands for this frame. /// + /// + /// Optional preparation after context and display metrics selection, before native frame creation. + /// /// - /// The native pointer to ImDrawData for the rendered frame. + /// Borrowed ImDrawData valid until the next frame or context retirement; the caller must not free it. /// - public partial IntPtr RenderFrame(Action drawFrame); + /// + /// This context has retired. + /// + /// + /// A frame operation is already active. Callbacks must balance their own drawing scopes. + /// + /// + /// A callback failed and its owned native frame also failed to retire. + /// + public partial IntPtr RenderFrame( + Action drawFrame, + Action? prepareFrame = null + ); /// /// Draws a renderer-registered texture without exposing a native ImGui texture identifier. @@ -127,5 +142,8 @@ public unsafe partial void DrawImage( /// /// Releases all unmanaged/native resources associated with this ImGui context. /// + /// + /// A frame operation is still using this context. No resources are released by this rejection. + /// public partial void Dispose(); } diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs index 7efa5e7cc..397a835d5 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs @@ -17,6 +17,7 @@ private sealed class ImGuiState : ISdl3ApplicationExtension { private readonly Sdl3PlatformApplication m_application; private IDisposable? m_registration; + private bool m_isRetiring; internal ImGuiState(Sdl3PlatformApplication application) { @@ -24,12 +25,22 @@ internal ImGuiState(Sdl3PlatformApplication application) } internal Dictionary contexts { get; } = []; + internal bool isRetiring => m_isRetiring; internal void Register() { m_registration = m_application.RegisterExtension(this); } + internal void Release( + uint windowId, + PlatformImGuiContext context + ) { + if (!contexts.TryGetValue(windowId, out PlatformImGuiContext? registered) || !ReferenceEquals(registered, context)) + throw new InvalidOperationException("The retired ImGui context no longer owns its application registration."); + contexts.Remove(windowId); + } + void ISdl3ApplicationExtension.ProcessNativeEvent( Sdl3PlatformApplication application, scoped ReadOnlySpan nativeEventData @@ -54,9 +65,11 @@ uint windowId void ISdl3ApplicationExtension.OnApplicationDisposing(Sdl3PlatformApplication application) { - foreach (PlatformImGuiContext context in contexts.Values) + m_isRetiring = true; + var retiring = new PlatformImGuiContext[contexts.Count]; + contexts.Values.CopyTo(retiring, 0); + foreach (PlatformImGuiContext context in retiring) context.Dispose(); - contexts.Clear(); s_states.Remove(application); m_registration?.Dispose(); m_registration = null; @@ -115,11 +128,15 @@ public static PlatformImGuiContext CreateImGuiContext( throw new ArgumentException("The window must belong to the supplied SDL application.", nameof(window)); ImGuiState state = s_states.GetValue(application, CreateState); + if (state.isRetiring) + throw new InvalidOperationException("The application presentation state is retiring and cannot create another context."); if (state.contexts.ContainsKey(window.windowId)) throw new InvalidOperationException("This window already owns an ImGui context."); - var context = new PlatformImGuiContext(application, window, contextFlags, interaction, renderer); - state.contexts[window.windowId] = context; + uint windowId = window.windowId; + var context = new PlatformImGuiContext(application, window, contextFlags, interaction, renderer, + retired => state.Release(windowId, retired)); + state.contexts[windowId] = context; return context; } @@ -150,7 +167,6 @@ Sdl3PlatformWindow window if (!state.contexts.TryGetValue(window.windowId, out PlatformImGuiContext? context)) return; context.Dispose(); - state.contexts.Remove(window.windowId); } private static ImGuiState CreateState(Sdl3PlatformApplication application) diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/LICENSE b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/LICENSE new file mode 100644 index 000000000..d952d62c0 --- /dev/null +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/LICENSE @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/NotoSansCJKsc-Regular.otf b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/NotoSansCJKsc-Regular.otf new file mode 100644 index 000000000..dc1556247 Binary files /dev/null and b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/NotoSansCJKsc-Regular.otf differ diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/README.md b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/README.md new file mode 100644 index 000000000..5113dad7c --- /dev/null +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/BuiltIn/Fonts/Supplemental/README.md @@ -0,0 +1,12 @@ +# Supplemental text font + +This unmodified Noto Sans CJK SC Regular 2.004 face supplies CJK glyphs missing from the existing text faces. Existing text and icon sources retain priority. It is loaded once into one context-owned pinned buffer, shared by that context's native font sources and released after native context destruction. Glyphs are baked on demand; the complete glyph set is not pre-rendered. + +- Upstream: https://github.com/notofonts/noto-cjk +- Font: https://raw.githubusercontent.com/notofonts/noto-cjk/Sans2.004/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf +- Font SHA-256: `2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b` +- Font bytes: 16,437,364 +- License: unmodified SIL Open Font License text in [LICENSE](LICENSE), obtained from https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/LICENSE +- License SHA-256: `6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2` + +The shared presentation adapter owns this content. No operating-system font lookup, platform table, separate font manager or generated-binding change is needed. diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.Fonts.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.Fonts.cs new file mode 100644 index 000000000..8762ecd7c --- /dev/null +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.Fonts.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using Inno.Native.ImGui; +using ImGuiNative = Inno.Native.ImGui.ImGui; + +namespace Inno.Adapter.Presentation.ImGui; + +/// +/// Owns validated supplemental font data and its lifetime when borrowed by the native atlas. +/// +public sealed unsafe partial class PlatformImGuiContext +{ + private const string C_SUPPLEMENTAL_FONT_FILE = "NotoSansCJKsc-Regular.otf"; + private const int C_SUPPLEMENTAL_FONT_BYTES = 16_437_364; + private const string C_SUPPLEMENTAL_FONT_HASH = "2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b"; + + private static byte[] ReadSupplementalFontData() + { + foreach (string directory in ResolveAssetDirectories(Path.Combine(DEFAULT_FONTS_DIRECTORY_RELATIVE_PATH, "Supplemental"))) + { + string path = Path.Combine(directory, C_SUPPLEMENTAL_FONT_FILE); + if (!File.Exists(path)) + continue; + using FileStream source = File.OpenRead(path); + if (source.Length != C_SUPPLEMENTAL_FONT_BYTES) + throw new InvalidDataException($"The bundled supplemental text font '{path}' has an unexpected length."); + byte[] data = GC.AllocateUninitializedArray(C_SUPPLEMENTAL_FONT_BYTES, pinned: true); + source.ReadExactly(data); + if (source.ReadByte() != -1 || !string.Equals(Convert.ToHexStringLower(SHA256.HashData(data)), C_SUPPLEMENTAL_FONT_HASH, StringComparison.Ordinal)) + throw new InvalidDataException($"The bundled supplemental text font '{path}' does not match its declared content identity."); + return data; + } + throw new FileNotFoundException("The bundled supplemental text font is required for readable CJK labels.", C_SUPPLEMENTAL_FONT_FILE); + } + + private static void MergeSupplementalFont( + ImFontAtlas* fonts, + byte[] data + ) { + ImFontConfig* configuration = ImGuiNative.Raw.ImFontConfig(); + try + { + configuration->MergeMode = 1; + // All faces borrow the same permanently pinned buffer until their owning context is destroyed. + configuration->FontDataOwnedByAtlas = 0; + fixed (byte* pointer = data) + if (ImGuiNative.Raw.AddFontFromMemoryTTF(fonts, pointer, data.Length, DEFAULT_FONT_SIZE_PIXELS, configuration, null) is null) + throw new InvalidOperationException("The bundled supplemental text font could not be added to the native atlas."); + } + finally + { + ImGuiNative.Raw.Destroy(configuration); + } + } +} diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.cs index 2517ddfc2..20f6c257e 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Context/PlatformImGuiContext.cs @@ -41,6 +41,8 @@ public sealed unsafe partial class PlatformImGuiContext private SDLWindow* m_textInputWindow = null; private TimeSpan m_lastFrameTime; private TimeSpan m_lastLiveResizeLockTime; + private Action? m_releaseRegistration; + private byte[]? m_supplementalFontData; private IntPtr m_iniFilename; private uint m_liveResizeLockedWindowId; private uint m_mousePendingLeaveWindowId; @@ -50,6 +52,7 @@ public sealed unsafe partial class PlatformImGuiContext private readonly bool m_enableSmoothResize; private bool m_hasStartedFrame; private bool m_isFrameActive; + private bool m_isFrameOperationActive; private bool m_textInputActive; private bool m_disposed; @@ -58,8 +61,10 @@ internal PlatformImGuiContext( Sdl3PlatformWindow window, ImGuiContextFlags contextFlags, ImGuiInteractionOptions interaction, - IPlatformImGuiRenderer? renderer + IPlatformImGuiRenderer? renderer, + Action releaseRegistration ) { + m_releaseRegistration = releaseRegistration ?? throw new ArgumentNullException(nameof(releaseRegistration)); var enableViewports = (contextFlags & ImGuiContextFlags.EnableViewports) != 0; var enableDocking = (contextFlags & ImGuiContextFlags.EnableDocking) != 0; m_enableSmoothResize = (contextFlags & ImGuiContextFlags.EnableSmoothResize) != 0; @@ -97,11 +102,14 @@ internal PlatformImGuiContext( io->MouseDrawCursor = 0; io->Fonts->RendererHasTextures = 1; ConfigureKeyRepeat(io); - ConfigureFonts(m_context, io); + m_supplementalFontData = ReadSupplementalFontData(); + ConfigureFonts(m_context, io, m_supplementalFontData); ImGuiPackedColor.EnsureInitialized(); UpdateDisplayMetrics(io); m_renderer = renderer ?? new PlatformImGuiSdlRenderer(window); + if (m_renderer.supportsVertexOffsets) + io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasVtxOffset; m_viewports = enableViewports ? new PlatformImGuiViewportBackend(application, window, m_renderer) : null; RegisterClipboardCallbacks(); } @@ -110,6 +118,8 @@ internal PlatformImGuiContext( m_viewports?.Dispose(); ImGuiFont.UnregisterContext(m_context); ImGuiNative.Raw.DestroyContext(m_context); + GC.KeepAlive(m_supplementalFontData); + m_supplementalFontData = null; if (renderer is null) m_renderer?.Dispose(); throw; @@ -313,6 +323,7 @@ float fontSizePixels ResolveFontPaths(out _, out List iconFontPaths); MergeIconFontsIntoCurrentBaseFont(io->Fonts, iconFontPaths); + MergeSupplementalFont(io->Fonts, m_supplementalFontData!); ImGuiFont.RegisterStyle(m_context, style, (nint)loadedFont); if (style == ImGuiFontStyle.Regular) io->FontDefault = loadedFont; @@ -320,7 +331,8 @@ float fontSizePixels private static void ConfigureFonts( ImGuiNativeContext* context, - ImGuiIO* io + ImGuiIO* io, + byte[] supplementalFontData ) { var fonts = io->Fonts; ImGuiNative.Raw.Clear(fonts); @@ -338,6 +350,7 @@ private static void ConfigureFonts( } MergeIconFontsIntoCurrentBaseFont(fonts, iconFontPaths); + MergeSupplementalFont(fonts, supplementalFontData); styleFonts[GetFontStyle(fontPaths[i])] = (nint)loadedFont; if (i == preferredIndex) { @@ -355,6 +368,7 @@ private static void ConfigureFonts( if (defaultFont != null) { MergeIconFontsIntoCurrentBaseFont(fonts, iconFontPaths); + MergeSupplementalFont(fonts, supplementalFontData); } } @@ -658,35 +672,73 @@ internal void PrepareLiveResizeWindow(uint windowId) /// /// The callback invoked by render frame within the operation's owned lifetime. /// + /// + /// Optional callback invoked with the owned context before native frame creation. + /// /// - /// The validated int ptr that represents the completed operation. + /// Borrowed native draw data for the completed frame, valid until the next frame or retirement. /// - public partial IntPtr RenderFrame(Action drawFrame) - { + public partial IntPtr RenderFrame( + Action drawFrame, + Action? prepareFrame + ) { ArgumentNullException.ThrowIfNull(drawFrame); - if (m_isFrameActive) + ObjectDisposedException.ThrowIf(m_disposed, this); + if (m_isFrameOperationActive || m_isFrameActive) throw new InvalidOperationException("An ImGui frame cannot be reentered."); var now = m_frameTimer.Elapsed; var deltaSeconds = (float)(now - m_lastFrameTime).TotalSeconds; m_lastFrameTime = now; - SynchronizePendingLiveResizeWindows(); - BeginFrame(deltaSeconds); + m_isFrameOperationActive = true; try { + SynchronizePendingLiveResizeWindows(); + BeginFrame(deltaSeconds, prepareFrame); drawFrame(); return EndFrame(); } - catch + catch (Exception failure) { - m_isFrameActive = false; + try + { + AbortNativeFrame(); + } + catch (Exception cleanupFailure) + { + throw new AggregateException("Presentation failed and its native frame could not retire.", failure, cleanupFailure); + } throw; } + finally + { + m_isFrameActive = false; + m_isFrameOperationActive = false; + } } - private void BeginFrame(float deltaTimeSeconds) + private void AbortNativeFrame() { + if (m_disposed || m_context->WithinFrameScope == 0) + return; + ImGuiNativeContext* previous = ImGuiNative.Raw.GetCurrentContext(); + ImGuiNative.Raw.SetCurrentContext(m_context); + try + { + ImGuiNative.Raw.EndFrame(); + } + finally + { + if (previous != m_context) + ImGuiNative.Raw.SetCurrentContext(previous); + } + } + + private void BeginFrame( + float deltaTimeSeconds, + Action? prepareFrame + ) { if (m_disposed) { throw new ObjectDisposedException(nameof(PlatformImGuiContext)); @@ -707,6 +759,7 @@ private void BeginFrame(float deltaTimeSeconds) UpdateTextInputState(io); m_viewports?.SynchronizeMetrics(); + prepareFrame?.Invoke(); m_isFrameActive = true; ImGuiNative.NewFrame(); @@ -800,8 +853,15 @@ ref SDLEvent sdlEvent } /// - /// Releases the resources owned by this instance. + /// Retires the context and its owned resources after the current frame operation has completed. /// + /// + /// Successful retirement removes this context's application registration and releases its borrowed font data. + /// Pending or failed retirement preserves both registration and ownership for a subsequent retry. + /// + /// + /// A frame operation still owns the context; the caller must retry after that operation completes. + /// public partial void Dispose() { if (m_disposed) @@ -809,6 +869,9 @@ public partial void Dispose() return; } + if (m_isFrameOperationActive) + throw new InvalidOperationException("A presentation context cannot retire during its frame operation."); + ImGuiNative.Raw.SetCurrentContext(m_context); var io = ImGuiNative.Raw.GetIO(); UnregisterClipboardCallbacks(); @@ -846,12 +909,17 @@ public partial void Dispose() m_renderer.Dispose(); ImGuiFont.UnregisterContext(m_context); ImGuiNative.Raw.DestroyContext(m_context); + GC.KeepAlive(m_supplementalFontData); + m_supplementalFontData = null; if (m_iniFilename != IntPtr.Zero) { Marshal.FreeCoTaskMem(m_iniFilename); m_iniFilename = IntPtr.Zero; } m_disposed = true; + Action? releaseRegistration = m_releaseRegistration; + m_releaseRegistration = null; + releaseRegistration?.Invoke(this); } private void UpdateDisplayMetrics(ImGuiIO* io) diff --git a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Rendering/PlatformImGuiSdlRenderer.cs b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Rendering/PlatformImGuiSdlRenderer.cs index e47a4a312..a6457d993 100644 --- a/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Rendering/PlatformImGuiSdlRenderer.cs +++ b/backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Rendering/PlatformImGuiSdlRenderer.cs @@ -34,6 +34,9 @@ internal PlatformImGuiSdlRenderer(Sdl3PlatformWindow window) /// public bool supportsViewports => false; + /// + public bool supportsVertexOffsets => true; + /// /// Records ImGui draw data for the main application viewport. /// diff --git a/build/cli/Inno.Build.Cli/BuildWorkspace.cs b/build/cli/Inno.Build.Cli/BuildWorkspace.cs index e9aeab4ba..121a0e315 100644 --- a/build/cli/Inno.Build.Cli/BuildWorkspace.cs +++ b/build/cli/Inno.Build.Cli/BuildWorkspace.cs @@ -214,7 +214,8 @@ .. PluginSourceService.GetActivatableMounts(scan) var compiler = new ScriptCompiler( new ScriptCompilerOptions { - projectRootDirectory = projectRoot + projectRootDirectory = projectRoot, + compilationEnvironment = new SdkScriptCompilationEnvironment(context.dotnetHost, projectRoot) }, assets, plugins); diff --git a/build/cli/Inno.Build.Cli/Commands/ShaderCommand.cs b/build/cli/Inno.Build.Cli/Commands/ShaderCommand.cs index 92d205e2c..f8b9b25d1 100644 --- a/build/cli/Inno.Build.Cli/Commands/ShaderCommand.cs +++ b/build/cli/Inno.Build.Cli/Commands/ShaderCommand.cs @@ -46,7 +46,9 @@ CancellationToken cancellationToken tool => product.files.Single(file => Path.GetFileName(file) == tool.ToString().ToLowerInvariant() + extension)); string includes = Path.GetDirectoryName(product.files.Single(file => Path.GetFileName(file) == "bgfx_shader.sh"))!; - ShaderArtifactBuilder.Compile(options.Require("assets"), options.Require("shader"), + ShaderBuildInputs inputs = ShaderBuildInputs.Capture(options.Require("assets"), cancellationToken); + using ProcessOwnedDirectory compilation = await StandardBuildCaches.CreateShaderWorkspaceAsync(root, cancellationToken).ConfigureAwait(false); + ShaderArtifactBuilder.Compile(inputs, compilation, options.Require("shader"), platform, renderer, options.Require("output"), new ToolRunner(executables, includes, context.host.services.workspaces), cancellationToken); return; diff --git a/build/cli/Inno.Build.Cli/Inno.Build.Cli.csproj b/build/cli/Inno.Build.Cli/Inno.Build.Cli.csproj index edafd08c6..3bf9a40b8 100644 --- a/build/cli/Inno.Build.Cli/Inno.Build.Cli.csproj +++ b/build/cli/Inno.Build.Cli/Inno.Build.Cli.csproj @@ -45,8 +45,11 @@ - - + + + + + diff --git a/build/distributions/Inno.Build.Distribution.Standard/Inno.Build.Distribution.Standard.csproj b/build/distributions/Inno.Build.Distribution.Standard/Inno.Build.Distribution.Standard.csproj index 0914565b1..0e3e93b12 100644 --- a/build/distributions/Inno.Build.Distribution.Standard/Inno.Build.Distribution.Standard.csproj +++ b/build/distributions/Inno.Build.Distribution.Standard/Inno.Build.Distribution.Standard.csproj @@ -11,6 +11,7 @@ + diff --git a/build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs b/build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs index 9609ae153..4072dc246 100644 --- a/build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs +++ b/build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Inno.Core.IO; using Inno.Build.Toolchains; namespace Inno.Build.Distribution.Standard; @@ -11,6 +14,40 @@ namespace Inno.Build.Distribution.Standard; /// public static class StandardBuildCaches { + /// + /// Creates a shader compilation workspace in the registered process-owned build cache. + /// + /// + /// The checkout owning cache maintenance and the source-independent compiler request. + /// + /// + /// Cancels registration or ownership acquisition before compilation begins. + /// + /// + /// A fresh directory lease; the caller must retain it until compilation finishes and dispose it afterward. + /// Remaining claim metadata is retired by the same cache maintenance service. + /// + /// + /// The cache boundary or operation ownership cannot be established. + /// + /// + /// The request was canceled before workspace ownership was returned. + /// + /// + /// The checkout root is blank or relative. + /// + public static ValueTask CreateShaderWorkspaceAsync( + string engineRoot, + CancellationToken cancellationToken = default + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(engineRoot); + if (!Path.IsPathFullyQualified(engineRoot)) + throw new ArgumentException("Shader workspaces require an absolute checkout root.", nameof(engineRoot)); + string root = Path.Combine(engineRoot, "artifacts", "build-tools", "shaders", "requests"); + BuildCacheOwnerCatalog.Register(engineRoot, root, "shader-requests", BuildCacheArtifactRole.ProcessWorkingDirectory, cancellationToken); + return ProcessOwnedDirectory.CreateAsync(root, cancellationToken); + } + /// /// Registers exact Native, binding and Support Pack ownership recorded by their publishers. /// Unrecorded directories remain outside the cleanup authority. @@ -40,6 +77,7 @@ IReadOnlyList components Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "native"), BuildCacheArtifactRole.ImmutableArtifact), "native"), Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "bindings"), BuildCacheArtifactRole.ImmutableArtifact), "bindings"), Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "support-packs"), BuildCacheArtifactRole.ImmutableArtifact), "support-packs"), + Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "build-tools", "shaders", "requests"), BuildCacheArtifactRole.ProcessWorkingDirectory), "shader-requests"), Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "managed", "workspaces"), BuildCacheArtifactRole.ReusableWorkspace), "managed-workspaces"), Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "build-tools", "managed-inputs", "evaluators"), BuildCacheArtifactRole.ImmutableArtifact), "managed-evaluators"), Describe(new BuildCachePublisher(Path.Combine(engineRoot, "artifacts", "build-tools", "managed-inputs", "requests"), BuildCacheArtifactRole.ProcessWorkingDirectory), "managed-input-requests"), diff --git a/build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs b/build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs index edfb58209..e818b09d1 100644 --- a/build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs +++ b/build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using Inno.Build.Composition; using Inno.Build.Toolchains; @@ -20,7 +21,8 @@ public static class StandardBuildEnvironment /// The native target explicitly selected for executable build tools, independently of exported games. /// /// - /// An explicit managed SDK executable, or null to use the current tool process's selected host. + /// An explicit SDK executable, or null to use DOTNET_HOST_PATH, the loaded shared runtime's + /// muxer, then normal executable discovery when this process has no shared runtime installation. /// /// /// A frozen execution context; publication targets must be selected independently. @@ -36,8 +38,24 @@ public static BuildCompositionContext Capture( BuildTargetId toolsTarget, string? dotnetHost = null ) { - string host = dotnetHost ?? Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet"; + BuildHostDescriptor buildHost = StandardBuildHosts.Capture(); + string host = dotnetHost ?? Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") + ?? CurrentRuntimeHost("dotnet" + buildHost.services.executableExtension); host = ToolchainEnvironment.ResolveExecutable(host); - return new BuildCompositionContext(host, applicationDirectory, StandardBuildHosts.Capture(), toolsTarget); + return new BuildCompositionContext(host, applicationDirectory, buildHost, toolsTarget); + } + + private static string CurrentRuntimeHost(string executable) + { + // An IDE can launch an apphost directly without setting DOTNET_HOST_PATH. + // Resolve the muxer beside the runtime that actually loaded this process before consulting PATH. + var runtime = new DirectoryInfo(RuntimeEnvironment.GetRuntimeDirectory()); + if (runtime.Parent?.Name == "Microsoft.NETCore.App" && runtime.Parent.Parent is { Name: "shared", Parent: { } installation }) + { + string runtimeHost = Path.Combine(installation.FullName, executable); + if (File.Exists(runtimeHost)) + return runtimeHost; + } + return executable; } } diff --git a/build/msbuild/Inno.Build.Tasks.targets b/build/msbuild/Inno.Build.Tasks.targets index dc76b9ee9..1c74210e4 100644 --- a/build/msbuild/Inno.Build.Tasks.targets +++ b/build/msbuild/Inno.Build.Tasks.targets @@ -131,11 +131,14 @@ Condition="'$(DesignTimeBuild)' != 'true'"> <_InnoFoundationExcludedProperties>$([System.String]::Copy('$(InnoTaskExcludedProperties)').Replace(';InnoTaskBootstrapOwner', '')) + <_InnoFoundationArtifacts Condition="'$(ArtifactsPath)' != ''">$(ArtifactsPath)/.inno-task-foundation + <_InnoFoundationArtifacts Condition="'$(ArtifactsPath)' == ''">$(InnoTaskArtifacts)/foundation + diff --git a/build/pipeline/Inno.Build/Inno.Build.csproj b/build/pipeline/Inno.Build/Inno.Build.csproj index 3bf32a643..d3fd52fa3 100644 --- a/build/pipeline/Inno.Build/Inno.Build.csproj +++ b/build/pipeline/Inno.Build/Inno.Build.csproj @@ -9,6 +9,7 @@ + - + diff --git a/build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs b/build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs new file mode 100644 index 000000000..f4dafb011 --- /dev/null +++ b/build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Inno.Build.Toolchains; +using Inno.Core.IO; +using Inno.Scripting.Compiler; + +namespace Inno.Build; + +/// +/// Evaluates the authoring project's selected SDK and maps its framework contract into scripting inputs. +/// +public sealed class SdkScriptCompilationEnvironment : IScriptCompilationEnvironmentProvider +{ + private readonly string m_dotnetHost; + private readonly string m_projectRoot; + + /// + /// Creates a host-owned input provider without selecting installed reference packs independently. + /// + /// + /// The .NET host executable selected by the composition root. + /// + /// + /// The authoring project whose ancestor SDK configuration governs compilation and IDE use. + /// + /// + /// A host or project location is empty. + /// + public SdkScriptCompilationEnvironment( + string dotnetHost, + string projectRoot + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(dotnetHost); + ArgumentException.ThrowIfNullOrWhiteSpace(projectRoot); + m_dotnetHost = dotnetHost; + m_projectRoot = Path.GetFullPath(projectRoot); + } + + /// + public ScriptCompilationInputs Capture(CancellationToken cancellationToken = default) + => CaptureAsync(cancellationToken).GetAwaiter().GetResult(); + + private async Task CaptureAsync(CancellationToken cancellationToken) + { + DotNetSdkDescriptor sdk = await DotNetSdkResolver.ResolveDirectoryAsync(m_dotnetHost, m_projectRoot, cancellationToken).ConfigureAwait(false); + string requests = Path.Combine(m_projectRoot, "Library", "Scripting", "SdkInputs"); + using ProcessOwnedDirectory owner = await ProcessOwnedDirectory.CreateAsync(requests, cancellationToken).ConfigureAwait(false); + string project = Path.Combine(owner.directory, "Framework.csproj"); + await File.WriteAllTextAsync(project, """ + + + false + false + + + + net10.0 + 14.0 + disable + false + + + + + + + + """, cancellationToken).ConfigureAwait(false); + _ = await RunSdkAsync(sdk, + [sdk.cliPath, "restore", project, "--disable-build-servers", "-m:1", "-nr:false", "-v:quiet"], + owner.directory, cancellationToken).ConfigureAwait(false); + string output = await RunSdkAsync(sdk, + [sdk.cliPath, "msbuild", project, "-nologo", "-m:1", "-nr:false", "-t:CaptureScriptFramework", + "-getItem:ReferencePath", "-getProperty:TargetFramework,LangVersion,DefineConstants,NETCoreSdkVersion"], + owner.directory, cancellationToken).ConfigureAwait(false); + using JsonDocument document = JsonDocument.Parse(output); + JsonElement properties = document.RootElement.GetProperty("Properties"); + if (properties.GetProperty("TargetFramework").GetString() != "net10.0" + || properties.GetProperty("LangVersion").GetString() != "14.0" + || properties.GetProperty("NETCoreSdkVersion").GetString() != sdk.sdkIdentity) + throw new InvalidDataException("SDK scripting evaluation returned an inconsistent framework, language or SDK identity."); + return new ScriptCompilationInputs(sdk.sdkIdentity, + document.RootElement.GetProperty("Items").GetProperty("ReferencePath").EnumerateArray() + .Select(static item => item.GetProperty("FullPath").GetString()!), + properties.GetProperty("DefineConstants").GetString()!.Split(';', StringSplitOptions.RemoveEmptyEntries)); + } + + private static async Task RunSdkAsync( + DotNetSdkDescriptor sdk, + IReadOnlyList arguments, + string directory, + CancellationToken cancellationToken + ) { + using var output = new StringWriter(); + using var errors = new StringWriter(); + try + { + await ToolchainEnvironment.RunAsync(sdk.hostPath, arguments, directory, cancellationToken, + DotNetSdkEnvironment.Create(sdk.hostPath), output, errors).ConfigureAwait(false); + return output.ToString(); + } + catch (InvalidOperationException failure) + { + throw new InvalidOperationException($"Script framework evaluation with SDK '{sdk.sdkIdentity}' failed.\n{output}{errors}", failure); + } + } +} diff --git a/build/tasks/Inno.Build.Tasks/CompileShaderTask.cs b/build/tasks/Inno.Build.Tasks/CompileShaderTask.cs index cd3476145..49a7eb7c6 100644 --- a/build/tasks/Inno.Build.Tasks/CompileShaderTask.cs +++ b/build/tasks/Inno.Build.Tasks/CompileShaderTask.cs @@ -197,12 +197,13 @@ CancellationToken cancellationToken throw new ArgumentException("A shader product must be published inside its owned Outputs directory.", nameof(OutputFile)); string directory = Path.GetDirectoryName(outputs)!; string runtime = Path.GetDirectoryName(typeof(CompileShaderTask).Assembly.Location)!; - NativeBuildInput[] inputs = [new("shader-source", Path.GetFullPath(AssetRoot)), - .. Directory.EnumerateFiles(runtime, "*.dll").Order(StringComparer.Ordinal) - .Select(path => new NativeBuildInput("compiler-runtime/" + Path.GetFileName(path), path))]; - string[] declarations = [compilerFingerprint, platform.id, platform.Resolve(renderer).key, Renderer, Configuration, ShaderPath, Path.GetFileName(output)]; using FileLease lease = FileLease.AcquireAsync(directory + ".lock", Timeout.InfiniteTimeSpan, cancellationToken) .GetAwaiter().GetResult(); + ShaderBuildInputs source = ShaderBuildInputs.Capture(AssetRoot, cancellationToken); + string[] declarations = [source.fingerprint, compilerFingerprint, platform.id, platform.Resolve(renderer).key, + Renderer, Configuration, ShaderPath, Path.GetFileName(output)]; + NativeBuildInput[] inputs = Directory.EnumerateFiles(runtime, "*.dll").Order(StringComparer.Ordinal) + .Select(path => new NativeBuildInput("compiler-runtime/" + Path.GetFileName(path), path)).ToArray(); string fingerprint = NativeBuildFingerprint.Create(declarations, inputs, cancellationToken); if (BuildArtifactManifest.IsComplete(directory, fingerprint, ["Outputs", "Metadata"])) { @@ -216,8 +217,13 @@ .. Directory.EnumerateFiles(runtime, "*.dll").Order(StringComparer.Ordinal) Directory.CreateDirectory(candidateOutputs); try { - ShaderArtifactBuilder.CompileGraphicsProgram(AssetRoot, ShaderPath, platform, renderer, - Path.Combine(candidateOutputs, Path.GetFileName(output)), tools, cancellationToken); + using (ProcessOwnedDirectory compilation = StandardBuildCaches.CreateShaderWorkspaceAsync(EngineRoot, cancellationToken) + .AsTask().GetAwaiter().GetResult()) + { + ShaderArtifactBuilder.CompileGraphicsProgram(source, compilation, ShaderPath, platform, renderer, + Path.Combine(candidateOutputs, Path.GetFileName(output)), tools, cancellationToken); + } + source.VerifyUnchanged(cancellationToken); if (NativeBuildFingerprint.Create(declarations, inputs, cancellationToken) != fingerprint) throw new InvalidDataException("Shader inputs changed during compilation; the candidate was not published."); BuildArtifactManifest.Write(candidate, fingerprint, ["Outputs", "Metadata"]); diff --git a/build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs b/build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs index 8b74687f6..0b77850be 100644 --- a/build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs +++ b/build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs @@ -32,19 +32,59 @@ public static class DotNetSdkResolver /// /// The selected host cannot resolve a valid SDK identity and absolute base path. /// - public static async ValueTask ResolveAsync( + public static ValueTask ResolveAsync( string hostPath, string projectPath, CancellationToken cancellationToken = default ) { ArgumentException.ThrowIfNullOrWhiteSpace(hostPath); ArgumentException.ThrowIfNullOrWhiteSpace(projectPath); - hostPath = ToolchainEnvironment.ResolveExecutable(hostPath); string project = Path.GetFullPath(projectPath); if (!File.Exists(project)) throw new FileNotFoundException("The managed entry project is absent.", project); + return ResolveDirectoryAsync(hostPath, Path.GetDirectoryName(project)!, cancellationToken); + } + + /// + /// Selects the SDK from an authoring owner's directory before any temporary evaluation project is created. + /// + /// + /// The managed host explicitly selected by the composition root. + /// + /// + /// The existing source owner whose ancestor global.json rules govern the operation. + /// + /// + /// Cancels discovery and drains the SDK process. + /// + /// + /// The selected SDK and fixed managed CLI path, without probing reference pack directories. + /// + /// + /// The source owner directory does not exist. + /// + /// + /// The host or selected SDK CLI is missing. + /// + /// + /// The host cannot resolve the source owner's requested SDK. + /// + /// + /// The caller cancels SDK discovery. + /// + public static async ValueTask ResolveDirectoryAsync( + string hostPath, + string ownerDirectory, + CancellationToken cancellationToken = default + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(hostPath); + ArgumentException.ThrowIfNullOrWhiteSpace(ownerDirectory); + string directory = Path.GetFullPath(ownerDirectory); + if (!Directory.Exists(directory)) + throw new DirectoryNotFoundException($"The managed SDK source owner is absent: '{directory}'."); + hostPath = ToolchainEnvironment.ResolveExecutable(hostPath); string information = await ToolchainEnvironment.CaptureOutputAsync(hostPath, ["--info"], - Path.GetDirectoryName(project)!, cancellationToken, + directory, cancellationToken, DotNetSdkEnvironment.Create(hostPath, new Dictionary { ["DOTNET_CLI_UI_LANGUAGE"] = "en-US" diff --git a/docs/README.md b/docs/README.md index fe01cd830..ef8e21069 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,3 +58,5 @@ [Inno.Testing.Source](testing/Inno.Testing.Source.md) 为需要 checkout 的测试读取显式 SDK 输入,源码只属于该测试支持程序集。 平台构建接入新增独立标准产品库、Windows 宿主及三个平台 Build integration;各项目的当前公开契约和签名映射见对应分类索引。 + +Editor UI 的程序集边界、当前文件树、手势、选择与基础能力复用见 [Editor UI 架构](architecture/EDITOR_UI_ARCHITECTURE.md),具体交互与实现约束见 [Editor UI 设计规范](architecture/EDITOR_UI_DESIGN_STANDARD.md)。[Editor 索引](editor/README.md) 包含 Assets.ImGui、Shaders.ImGui、Graph.ImGui 和 Workbench 的独立项目页。 diff --git a/docs/architecture/BUILD_CACHE_ARCHITECTURE.md b/docs/architecture/BUILD_CACHE_ARCHITECTURE.md index 63e82c9b6..fdd54f991 100644 --- a/docs/architecture/BUILD_CACHE_ARCHITECTURE.md +++ b/docs/architecture/BUILD_CACHE_ARCHITECTURE.md @@ -6,6 +6,8 @@ 构建工具冻结实际输入和工具选择,组件维护自己的 recipe,发布器验证并安装完整产物。缓存身份用于判断当前请求是否可以消费这些产物;工作目录用于保留兼容的编译中间态,两者有不同的生命周期。 +最初的 Foundation 引导是独立的普通 SDK 编译工作区:显式 `ArtifactsPath` owner 下的 `.inno-task-foundation`,或默认 Task artifacts 下的固定 `foundation`。其配置目录复用,不按 Build 次数增加;它不属于不可变 publication,也不参与根据后缀猜测删除。中立引导和调用方源码不能以不同 SDK 属性共享同一个编译输出。实际 publisher 与 runtime publication 仍使用既有 Core.IO owner、lease 和维护协议。 + 绑定 generation 同时携带不可变产物目录与实际发布 owner;selection 将两者一起传入后续 MSBuild。冻结源码树只改变组件源码位置,不改变产物的协调域。生成 Task 也输出同一 owner,materialization 不从源码工作区推测它,并继续校验产物必须属于声明 owner。 托管输入规划使用 `artifacts/managed/evaluations/` 下独立的 SDK `ArtifactsPath`,不改写普通 IDE 的 `obj`。还原、声明的输入工程还原、求值与内容读取都在同一 owner/叶锁范围内;不同全局属性的辅助工程拥有独立子布局,避免 RID 图相互覆盖。派生图可以重建,源输入变化仍在 fresh 校验中拒绝。 diff --git a/docs/architecture/CURRENT_ACCEPTANCE.md b/docs/architecture/CURRENT_ACCEPTANCE.md index 2f8409866..1c656ea83 100644 --- a/docs/architecture/CURRENT_ACCEPTANCE.md +++ b/docs/architecture/CURRENT_ACCEPTANCE.md @@ -2,8 +2,27 @@ [架构索引](README.md) · [当前问题](CURRENT_ISSUES.md) · [Wiki 首页](../README.md) +当前 Editor UI 的分层、Session、Selection、History、插件调用方及十一项评价见[完整计划验收](../../records/architecture/acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md)、[当前 UI 架构](EDITOR_UI_ARCHITECTURE.md)及[issue 闭合台账](../../records/architecture/EDITOR_UI_CLOSURE_PLAN.md)。生产实现 `dd5979c6` 的本地适用功能与验收已完成。最终交付 SHA、只含文档/证据的后续差异、精确提交 CI 与 PR Ready 状态以 [PR #26 验证附件](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338)为准;本地结果不代替该附件,也不复用历史提交的绿色结果。 + +| 当前 UI 范围 | 本地结果 | +| --- | --- | +| 源码、工具、入口和产品准备 | 11/11 阶段通过 | +| Contracts / Workflows / Visual / Lifecycle | 分别 822 / 704 / 524 / 15 次通过;Contracts 另有 2 次 Unix 专用测试在 Windows 未执行;各 profile 有重叠,不累加为独立测试数量 | +| Core Serialization / Scene / Core IO | 97/97、67/67、41/41 通过 | +| 工程、公开依赖、XML、Wiki | 241 工程求值;34,582 API inventory;197/197 实际编译示例通过 | +| Windows Editor | Debug/Release × D3D11/D3D12/Vulkan 六组通过,实际界面工作流及每类 Panel 100 次开关 | +| 原生输入与尺度 | 真实 Context、公开输入、56 个 Zoom/DPI/尺寸组合;模拟 metrics 与物理设备分开 | +| 生命周期 | 文档 100 次、十次连续 reload、严格代际回收和失败/恢复边界通过 | +| 受控性能 | 150 条成对记录;图尾部额外十进程复验;原始回退保留,无未解决的可重复门槛回退 | + +原始 producer 已恢复为可运行且输入可核对的对照;此前基线建立失败不再是当前阻塞。图尾部初始十帧采样出现回退,增加既有采样轮数后未重现持续 P95 回退;两批结果均在报告中,不把初次失败改写为通过。完整检查最差 P95 24.5695 ms,接近 25 ms 预算,不能声称性能余量无限。 + +真实物理显示器、OS IME、人工 Mac 和主观可用性未执行;真实 Mac 自动结果由精确提交 CI 附件提供。32 项完整解决的 issue 在分支验证后仍保持 Open,等待 PR 合并;更广文档 issue #18 仅部分相关。整个引擎其他开放问题见[当前问题](CURRENT_ISSUES.md),本轮 UI 完成不表示它们也已解决。 + 当前依赖通过标准 `.csproj` / `PackageReference` 和 SDK Restore 解析,源码不保存独立依赖锁清单;本轮范围与证据见[标准 NuGet 还原验收](../../records/architecture/acceptance/STANDARD_NUGET_RESTORE_ACCEPTANCE_2026_10_26.md)。基线保持 .NET SDK 10.0.402、C# 14、BGCS 4.1.2。 +新项目的默认 `editor.ini` 已完成本地专项验收,见[默认布局报告](../../records/architecture/acceptance/EDITOR_DEFAULT_LAYOUT_ACCEPTANCE_2026_10_29.md)。标准 Host 提供布局,Core 复用原子文档写入;已有文件不被默认值重置。该结果不替代完整 UI 原计划或最终提交的 CI。 + 此前 BGCS 消费与三项优先修复的独立证据见[BGCS 4.1.2 消费验收](../../records/architecture/acceptance/BGCS412_CONSUMER_ACCEPTANCE_2026_10_25.md)及[专项报告](../../records/architecture/acceptance/PRIORITY_FIX_ACCEPTANCE_2026_10_25.md)。历史通过数量不能当作当前源码已重跑。 以下表格保留此前 BGCS 消费验收的独立范围和数量;不与本轮标准还原检查重复累计,也不代表本轮重跑完整产品矩阵。 @@ -26,9 +45,9 @@ 本轮构建专项合计 74 项通过,0 失败 / 跳过,不重复计入此前 Coroutine / Runtime 的 142 项。本次发现的旧 package content 与新 SDK DLL 混装由 owned Restore / compilation 求值隔离修复;初始失败、哈希证明及最终复验见报告。 -## 尚未完成的矩阵与问题 +## 此前 BGCS 专项的矩阵与问题 -- macOS / Metal 无设备,未执行。 +- 该次 BGCS 专项没有本地 macOS / Metal 设备验收;后续 Editor 的 Mac 自动 CI 结果见上方最新复审,人工设备检查仍未执行。 - Windows Player 六条有界图形路径已通过;Editor Debug/Release、完整视觉、焦点、DPI、音频及长期运行矩阵未在本轮重跑。 - 示例产品已观察存储读取,尚未触发新的最高分写入,不能宣称分数跨启动存储已验收。 - 非致命 console 资源诊断仍待归因;自动维护预算、热构建成本及其他审计问题继续开放,均按报告链接进入 issue。 diff --git a/docs/architecture/CURRENT_ISSUES.md b/docs/architecture/CURRENT_ISSUES.md index 7a71ad5f9..c478b7e54 100644 --- a/docs/architecture/CURRENT_ISSUES.md +++ b/docs/architecture/CURRENT_ISSUES.md @@ -6,6 +6,16 @@ 最新全仓深审见[四仓库核心验收报告](../../records/architecture/acceptance/FULL_ENGINE_AUDIT_2026_10_24.md)。正式架构门禁通过不表示资源清理、错误原子性和热路径成本都已闭合。 +## Editor UI 计划的当前结论 + +[原计划复审](../../records/architecture/acceptance/EDITOR_UI_PLAN_REVIEW_2026_10_27.md)中的实现和本地验收缺口已在 `dd5979c6` 闭合,见[完整报告](../../records/architecture/acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md)和[真实 issue 台账](../../records/architecture/EDITOR_UI_CLOSURE_PLAN.md)。菜单、Graph、Console、Stats、可见工作控制、实际界面工作流、完整尺度与输入、100 次开关和十次 reload 均有候选源码证据,不能再把此前中间状态描述为当前实现缺失。 + +原产品 producer 和同输入五组对照已完成。完整 dirty inspection 通过十个独立进程的原预算;最差 P95 24.5695 ms,余量较小。图初始十帧尾部标记在预先声明的 50 帧/进程复验中未重现为持续回退;两批数据保留。2,000 节点图约 32 ms 的 P95、资源搜索约 73 ms 仍是值得继续改善的产品成本,不宣称所有场景稳定 60 FPS。 + +分支修复包括资产源身份/内容、Missing Scene 分配、CJK 字体、精确 Context 退休、Core 读取共享与原子创建、脚本缓存发布,以及真实 SDK 输入与包加载身份。修复均位于原职责 owner;没有 UI fallback、BGCS 补丁或第二份基础能力系统。#13 非 LIFO 拒绝、#16 实际 assets/load、#19 同 SDK references 的本地验收已通过。 + +精确交付 SHA、最终 Windows/macOS 自动 CI、英文 issue 验证和 PR Ready 状态统一见 [PR #26 验证附件](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338)。在该附件完成前不能从本地结果推断最终 CI 成功;issue 保持 Open,合并后关闭,#18 只部分关联。人工 Mac、真实显示器、OS IME、长期体验仍未执行。以下独立问题没有因此视为解决。 + ## 本轮已修复并验证 F02/F08 已在 Core Coroutine owner 内统一修复:嵌套枚举器逆序释放,完成/停止/失败共用退休流程;重入停止失效 handle 后在回调返回安全点清理,不再改坏活动遍历。Debug/Release 各 40 项回归通过,Runtime 62 项通过。异常明确汇总,未增加第二套调度器或事件系统。 @@ -25,10 +35,9 @@ B02 / F09 的当前处理是使用标准 `.csproj` 依赖还原,不再维护 | R02 | Runtime coroutine 接入 | 私有 scheduler 只有创建、Tick、stop 和 Dispose,当前没有 admission 路径;需落地最小真实契约或删除未使用状态,不能宣称已集成。 | | T01/T02 | Browser 验收覆盖 | 非致命 console 404 尚未归因;最高分跨启动写入没有在有界游戏运行中触发,已各自提交 issue。 | | B01 | Native 构建诊断 | Windows native 源码编码告警仍需按组件 owner 处理;B02 的依赖还原范围独立验收。 | -| F03 | History 拒绝操作的原子性 | 非 LIFO Commit 在校验 ID 前弹栈,public handler 反例确认内层修改无法随后回滚/Undo。不能用正常顺序用例通过关闭此项。 | | F04/F05 | 插件 UI / 2D 热路径 | UI 插件在纹理 GPU 命中前按 draw 完整复制像素;2D 模型的非空 viewContent 使完整帧缓存持续绕过。见全仓报告的分配实测和调用链,尚未修复。 | | F06/F07 | 热构建与缓存维护成本 | 暖 Native 准备反复读取约 4.5 GB;当前 5 秒 inventory 超时,30 秒 dry-run 可完成。内容/owner 正确性协议已存在,但没有以此证明日常维护必然收敛或性能足够。 | -| O01 | macOS / Metal | 历史 revision 的两条 CI 在单线程 Metal surface 退休用例超时,根因未确认。本轮没有 Mac,不能关闭或直接归责 BGCS。 | +| O01 | macOS / Metal | 历史 revision 的超时保留原记录;`2dc37114` 的 Mac 自动 CI 已通过。Metal refcount warning 仍存在且 master 也可见,根因未确认;人工 Mac 交互未执行,不能宣称无泄漏或归责 BGCS。 | | O02 | 完整构建性能 | 本轮有当前热构建采样及同 .NET 10 的路径解析实现对照,但没有完整改造前的空闲、同输入五次 Editor 基线。历史高负载耗时不作有效对照,不宣称整体提速或已排除全部回退。 | | O06 | XML / Wiki 语义 | 符号、签名和示例门禁不证明全部历史注释足够具体;部分历史参数说明仍需补充实际约束和失败语义。新增构建 API 的所有权、失败与生命周期单独说明。 | @@ -36,7 +45,7 @@ B02 / F09 的当前处理是使用标准 `.csproj` 依赖还原,不再维护 | 范围 | 验收边界 | | --- | --- | -| macOS ARM64 / Unix | 本轮无实机;Metal、Editor Debug/Release、示例产品、CoreCLR、NativeAOT 和不同卷格式未执行。历史 CI 与 fixture 仅保留原范围。 | +| macOS ARM64 / Unix | 本地没有实机;人工 Metal、不同物理显示器/卷格式和完整发布矩阵未执行。当前交付的托管、原生和产品自动 CI 结果见上方精确提交附件;历史 CI 与 fixture 不替代它。 | | Browser 产品 | 4.1.2 Native 与两种发布通过,解释执行和 AOT 各运行 3,000 帧。完整交互、游戏得分跨启动保存及 Browser 矩阵仍未闭合。 | | Rider 交互 | SDK 入口及 Task worker 回归不等于本轮操作过 Rider Run;没有单独的 Rider 生产分支。 | | GUI / 设备 | Play、昼夜切换、返回 Edit、窗口变化和退出按报告列出;完整 DPI/焦点、所有输入、音频听感及长期运行不计作通过。 | diff --git a/docs/architecture/EDITOR_UI_ARCHITECTURE.md b/docs/architecture/EDITOR_UI_ARCHITECTURE.md new file mode 100644 index 000000000..bad3b2d04 --- /dev/null +++ b/docs/architecture/EDITOR_UI_ARCHITECTURE.md @@ -0,0 +1,90 @@ +# Editor UI 架构与执行边界 + +[架构索引](ENGINE_ARCHITECTURE_OVERVIEW.md) · [Editor Wiki](../editor/README.md) + +## 当前目录 + +```text +src/composition/editor/ +├── contracts/Inno.Editor.Annotations/ # Stable discovery metadata +├── framework/ +│ ├── Inno.Editor.Core/ # Context, Module, Panel, state +│ ├── Inno.Editor.Interactions/ +│ │ ├── Actions/ # Availability and fresh dispatch +│ │ ├── Selection/ # Ordered Core Identity references +│ │ ├── Editing/ # One shared History gesture +│ │ ├── History/ # Neutral records and atomic transactions +│ │ ├── Documents/ # Save/Revert/Apply/Close ownership +│ │ ├── Menus/ # Action-derived menu models +│ │ └── Viewport/ # Generic interaction tools +│ ├── Inno.Editor.Inspection/ # Registries and serialized controls +│ ├── Inno.Editor.Graph/ # Graph document/state/history +│ └── Inno.Editor.Settings/ # Structured settings and Core Events +├── features/ +│ ├── Inno.Editor.Assets/ +│ │ ├── Editing/ # Reusable source mutations +│ │ ├── History/ # Source archives and neutral records +│ │ ├── Documents/ # Immediate draft history/recovery +│ │ └── Samples/ # Formal Sample import +│ ├── Inno.Editor.Scene/Documents/ # Granular SceneEdits and batch operations +│ ├── Inno.Editor.Shaders/Materials/ # Material drafts and source presentation +│ ├── Inno.Editor.Rendering/Pipelines/ # Pipeline source drafts +│ ├── Inno.Editor.Scripting/Runtime/ # Compilation and retirement; no UI +│ └── Inno.Editor.Exporting/Runtime/ # Export ownership; no UI +├── presentation/ +│ ├── Inno.Editor.ImGui/ +│ │ ├── Runtime/ # Session, frame and owner lifetime +│ │ │ ├── EditorUiSession.cs +│ │ │ └── EditorMenuRenderer.cs # Unified models to ImGui +│ │ ├── Controls/ # Pointer-free facade and borrows +│ │ ├── Widgets/ # Session-owned controls +│ │ └── Styling/ # Palette and density metrics +│ ├── Inno.Editor.Assets.ImGui/ # Browser state, icons, basic Inspector +│ ├── Inno.Editor.Shaders.ImGui/ # Material and node presentation +│ ├── Inno.Editor.Graph.ImGui/ # Generic canvas geometry and hit tests +│ └── Inno.Editor.Workbench/ # Global commands, settings, export UI +├── panels/ # Concrete views, no Panel references +└── hosting/Inno.Editor.Hosting/ # Runtime composition and lifetime +``` + +## 真正的依赖与执行链 + +领域操作和 Undo payload 不包含原生 UI 类型。Panel 读取当前 owner/context,调用 feature API;feature 先验证并执行修改,然后向同一个 History 发布中立记录。ImGui adapter 负责 native context、ID stack、绘制借用和 transient 状态。宿主负责初始化顺序与候选切换。 + +普通菜单、快捷键、工具栏与 command search 消费同一 Action 的 Query/Execute;presentation 不能维护另一份 enabled/checked 业务规则。Action 索引按 active generation 失效。所有跨帧目标用 Core Identity 重新解析,不能保留 Component wrapper 或 collectible Type。 + +静态菜单路径和结构在当前 generation 内冻结;快捷键按按键、规范化修饰键及 area 索引候选。缓存只保存声明事实,菜单状态和动态 source 仍按请求查询。动态菜单采用局部不可变结构合并,不把请求参数保存到静态模板。候选完成和 shutdown 明确清空索引;此处是内部执行机制,不增加项目作者配置。 + +非 Identity target 只有明确的中立标量可以强持有,其余对象使用弱引用,包括 boxed struct。宿主程序集的值类型仍可能包含插件对象,不能根据 `IsValueType` 或程序集不可回收就假定其内容中立。Selection、focus 与 snapshot 复用同一规则。 + +连续输入在首个有效 sample 打开 EditorEditGesture,release 提交,Escape 回滚。显式 Selection 切换先提交当前输入,准备失败则保持原选择;关闭 owner 或未完成的控件消失时回滚。命令和 reload 边界同样使用已有 Core Events 准备通知。材质/Pipeline 不再自己维护 deferred gesture;SceneEdits 和 asset draft service 都立即记录。 + +多选包含有序身份和 anchor;visible range 来自同一过滤/排序结果。批量源操作归一化最小 roots,先验证所有 owner,再由既有 History transaction 执行;失败不接受半批结果。共同属性使用 owner serialization context,保持各对象独立的 before 值。 + +## 复用与颗粒度 + +复用 Core Events、Identity、Serialization、AtomicFile 与现有 Graph/History;未新增平行事件总线、Undo 栈、identity registry 或依赖注入框架。Graph.ImGui 只提取实际共用的坐标、网格、曲线及命中机制;Shader 的 ports、参数与诊断仍由 Shader owner 解释。 + +ShaderEditor 是最大的领域专用 view,其 canvas、预览、菜单和文档协调按功能文件组织。该部分仍有降低复杂度的空间;不通过新增空 interface/project 或把全部方法变 public 来伪装解耦。架构评价和实际验收见本轮交付报告。 + +## 自动约束 + +EditorUiBoundaryValidator 校验 feature 无 UI/native/Panel 引用、Panel 无相互引用、native 访问集中在 adapter、Graph 无 Rendering/Shader 依赖。公开 API 依赖分组、XML、逻辑脚本导出、Solution 和 Wiki 继续由仓库统一验证器验证。 + +正式脚本 SDK 的 Build CLI 直接引用 Assets、Shaders、Graph 等导出 API 的实际 owner,不通过 FileBrowser、ShaderEditor、Hierarchy 或 Inspector Panel 间接携带编译 API。CLI 的这项引用边界同样受正反例约束。 + +Hierarchy 与 Inspector 自己公开的 drop target 协议仍由它们各自声明并被 SDK 直接引用;这些是明确的 UI 扩展契约,不是其他模块 API 的传递载体。校验器解析真实的 assembly export 声明,空清单或注释中的声明都不能充当 owner 证明。 + + +## 精确多选分量与 History 查询 + +属性编辑传递 root member、当前手势 key 和可选 scalar component。向量、整数向量及矩形控件明确传递被编辑的公共字段;Euler 控件声明具体轴。SceneEdits 使用当前公开值字段或 Core Quaternion 运算组合目标值,未读取私有字段,也不缓存插件 metadata。持久 History 继续保存既有 root property bytes,未增加第二种序列化格式。 + +History Query 只判断当前 owner/handler 是否可用。草稿的 expected bytes 在实际 Apply 时检查,避免同一事务较早 sample 因当前值已前进而使整个 Undo 永久禁用。Apply 冲突仍明确失败并保留栈位置。 + + +## 临时运行世界的 Identity 路由 + +启动时登记 Asset 与 Edit allocator;Play 的独立 allocator 由 Scene workspace 在准备候选前取得 `RegisterIdentityDomain` 路由 lease。Selection、focus、queued Action 与 native DragDrop 共同读取这一域路由表,实际对象始终由 Core Identity 解析。返回 Edit 并恢复选择成功后再释放 lease,失败保留可重试的 owner;不登记全局 allocator,也不保存第二张 live object 表。 + +`IEditorSelectionCoordinator` 是 Host 组合协议,承担临时 presentation 的域所有权,不再导出给项目脚本。脚本继续通过 `EditorInteractions` 使用选择和交互,不负责创建或登记 allocator。 diff --git a/docs/architecture/EDITOR_UI_DESIGN_STANDARD.md b/docs/architecture/EDITOR_UI_DESIGN_STANDARD.md new file mode 100644 index 000000000..d8ed91dc7 --- /dev/null +++ b/docs/architecture/EDITOR_UI_DESIGN_STANDARD.md @@ -0,0 +1,118 @@ +# Editor UI 设计与实现规范 + +[Wiki 首页](../README.md) · [Editor 架构](EDITOR_UI_ARCHITECTURE.md) · [Identity / Reference / Reload](IDENTITY_REFERENCE_RELOAD_STANDARD.md) + +本页定义当前 Editor 的交互和实现契约。测试结果及未完成项属于验收报告;本页不以规范代替验收证据。 + +## 首次打开项目 + +标准 Host 为缺少 `editor.ini` 的项目提供通用停靠布局,并在加载 presentation 前原子创建文档。布局资源由 Host 组合层拥有;Core 只处理传入的中立文档与现有状态协议。已有布局(包括刻意留空的文件)不能被默认值覆盖。默认资源不得带入示例场景、Asset ID、插件状态、Selection、History 或临时弹窗;后续保存复用统一文档生命周期。 + +## 职责与依赖 + +| Owner | 职责 | 禁止承担的职责 | +| --- | --- | --- | +| Core | Identity、Events、IO、序列化等基础机制 | 具体 Panel、ImGui 或领域编辑 | +| Editor Interactions | Action、菜单模型、选择、手势、History、文档协议 | Native 绘制、具体领域数据模型 | +| Feature | 原子领域修改、验证、恢复与 History payload | 停靠布局、Native 控件、另一个 Panel 的状态 | +| ImGui presentation | Session、作用域、控件、输入路由和通用样式 | 领域持久化、第二套事件总线或 Undo 栈 | +| Domain presentation | Browser、Shader 等领域视觉与组合 | 修改底层机制以维护领域类型白名单 | +| Panel | 组合已有 Feature 和 presentation | 独立资产数据库、编译流水线或通用状态管理器 | + +新增扩展先确定程序集、所有权和失败边界,再实现控件。每个项目采用项目级命名空间;Widgets 只在对应 `ImGuiWidget..cs` 中组织紧密相关的类型。公开 API 必须有实际外部消费者,内部缓存不得因测试方便而公开。 + +## 样式、尺寸与布局 + +样式分为基础语义值、通用组件值和领域呈现值。通用层表达文本、边框、焦点、禁用和内容强调,不认识 Shader 节点类别或 Asset 类型。Browser 布局属于 Assets presentation;Shader 端口和节点类别属于 Shader presentation。 + +Console、Hierarchy 和 Settings 的专属颜色与尺寸由各自 Panel 的内部 `Styling` 类型维护,Tag/Layer 颜色属于 Inspector 的内部呈现。公共 `EditorPalette` 与 `EditorStyleMetrics` 只保留基础值和可复用的卡片、标题、分区、表单等组件值;这些内部领域样式不进入脚本 API,也不保存 Session、Type 或扩展实例。 + +- DPI 表达显示设备密度,Zoom 表达用户的逻辑尺寸偏好;不能把同一尺寸重复缩放。 +- 后端先选择所属 Context 并更新显示 metrics,再执行 preparation callback,最后开始 Native frame。Session-owned Zoom/Density 在准备阶段统一生效;绘制中请求修改只影响后续帧。单独创建的 metrics 仍可作为无 Native 的即时值模型使用。 +- 表单先扣除操作列,再按标签与输入约 2:3 分配空间。长标签和窄 Inspector 使用裁剪、换行或 Tooltip,不能遮挡输入。 +- 同类按钮、搜索、树行、菜单、表格、卡片和 Modal 使用同一套通用 metrics。 +- 每个内容区只有一个主要滚动 owner。下拉框和菜单在当前 viewport 工作区内有界滚动。 +- 纯图标操作必须提供明确 Tooltip;错误、Missing、只读与禁用状态不能只依赖颜色。 +- 空内容说明下一步操作;忙碌状态说明当前工作;失败保留具体诊断和可执行的恢复路径。 + +尺度契约测试与物理显示器测试分别记录。模拟 framebuffer scale 不能代表已在所有真实 DPI 设备验收。 + +## Action、菜单、工具栏与快捷键 + +动作的唯一业务入口是现有 Action 协议。主菜单、上下文菜单、命令搜索、工具栏和快捷键查询相同的可用性、checked 状态和禁用原因。 + +带目标的 `EditorInteraction` 只用于当前同步请求,并在请求内强持有目标;不能把它缓存到后续帧或 generation。长期焦点与延迟路由另由既有 Interactions 使用 Core Identity 或弱引用管理。Hierarchy/Inspector 的公开 drop target 也是请求级借用,不属于 History payload 或持久状态。 + +静态声明的 Action ID、area、路径、顺序和快捷键按 extension generation 建索引。Selection、Play、Missing、只读状态及动态菜单参数必须来自当前请求,不能进入 generation 长寿命缓存。候选完整成功后原子替换;失败保留 last-good。 + +菜单路径表达用户任务,显示名称不承担持久身份。排序稳定、分组清晰,没有空组、重复动作或多余分隔线。上下文菜单的目标必须与当前选中或右击的记录一致;目标消失时关闭菜单,不能继续操作旧对象。 + +文本框、Popup 和 Modal 占有输入时,不向底层列表派发编辑键。保存等明确工作流仍遵守现有输入策略,不能以每个 Panel 自行判断快捷键代替统一路由。 + +## 选择、焦点与键盘导航 + +焦点、Selection、激活和数据修改是四种不同操作。跨帧定位对象必须重新经过 Core Identity;列表缓存只保留中立显示数据和 ID。 + +| 输入 | 行为 | +| --- | --- | +| Up / Down | 在当前可见序列移动 | +| Home / End | 移到首尾 | +| PageUp / PageDown | 按当前可见页移动 | +| Shift | 从 anchor 扩展范围 | +| 平台主修饰键 | 遵守产品注入的键盘策略 | +| Enter | 激活当前条目 | +| Escape | 退出搜索或取消当前交互 | + +过滤、排序、展开、鼠标范围选择与键盘范围选择必须使用同一序列。隐藏项不能被误选,裁剪不能丢失重命名、拖动、anchor 或焦点。安装 Plugin 内容保持只读,不能因为换了一种输入方式而绕过领域权限。 + +File Browser 的树、列表与网格根据当前键盘焦点选择重命名所在区域;不能要求先点击条目,也不能把输入框放到上次使用的另一种呈现中。它们复用同一个重命名 Action 与 History,领域修改保持不变。 + +Console 搜索对 message、source、file 及已有诊断文本执行不区分大小写的普通子串匹配,并与级别过滤组合。Collapse、详情、复制和选择必须对应相同的匹配 occurrences。Clear 清除派生选择及展开状态,不建立第二份日志存储。 + +## 编辑、History 与文档 + +可逆数据修改统一进入 `EditorInteractions.history`。Feature 先完成原子领域修改,再记录中立 payload。单次连续拖动产生一条逻辑 Undo;两次独立拖动产生两条。布尔、创建、删除和排序不能使用连续值 mergeKey 合并。 + +Escape 回滚当前手势;命令、保存、Play、reload 和关闭文档先完成或取消相应手势,再进入自己的操作。批量中途失败必须恢复所有已修改内容。回滚失败保留 payload、owner 和诊断,不能提前释放后宣称成功。 + +Missing 形成可恢复 barrier,不移动 Undo/Redo 栈,不丢原始 bytes;恢复后原操作重新可用。保存、打开、纯选择不属于数据 Undo。文档 dirty、保存、放弃、关闭与恢复通过统一文档协议完成;不能用 Panel 局部标志隐藏尚未发布的领域修改。 + +## Graph 与大视图 + +通用 Graph 文档不依赖 Rendering 或 ImGui。Graph ImGui 只负责节点几何、命中、框选、移动预览和连接意图;Shader owner 解释端口、节点、参数、编译与诊断。呈现层不能直接代替领域 controller 修改文档。 + +几何缓存只保存 ID 和中立值,其有效性由文档、revision、generation 和尺度决定。平移通过坐标变换处理;局部节点变化只失效相关几何。自定义内容回调只属于当前绘制作用域,不进入长期缓存。 + +固定行高列表按可见行提交;树按 revision、展开及过滤建立扁平可见序列。Console 对可变高度内容缓存测量结果,宽度、尺度、内容和展开变化时失效。原生子窗口及上下文菜单复用有限的呈现槽位,不能用无限增长的记录 ID 创建历史窗口。 + +优化先定义负载和采样方法。提交项数量、分配、CPU、GC、frame median/P95 与功能结果一起验收;不能只展示 FPS,不能用降低正确性换取指标。 + +## 生命周期与异常 + +每个交互 Host 拥有一个 `EditorUiSession`。`current` 是同步作用域访问器;状态仍属于具体 Session 和 presentation owner。Native 借用不能越过 frame、window、context 或 owner thread。 + +Begin/End、Push/Pop、Child、Table、Popup、Clip 和样式遵守各自真实配对规则,并用局部 `try/finally` 释放本次所有权。扩展异常保留原始诊断,后续合法帧必须能够继续绘制。不能清空其他 owner 的状态来恢复当前 Panel。 + +循环行的 ID 生命周期必须覆盖异常、提前退出和普通完成;移除条目后的 break 也通过同一 finally 释放,不在分支中手工重复 PopId。架构源码门禁检查这一已确认的回归模式,并检查重复呈现别名及失效 XML 说明;真实绘制、失效借用和跨 owner 异常仍由各自 Native 契约验收。 + +Presentation 的准备、绘制与发布属于同一次 owner 操作;期间拒绝重入和退休。准备失败不创建 Native frame;绘制失败只结束所属 Context 的失败帧,不发布部分 draw data。调用方仍须平衡自己打开的窗口和栈,后端不伪造修复其他 owner 的作用域。 + +关闭 Panel 退休其暂态控件;关闭文档退休文档状态。generation 切换不能保留旧 Type、delegate、扩展实例或 collectible 引用。旧 ALC 退休严格执行既有 GC/finalizer/weak-monitor barrier;timeout 进入 Faulted,不能清空 monitor 继续。 + +`editor.ini` 保存可读布局及中立项目导航,不保存 Selection、Undo、runtime 引用或编译中间态。正常退出先捕获 Module/Panel,再捕获最新布局,最后原子写入。 + +## 交付与验收 + +变更必须同步源码、调用方、项目引用、公开 XML、Wiki 和适用测试。使用真实公开契约与可替换边界,不使用 IVT、私有反射或测试后门。 + +`tests/Invoke-EditorUiAcceptance.ps1` 编排既有 xUnit/VSTest,按 Contracts、Workflows、Visual、Lifecycle、Performance、Products 分组。每次保留独立 SDK、源码身份、TRX 和结果摘要;空发现不能视为成功。Products 必须显式提供平台产品的 `-GraphicsApis` 和启动日志期望 `-BackendPattern`;共享入口不内嵌 backend 名称作为默认断言。`-ArtifactsPath` 与 `-ResultsDirectory` 指定输出 owner;需要把大量临时文件放到其他卷时,使用 `-TemporaryDirectory` 明确指定较短的临时根。默认保留操作系统的 TEMP/TMP,不把它们隐式嵌套到深层结果目录。该参数同样传递给隔离测试入口,普通测试与独立进程使用同一临时 owner。 + +Settings 的一次 Apply 复用单一 History transaction,所有 dirty owner 成功后才接受新基线。失败先尝试回滚并保留草稿;可恢复的文件/校验错误在窗口内反馈并记录完整诊断。History 完整性丢失后禁止继续 Apply,不能把错误吞掉后重试。打开的设置窗口借用当前 generation 的编辑对象,使用现有 admission guard 延迟 reload,关闭后恢复许可;不为此建立另一套状态恢复或 History 系统。 + +验收编排从正式 Editor 入口取得实际 Native target、tool target 和产品属性,不全局覆盖 `InnoTestNativeProduct`。需要原生部署的测试工程声明自己的产品及目标;纯契约测试不会因传递引用包含 Native 程序集而自动承担完整产品准备。普通执行、隔离发现和隔离执行使用相同的 SDK 输入。 + +原生借用验证与纯值转换必须按实际契约区分:`EditorDrawList` 每次操作验证原窗口、帧、Context 和线程;颜色转换保留既有 Session/Context/线程准入。颜色格式的饱和、取整及非有限值行为通过实际原生 oracle 验证,不用普通数值相等推断所有边界一致。 + +`EditorUiInput` 的 Control/Super 表示物理修饰键,不继承原生控件内部的快捷键归一化。呈现边界依据当前 Context 的键盘配置还原这些值,列表选择复用同一快照语义,平台仍通过 `EditorKeyboardPolicy` 选择 primary modifier。原生文本控件继续使用该 Context 的输入习惯;测试必须显式声明自己的键盘配置,并覆盖 Control/Super 与两种原生模式的组合,不能依赖 runner 的默认值。 + +最终报告分别评价架构、组织、状态与生命周期、抽象、复用、样式、扩展、交互、Undo/Redo、菜单和颗粒度;每项列出证据与限制。目录整齐、测试数量或正常启动不能代替视觉、输入、重复 reload、资源收敛和成对性能验收。 diff --git a/docs/architecture/ENGINE_ARCHITECTURE_OVERVIEW.md b/docs/architecture/ENGINE_ARCHITECTURE_OVERVIEW.md index 3094c06c7..83521eed3 100644 --- a/docs/architecture/ENGINE_ARCHITECTURE_OVERVIEW.md +++ b/docs/architecture/ENGINE_ARCHITECTURE_OVERVIEW.md @@ -182,7 +182,7 @@ Core Events 是唯一事件机制;跨代 live object 经 Identity 解析。Mis - `Inno.Editor.Hosting`:[`src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj`](../../src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj)。 - `Inno.Editor.Panel.FileBrowser`:[`src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj)。 - `Inno.Editor.Panel.GameView`:[`src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj)。 -- `Inno.Editor.Panel.Global`:[`src/composition/editor/panels/Inno.Editor.Panel.Global/Inno.Editor.Panel.Global.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.Global/Inno.Editor.Panel.Global.csproj)。 +- `Inno.Editor.Workbench`:[`src/composition/editor/presentation/Inno.Editor.Workbench/Inno.Editor.Workbench.csproj`](../../src/composition/editor/presentation/Inno.Editor.Workbench/Inno.Editor.Workbench.csproj)。 - `Inno.Editor.Panel.Hierarchy`:[`src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj)。 - `Inno.Editor.Panel.Inspector`:[`src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj)。 - `Inno.Editor.Panel.Logging`:[`src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj`](../../src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj)。 diff --git a/docs/architecture/IDENTITY_REFERENCE_RELOAD_STANDARD.md b/docs/architecture/IDENTITY_REFERENCE_RELOAD_STANDARD.md index 2f8a07077..4d2f60d3b 100644 --- a/docs/architecture/IDENTITY_REFERENCE_RELOAD_STANDARD.md +++ b/docs/architecture/IDENTITY_REFERENCE_RELOAD_STANDARD.md @@ -108,6 +108,8 @@ Editor 的 identity-aware API 必须从当前明确 scope 解析 allocator;不 ### 3.3 Registry 的唯一权威性 +资产文件索引的无内容变化刷新不能无条件注销、重建全部 runtime identity。`AssetFileSystem` 以完整候选对账当前 persistent entry ID,复用有效条目并退休真正移除的条目;普通枚举失败保留原索引。UI 继续通过 Core Identity 解析,不另建 path → live object 表,也不依赖每帧全量重建来维持正确性。 + - live object 的 `persistentId/runtimeId → instance` 解析以 `IdentityAllocator` 为唯一权威。 - 领域可以建立 `path/semantic ID → persistentId` 的二级索引,但不得再维护长期的 `path/semantic ID → collectible object` 权威表。 - 内部 dense/sparse array 可以优化 allocator;slot 必须带 generation,slot 复用后旧 runtime ID 必须失效。 @@ -695,6 +697,7 @@ resolved - Undo 创建/删除 Missing Component/System/Asset reference。 - redo branch、transaction compensation 和 disk-spilled payload 保持中立。 - Scene/Asset history 始终获得完整 Asset/reference Serialization Context。 +- 局部 Scene 属性与元素状态通过所属 Core Identity domain 按需解析当前引用;禁止为每次标量编辑重建整场景的 live object 双向索引。同步作用域固定 Scene owner,拒绝过期、错误 domain、跨 Scene 和类型不匹配的引用;异常与嵌套回调必须恢复原作用域。元素别名只保留经校验的中立 Guid 映射。完整图、Prefab、子树与 reload 候选的冻结局部 ID 映射仍属于各自的当前图事务,不承担当前属性的 live object authority。 - `Remove ScriptComponent → remove Plugin/Asset → Undo → recover → Redo` 全链通过。 ### 13.5 GC barrier @@ -761,3 +764,9 @@ Contributor snapshot、scratch、内容读取流与异步 clip 准备属于各 内容 store 的 reader generation 与代码 TypeCache/Assembly generation 具有不同语义:reader pin 只保护冻结内容,不成为 live object 第二索引;跨 generation live object 仍通过 Core.Identity 重新解析。Settings 与 Asset 引用使用 owner 的完整 SerializationContext。只读 Pack 与 Asset lease 不公开物理路径。 本轮职责迁移保留显式 Stable Type ID,并固定迁移前推导出的持久 DTO ID;内部 parser、临时键和生成 catalog 不进入持久协议。退出顺序和 GC barrier 继续按本页原规范执行,不能把缓存 reader 已释放当作 collectible ALC 已退休。 + +## 完整捕获 key 的瞬时边界 + +Core Serialization 的可选捕获 key 仅保存中立 runtime identity/domain、计数和当前 metadata/generation stamp,用于同一进程的不可变状态观察;这些值不写入编码结果、History、Scene、Workspace 或插件状态。跨 callback 的临时 identity 验证复用 Core Identity 的弱 registry 绑定,不创建对象回查表。Builder 在同步回调退出时清空 context、metadata、临时 identity 检查与存储;持有快照或已退休 builder 不得阻止真实 Assembly/ALC 退休。 + +Scene 在现有结构化对象数组中声明每个 owner 的完整写入 revision,以及精确 source/parent ID、sibling 位置和组件属性。结构、顺序、根属性、别名与 System 仍由原 writer 处理。未跟踪字段、自定义 Converter、可变引用、Missing 和 Prefab 等不完整证明仍执行权威 capture,不能以 UI 通知或局部 revision 跳过状态。Core 也禁止以父节点的局部 key 隐式证明嵌套 Converter 的状态,并从独立属性 payload 传播中立执行标记。变化期间输入不稳定或 identity 退休时拒绝发布新快照,原捕获保持有效。普通保存、Undo payload 与 Missing 恢复共用原二进制 writer/reader;捕获 key 不建立第二种数据格式或恢复协议。此处能力的完整失败和线程契约见 [Serialization](../core/Inno.Core.Serialization.md)。 diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 11a538845..de78eed1b 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -15,6 +15,8 @@ | [Web Player](WEB_PLAYER_ARCHITECTURE.md) | 浏览器启动、回调、链接与运行生命周期如何组合? | | [Identity 与热重载](IDENTITY_REFERENCE_RELOAD_STANDARD.md) | 如何跨代定位对象、保留 Missing、退休旧模块? | | [C# 开发规范](CSHARP_DEVELOPMENT_STANDARD.md) | 如何组织源码、公开契约、错误和注释? | +| [Editor UI 架构](EDITOR_UI_ARCHITECTURE.md) | Feature、Panel、presentation 和 Session 如何协作? | +| [Editor UI 设计规范](EDITOR_UI_DESIGN_STANDARD.md) | 样式、输入、选择、History、菜单和生命周期遵守什么契约? | | [当前问题](CURRENT_ISSUES.md) | 哪些能力或证据仍未收口? | | [当前验收](CURRENT_ACCEPTANCE.md) | 当前代码实际验证了什么,有哪些环境限制? | @@ -27,3 +29,5 @@ 日期化计划、阶段报告和旧审查保存于仓库 records 目录;当前 Wiki 不通过历史完成声明解释当前能力。 - [Native 构建平台职责与文件树](NATIVE_BUILD_PLATFORM_OWNERSHIP.md) + +- [Editor UI 架构与交互所有权](EDITOR_UI_ARCHITECTURE.md) diff --git a/docs/assets/Inno.Assets.Pipeline.md b/docs/assets/Inno.Assets.Pipeline.md index 29cd4e12d..dcb5ac64c 100644 --- a/docs/assets/Inno.Assets.Pipeline.md +++ b/docs/assets/Inno.Assets.Pipeline.md @@ -9,6 +9,8 @@ Catalog 文件提升在 journal 删除失败时先恢复旧 snapshot;外层 me ## Missing 与只读失败记录 +`AssetFileSystem.Refresh()` 先枚举和验证完整候选,再更新现有索引。persistent entry ID 未改变的源条目保留同一实例和 runtime identity;普通 Rescan 不会让当前 Browser 选择及派生视图失效。新增条目通过 Core Identity 注册,移除条目在候选准备成功后退休。枚举、权限、重复 identity 或注册失败保留上一次完整索引,不发布部分文件树;只读 Mount 和 source 过滤规则保持由各自 owner 管理。 + Asset reference 和 dependency record 只按 persistent ID 解析;last-known path 仅作诊断。旧路径出现新 identity 时原引用保持 Missing。 Tombstone 保留中立 property bytes、依赖描述与 last-successful artifact key;释放 live payload/强引用,不将旧 artifact 视为当前可加载内容。Tombstone 的旧 key 不在活动 path 集合中,因此仍允许正常 CAS 回收;保留 key 不等于永久保留缓存文件。 @@ -21,6 +23,10 @@ Tombstone 保留中立 property bytes、依赖描述与 last-successful artifact [Assets 索引](README.md) · [Runtime Assets](Inno.Assets.md) · [Plugins](../plugins/Inno.Plugins.Authoring.md) +## 原子发布与源发现 + +源过滤直接复用 Core IO 的 `AtomicFile.IsCandidatePath`,不把尚未发布的候选文件导入为资产。永久 `.imeta` 继续属于导入设置;构建源快照必须包含它,不能因为资产目录列表不展示 metadata 就从构建身份中排除。 + ## 职责与边界 该 authoring project 拥有 Source Mount、文件索引/watcher、Importer、Build Processor、`.imeta`、依赖图、Catalog、CAS Artifact、canonical authoring object 和 runtime closure export。Player 不引用它。 @@ -80,6 +86,8 @@ Authoring 启动与 Rescan 时,当前 source 的 `.imeta` 是“路径属于 运行时导出同时校验创作依赖:沿 Artifact 依赖递归检查当前导入状态和源指纹(包括 include/Source 输入)。Editor 可以继续使用 last-good,但失败、缺失或过期的必需创作输入不能认证 Player 构建。导出不在中途重导入,以免已编译的目标产物与新资产混用 generation;拒绝时报告完整依赖路径,需重导入后重新构建。 +可写创作源的路径 Load 和 Source 依赖重新验证实际内容,不把相同长度或修改时间当作内容未变的证明。验证使用标准库流式 SHA-256,避免只为检查有效性分配完整源文件字节数组。源被锁定或拒绝访问时走既有导入失败与 last-good 协议:保留可用产物、公开失败诊断,不能把它认证为当前成功输入;解除阻塞后再次 Load 可恢复。只读安装 mount 由已验证的不可变 generation 提供内容身份,仍可复用其有效 stamp。持久 ID、公开签名和源 sidecar 格式不变;这项完整性修复增加必要的源读取,不宣称为零成本优化。 + ### Source Mount 候选与共同 Recovery `PrepareSourceMounts` 返回未发布的 `AssetSourceMountTransaction`。`sourceMounts`、 @@ -235,208 +243,143 @@ static bool AdvanceSampleImport( 示例中的 `assets` 已由宿主初始化并持有,调用须在其 owner thread。Sample 工作流调用 `PrepareSampleImport` 后保留事务和初始为 false 的 `validationStarted`,逐帧调用示例;返回 true 后不再推进。失败或取消进入 Rollback,遇到退休未完成时继续持有事务与依赖,不能以 Dispose 抛异常为由提前释放 AssetPipeline。 -## 当前源码公开 API 清单 - -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Assets.Pipeline.AssetBuildContext`1` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetBuildContext.AssetBuildContext(TDefinition definition, System.Collections.Generic.IReadOnlyList inputs)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L25) | Creates a build context.
`definition`:The build definition.
`inputs`:The immutable input catalog snapshots. | — | — | -| [`public TDefinition Inno.Assets.Pipeline.AssetBuildContext.definition { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L36) | Gets the build definition. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetBuildContext.inputs { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L41) | Gets the input catalog snapshots. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetBuildContext where TDefinition : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L14) | Provides a stable input snapshot to an aggregate asset build. | — | — | - -### `Inno.Assets.Pipeline.AssetBuildProcessor` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Assets.Pipeline.AssetBuildProcessor.AssetBuildProcessor()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L37) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract System.Type Inno.Assets.Pipeline.AssetBuildProcessor.definitionType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L51) | Gets the definition type accepted by this processor. | — | — | -| [`public string Inno.Assets.Pipeline.AssetBuildProcessor.processorId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L44) | Gets the stable processor identifier used by build cache keys. | — | — | -| [`public abstract class Inno.Assets.Pipeline.AssetBuildProcessor`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L37) | Defines an automatically discovered aggregate asset build processor. | — | — | -### `Inno.Assets.Pipeline.AssetBuildProcessor`1` +## Artifact 与部署摘要补充契约 - - - - + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| 声明 | 行为与结果 | 所有权 / 线程 | 失败或限制 | | --- | --- | --- | --- | -| [`protected Inno.Assets.Pipeline.AssetBuildProcessor.AssetBuildProcessor()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L78) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetBuildProcessor.BuildAsync(Inno.Assets.Pipeline.AssetBuildContext context, Inno.Assets.Pipeline.AssetArtifactWriter output, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L101) | Builds immutable outputs from a consistent asset snapshot.
返回:An operation that completes when all outputs are staged.
`context`:The build context.
`output`:The candidate output writer.
`cancellationToken`:Cancellation for the build. | — | — | -| [`public override sealed System.Type Inno.Assets.Pipeline.AssetBuildProcessor.definitionType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L84) | Gets the concrete type handled by this extension implementation. | — | — | -| [`public abstract class Inno.Assets.Pipeline.AssetBuildProcessor where TDefinition : Inno.Assets.AssetObject : Inno.Assets.Pipeline.AssetBuildProcessor`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L78) | Provides a strongly typed aggregate asset build processor. | — | — | - -### `Inno.Assets.Pipeline.AssetBuildProcessorAttribute` - - - - +| [`public sealed class Inno.Assets.Pipeline.AssetArtifactWriter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | 建立本次 aggregate build 的输出/诊断收集器。 | 内部结果由 Pipeline 使用;公开 Writer 不提供物理路径。 | 输出不能重复写入。 | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetArtifactWriter.WriteAsync(string outputName, System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope = Inno.Assets.Pipeline.AssetDeploymentScope.Runtime)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L36) | 复制 bytes,暂存唯一的命名输出;返回已完成的 ValueTask。AuthoringOnly 输出不进入运行闭包。 | Writer 属于当前 aggregate build;调用后可释放/修改传入缓冲区。 | 空名称、非法 scope、将 runtime/asset-state 标为 AuthoringOnly、重复名称或预取消均失败。 | +| [`public void Inno.Assets.Pipeline.AssetArtifactWriter.ReportDiagnostic(string message)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L60) | 向本次构建加入诊断文本。 | 不开始导入、发布或文件 IO。 | 空白诊断会抛 ArgumentException。 | +| [`public Inno.Assets.Pipeline.AssetArtifactWriter.AssetArtifactWriter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | 建立本次 aggregate build 的输出/诊断收集器。 | 内部结果由 Pipeline 使用;公开 Writer 不提供物理路径。 | 输出不能重复写入。 | -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetBuildProcessorAttribute.AssetBuildProcessorAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L22) | Creates build-processor discovery metadata.
`id`:Globally stable build processor identifier. | — | — | -| [`public string Inno.Assets.Pipeline.AssetBuildProcessorAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L31) | Gets the globally stable build processor identifier. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetBuildProcessorAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L14) | Declares the immutable cache protocol identity of an automatically discovered asset build processor. | — | — | +## 当前源码公开 API 清单 -### `Inno.Assets.Pipeline.AssetCacheOptions` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Assets.Pipeline.AssetCacheOptions Inno.Assets.Pipeline.AssetCacheOptions.CreateDefault()`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L26) | Creates the default cache policy.
返回:The default cache options. | — | — | -| [`public System.TimeSpan Inno.Assets.Pipeline.AssetCacheOptions.garbageCollectionGracePeriod { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L18) | Gets the minimum age of an unreachable artifact before it can be collected. | — | — | -| [`public long Inno.Assets.Pipeline.AssetCacheOptions.maximumSizeBytes { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L13) | Gets the maximum artifact cache size in bytes, or zero for no size limit. | — | — | -| [`public readonly struct Inno.Assets.Pipeline.AssetCacheOptions`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L8) | Controls cleanup of rebuildable asset database data. | — | — | - -### `Inno.Assets.Pipeline.AssetCatalogCandidate` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Assets.Pipeline.AssetCatalogCandidate.Commit()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L48) | Publishes staged source metadata and atomically promotes the validated catalog, compensating metadata on failure. | — | `System.InvalidOperationException`:Thrown when the candidate has already been committed or disposed.
`System.IO.IOException`:Source metadata changed externally, a sidecar cannot be written, or the catalog cannot be promoted.
`System.UnauthorizedAccessException`:The operating system rejects a source sidecar or catalog write, including a directory at a file destination. | -| [`public void Inno.Assets.Pipeline.AssetCatalogCandidate.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L60) | Removes the candidate catalog staging storage without disposing the candidate loader. | — | — | -| [`public Inno.Assets.Pipeline.AssetLoader Inno.Assets.Pipeline.AssetCatalogCandidate.loader { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L34) | Gets the isolated loader whose catalog and in-memory records represent the candidate generation. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetCatalogCandidate : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L14) | Owns the isolated catalog storage used to validate one candidate Asset Pipeline generation. | The caller owns Inno.Assets.Pipeline.AssetCatalogCandidate.loader and must dispose it after the candidate has either been committed or discarded. Disposing this object removes only its rebuildable staging storage. | — | - -### `Inno.Assets.Pipeline.AssetChangedEvent` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetChangedEvent.AssetChangedEvent(string relativePath, System.IO.WatcherChangeTypes changeType, string oldRelativePath = "")`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L17) | Batched file-system change event for asset source files.
`relativePath`:Changed path relative to watched root.
`changeType`:File-system change type.
`oldRelativePath`:Old path for rename operations. | — | — | -| [`public System.IO.WatcherChangeTypes Inno.Assets.Pipeline.AssetChangedEvent.changeType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L30) | Underlying file-system change type. | — | — | -| [`public string Inno.Assets.Pipeline.AssetChangedEvent.oldRelativePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L36) | Old path relative to watched root for rename operations. Empty for non-rename changes. | — | — | -| [`public string Inno.Assets.Pipeline.AssetChangedEvent.relativePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L25) | Changed path relative to watched root. | — | — | -| [`public readonly struct Inno.Assets.Pipeline.AssetChangedEvent`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L17) | Batched file-system change event for asset source files.
`relativePath`:Changed path relative to watched root.
`changeType`:File-system change type.
`oldRelativePath`:Old path for rename operations. | — | — | - -### `Inno.Assets.Pipeline.AssetDeploymentScope` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Assets.Pipeline.AssetDeploymentScope.AuthoringOnly = 1`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L16) | The asset exists only for authoring workflows and is omitted from deployed catalogs. | — | — | -| [`Inno.Assets.Pipeline.AssetDeploymentScope.Runtime = 0`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L11) | The asset is deployed and must produce a named runtime artifact output. | — | — | -| [`public enum Inno.Assets.Pipeline.AssetDeploymentScope : int`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L6) | Defines whether an imported asset participates in deployed runtime content. | — | — | - -### `Inno.Assets.Pipeline.AssetExportContext` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.IAssetArtifactLookup Inno.Assets.Pipeline.AssetExportContext.artifacts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L46) | Gets the owner-bound immutable outputs used to reconstruct editable sources. | — | — | -| [`public Inno.Core.Serialization.SerializationRegistry Inno.Assets.Pipeline.AssetExportContext.serialization { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L35) | Gets the serialization registry bound to the active export generation. | — | — | -| [`public Inno.Assets.Pipeline.AssetSerializationServices Inno.Assets.Pipeline.AssetExportContext.services { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L40) | Gets the narrow structured serialization API bound to this export generation. | — | — | -| [`public Inno.Extensibility.Types.TypeCatalog Inno.Assets.Pipeline.AssetExportContext.types { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L29) | Gets the type catalog bound to the active export generation. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetExportContext`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L12) | Provides generation-bound services for one editable asset source export. | — | — | - -### `Inno.Assets.Pipeline.AssetFileEntry` - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetFileEntry.AssetFileEntry()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L11) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetFileEntry.assetPath { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L30) | Gets the isolated source path. | — | — | -| [`public string Inno.Assets.Pipeline.AssetFileEntry.extension { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L64) | Gets the normalized lower-case file extension. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isDirectory { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L60) | Gets whether the entry represents a directory. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isReadOnly { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L40) | Gets whether source mutations are forbidden. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isSample { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L45) | Gets whether this entry is the root of an installed Plugin sample awaiting Project import. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isSampleContent { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L50) | Gets whether this entry belongs to an installed Plugin sample subtree awaiting Project import. | — | — | -| [`public string Inno.Assets.Pipeline.AssetFileEntry.name { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L16) | Gets the final source path segment, or the semantic mount-root label when this entry is a source root. | — | — | -| [`public string Inno.Assets.Pipeline.AssetFileEntry.nameWithoutExtension { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L25) | Gets the final source path segment without its last extension. | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetFileEntry.parentAssetPath { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L55) | Gets the isolated parent directory path. | — | — | -| [`public Inno.Assets.AssetSourceId Inno.Assets.Pipeline.AssetFileEntry.source { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L35) | Gets the owning source mount identity. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetFileEntry : Inno.Core.Identity.IdentityObject`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L11) | One node in the asset source filesystem index. | — | — | - -### `Inno.Assets.Pipeline.AssetFileSystem` - - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetFileSystem.AssetFileSystem(System.Collections.Generic.IReadOnlyList mounts, bool autoStart = true, int flushDelayMs = 80, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null, bool requireWritableProject = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L96) | Creates an indexed file system over isolated source mounts.
`mounts`:Complete source mount snapshot.
`autoStart`:Whether writable project source watching starts immediately.
`flushDelayMs`:Watcher batch delay in milliseconds.
`sourcePolicy`:Source filtering policy, or null for defaults.
`requireWritableProject`:Whether the project mount must support authoring mutations. Runtime catalogs may set this to false when every mounted identity directory is read-only. | — | — | -| [`public Inno.Assets.Pipeline.AssetFileSystem.AssetFileSystem(string assetRoot, bool autoStart = true, int flushDelayMs = 80, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L59) | Creates an indexed source file system.
`assetRoot`:The absolute source root.
`autoStart`:Whether file watching should start immediately.
`flushDelayMs`:The watcher batch delay in milliseconds.
`sourcePolicy`:The source filtering policy, or null for defaults. | — | — | -| [`public void Inno.Assets.Pipeline.AssetFileSystem.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L366) | Releases the resources owned by this implementation. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileSystem.Exists(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L210) | Determines whether an indexed source entry exists.
返回:true when the entry exists.
`path`:The isolated source path. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.GetChildren(Inno.Assets.AssetPath parent)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L285) | Gets immediate children of an indexed directory.
返回:The immediate child entries.
`parent`:The isolated parent directory path. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.GetEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L261) | Gets a stable snapshot of indexed entries.
返回:The indexed entries.
`includeDirectories`:Whether directory entries should be included. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.PollChanges()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L308) | Polls normalized changes and refreshes the indexed source snapshot.
返回:The changes observed since the previous poll. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.PollChanges(out bool requiresFullRescan)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L319) | Polls changes and reports whether watcher recovery requires a full rescan.
返回:The changes observed since the previous poll.
`requiresFullRescan`:Whether the watcher reported an unreliable event stream. | — | — | -| [`public void Inno.Assets.Pipeline.AssetFileSystem.Refresh()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L188) | Rebuilds the indexed source file snapshot. | — | — | -| [`public void Inno.Assets.Pipeline.AssetFileSystem.Start()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L168) | Starts source file watching. | — | — | -| [`public void Inno.Assets.Pipeline.AssetFileSystem.Stop()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L177) | Stops source file watching. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileSystem.TryGetEntry(Inno.Assets.AssetPath path, out Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L232) | Tries to resolve an indexed source entry.
返回:true when the entry exists.
`path`:The isolated source path.
`entry`:The resolved entry when available. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L335) | Waits for a quiet watcher window, refreshes the index, and returns queued changes.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.WaitForIdle(out bool requiresFullRescan)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L346) | Waits for queued changes and reports whether a full rescan is required.
返回:The normalized queued changes.
`requiresFullRescan`:Whether the watcher reported an unreliable event stream. | — | — | -| [`public string Inno.Assets.Pipeline.AssetFileSystem.assetRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L37) | Gets the absolute source asset root. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetFileSystem.isWatching { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L42) | Gets whether source file watching is active. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetFileSystem : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L18) | Indexed asset source filesystem model backed by Inno.Assets.Pipeline.AssetWatcher. | — | — | - -### `Inno.Assets.Pipeline.AssetImportContext` - @@ -459,96 +402,27 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetImportContext.AcquireArtifact(System.Guid assetId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L106) | Acquires an immutable dependency output and records its invalidation dependency automatically.
返回:A caller-owned lease that must be disposed after reading.
`assetId`:Persistent dependency identity.
`outputName`:Stable named output protocol. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnArtifact(System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L301) | Declares an imported artifact that invalidates this imported asset.
`persistentId`:The persistent identity of the artifact owner. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnAsset(Inno.Assets.AssetDependency dependency)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L243) | Declares a direct runtime dependency by persistent descriptor.
`dependency`:The persistent dependency descriptor. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnAsset(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L230) | Declares a direct runtime dependency by isolated source path.
`path`:The isolated dependency path. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnCustomInput(string key, string fingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L320) | Declares a custom deterministic input that invalidates this asset.
`key`:The input identifier.
`fingerprint`:The current deterministic input fingerprint. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnSource(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L285) | Declares a source file that invalidates this imported asset.
`path`:The isolated dependency path. | — | — | -| [`public System.ReadOnlyMemory Inno.Assets.Pipeline.AssetImportContext.ReadSourceBytes(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L199) | Reads another source from the same candidate mount snapshot and records it as an import dependency.
返回:An immutable source snapshot owned by the import operation.
`path`:An isolated path whose mount dependency was declared when it crosses Plugin boundaries. | — | `System.InvalidOperationException`:Thrown when the source crosses an undeclared mount boundary or references the writable Project from a Plugin.
`System.IO.FileNotFoundException`:Thrown when the resolved source does not exist. | -| [`public string Inno.Assets.Pipeline.AssetImportContext.ReadSourceUtf8Text(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L216) | Reads another source as UTF-8 from the current candidate mount snapshot and records the dependency.
返回:Decoded UTF-8 text without an optional byte-order mark.
`path`:An isolated source path. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportContext.ReadUtf8Text()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L179) | Reads the source bytes as UTF-8 text.
返回:The decoded text without an optional byte-order mark. | — | — | -| [`public TAsset Inno.Assets.Pipeline.AssetImportContext.ResolveDependency(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L263) | Resolves and declares a strongly typed runtime asset dependency during import.
返回:The currently committed dependency asset.
`path`:Isolated dependency path. | — | `System.InvalidOperationException`:Thrown when the dependency cannot be imported or has another type. | -| [`public string Inno.Assets.Pipeline.AssetImportContext.absolutePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L122) | Gets the absolute source path. | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetImportContext.assetPath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L117) | Gets the isolated source path. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportContext.extension { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L171) | Gets the normalized lower-case source extension. | — | — | -| [`public Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImportContext.importSettings { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L166) | Gets the settings restored from this source's sidecar against the isolated import generation. The value is detached and changes made during import are not saved to the sidecar. | — | — | -| [`public System.Guid Inno.Assets.Pipeline.AssetImportContext.persistentId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L137) | Gets the persistent identity assigned to the source asset. | — | — | -| [`public Inno.Assets.IAssetReferenceResolver Inno.Assets.Pipeline.AssetImportContext.references { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L155) | Gets the asset-reference resolver bound to the isolated candidate generation. | — | — | -| [`public Inno.Core.Serialization.SerializationRegistry Inno.Assets.Pipeline.AssetImportContext.serialization { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L149) | Gets the serialization registry bound to the active import generation. | — | — | -| [`public Inno.Assets.Pipeline.AssetSerializationServices Inno.Assets.Pipeline.AssetImportContext.services { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L160) | Gets the narrow structured serialization API bound to this importer candidate. | — | — | -| [`public System.ReadOnlyMemory Inno.Assets.Pipeline.AssetImportContext.sourceBytes { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L127) | Gets the raw source bytes. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportContext.sourceHash { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L132) | Gets the deterministic source hash. | — | — | -| [`public Inno.Extensibility.Types.TypeCatalog Inno.Assets.Pipeline.AssetImportContext.types { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L143) | Gets the type catalog bound to the active import generation. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImportContext`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L16) | Collects source data and dependency declarations for one import operation. | — | — | - -### `Inno.Assets.Pipeline.AssetImporter` - - + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Assets.Pipeline.AssetImporter.AssetImporter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L38) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public virtual Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImporter.CreateImportSettings()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L71) | Creates a detached settings value for this importer in the current extension generation.
返回:A fresh serializable value with a registered stable type identity, or null when this importer has no settings. Defaults must be deterministic; implementations must not retain the returned instance. | — | — | -| [`public virtual Inno.Assets.Pipeline.AssetDeploymentScope Inno.Assets.Pipeline.AssetImporter.deploymentScope { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L52) | Gets whether imported assets are deployed or retained only for authoring workflows. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImporter.importerId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L45) | Gets the stable importer implementation identifier. | — | — | -| [`public abstract System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetImporter.supportedExtensions { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L62) | Gets the normalized source extensions accepted by this importer. | — | — | -| [`public abstract System.Type Inno.Assets.Pipeline.AssetImporter.targetAssetType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L57) | Gets the concrete asset type produced by this importer. | — | — | -| [`public abstract class Inno.Assets.Pipeline.AssetImporter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L38) | Defines metadata shared by automatically discovered asset importers. | — | — | - -### `Inno.Assets.Pipeline.AssetImporter`1` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Assets.Pipeline.AssetImporter.AssetImporter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L101) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected virtual System.Threading.Tasks.ValueTask?> Inno.Assets.Pipeline.AssetImporter.ExportAsync(Inno.Assets.Pipeline.AssetExportContext context, TAsset asset, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L144) | Exports an asset back into source bytes.
返回:The source bytes, or null when export is unsupported.
`context`:The generation-bound source export services.
`asset`:The asset to export.
`cancellationToken`:Cancellation for the export. | — | — | -| [`protected abstract System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetImporter.ImportAsync(Inno.Assets.Pipeline.AssetImportContext context, Inno.Assets.Pipeline.AssetImportWriter output, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L123) | Imports one source into a managed asset and named artifact outputs.
返回:An operation that completes when the candidate has been fully staged.
`context`:The import transaction context.
`output`:The candidate output writer.
`cancellationToken`:Cancellation for the import. | — | — | -| [`public override sealed System.Type Inno.Assets.Pipeline.AssetImporter.targetAssetType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L106) | Gets the runtime asset type accepted by this importer implementation. | — | — | -| [`public abstract class Inno.Assets.Pipeline.AssetImporter where TAsset : Inno.Assets.AssetObject : Inno.Assets.Pipeline.AssetImporter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L101) | Provides the strongly typed implementation base for an asset importer. | — | — | - -### `Inno.Assets.Pipeline.AssetImporterAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetImporterAttribute.AssetImporterAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L23) | Creates importer discovery metadata.
`id`:Globally stable importer protocol identifier. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImporterAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L32) | Gets the globally stable importer protocol identifier. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImporterAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L15) | Declares the immutable protocol identity of an automatically discovered asset importer. | — | — | - -### `Inno.Assets.Pipeline.AssetImportExtensionUnavailableException` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.AssetImportExtensionUnavailableException(string extensionKind, string extensionId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L22) | Describes a missing extension using generation-neutral identities.
`extensionKind`:The stable extension protocol identity.
`extensionId`:The required implementation's stable identity. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.extensionId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L42) | Gets the required implementation's stable identity. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.extensionKind { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L37) | Gets the stable extension protocol identity. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImportExtensionUnavailableException : System.InvalidOperationException`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L11) | Reports a required authoring extension absent from the current generation. | The host may defer this condition during discovery; it remains an import failure once discovery is complete. | — | - -### `Inno.Assets.Pipeline.AssetImportFailure` - + + @@ -560,73 +434,9 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetImportFailure.AssetImportFailure(string assetPath, string diagnostics)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Describes a writable-source import failure introduced after an earlier health snapshot.
`assetPath`:The isolated path of the source that failed to import.
`diagnostics`:The deterministic importer diagnostics associated with the failure. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportFailure.Deconstruct(out string assetPath, out string diagnostics)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Assets.Pipeline.AssetImportFailure.Equals(Inno.Assets.Pipeline.AssetImportFailure other)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Assets.Pipeline.AssetImportFailure.Equals(object obj)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Assets.Pipeline.AssetImportFailure.GetHashCode()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetImportFailure.operator ==(Inno.Assets.Pipeline.AssetImportFailure left, Inno.Assets.Pipeline.AssetImportFailure right)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetImportFailure.operator !=(Inno.Assets.Pipeline.AssetImportFailure left, Inno.Assets.Pipeline.AssetImportFailure right)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Assets.Pipeline.AssetImportFailure.ToString()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportFailure.assetPath { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L45) | The isolated path of the source that failed to import. | — | — | -| [`public string Inno.Assets.Pipeline.AssetImportFailure.diagnostics { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L46) | The deterministic importer diagnostics associated with the failure. | — | — | -| [`public readonly record struct Inno.Assets.Pipeline.AssetImportFailure : System.IEquatable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Describes a writable-source import failure introduced after an earlier health snapshot.
`assetPath`:The isolated path of the source that failed to import.
`diagnostics`:The deterministic importer diagnostics associated with the failure. | — | — | - -### `Inno.Assets.Pipeline.AssetImportHealthSnapshot` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Assets.Pipeline.AssetImportHealthSnapshot Inno.Assets.Pipeline.AssetImportHealthSnapshot.empty { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L32) | Gets the immutable snapshot used when no Asset Pipeline generation is active. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImportHealthSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L14) | Represents an immutable observation of writable-source import failures at one point in time. | The snapshot deliberately keeps importer fingerprints private to the Asset Pipeline. Callers can compare snapshots without depending on catalog records, importer implementation names, or storage details. | — | - -### `Inno.Assets.Pipeline.AssetImportSettingsSnapshot` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Assets.Pipeline.AssetImportSettingsSnapshot.fingerprint { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L30) | Gets the source metadata fingerprint required when saving this copy. | — | — | -| [`public Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImportSettingsSnapshot.value { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L25) | Gets the editable settings copy, or null for an importer without settings. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImportSettingsSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L12) | Provides a detached settings value and an optimistic concurrency token for its source sidecar. | The value belongs to the current importer generation. Editors must retain neutral serialized state, not this instance, across extension reloads. Reading this snapshot never edits source metadata. | — | - -### `Inno.Assets.Pipeline.AssetImportWriter`1` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnArtifact(System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L132) | Declares an asset artifact input that invalidates this import.
`persistentId`:The stable persistent identity used for lookup. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnAsset(Inno.Assets.AssetDependency dependency)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L116) | Declares a runtime dependency by persistent descriptor.
`dependency`:The dependency consumed by depends on asset; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnAsset(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L108) | Declares a runtime dependency by isolated source path.
`path`:The normalized file-system path used by this operation. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnCustomInput(string key, string fingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L143) | Declares a custom deterministic import input.
`key`:The input identifier.
`fingerprint`:The deterministic input fingerprint. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnSource(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L124) | Declares a source input that invalidates this import.
`path`:The normalized file-system path used by this operation. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.ReportDiagnostic(string message)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L154) | Adds a non-fatal import diagnostic.
`message`:The diagnostic message. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.SetAsset(TAsset asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L58) | Assigns the managed asset produced by the importer.
`asset`:The imported asset. | — | — | -| [`public void Inno.Assets.Pipeline.AssetImportWriter.SetDeploymentScope(Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L43) | Selects the deployment scope for this imported asset. This permits one importer to produce runtime assets and editor-only assets from different source documents without changing their asset type.
`deploymentScope`:The scope of the complete imported asset, including its generated state. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetImportWriter.WriteArtifactAsync(string outputName, System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope = Inno.Assets.Pipeline.AssetDeploymentScope.Runtime)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L84) | Writes one immutable named artifact output.
返回:A completed operation after the output has been staged.
`outputName`:The stable output name.
`bytes`:The output content.
`cancellationToken`:Cancellation for the write operation.
`deploymentScope`:Whether this output is retained in runtime packages. Runtime payload and asset state cannot be authoring-only. | — | — | -| [`public TAsset? Inno.Assets.Pipeline.AssetImportWriter.asset { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L33) | Gets the candidate asset assigned by the importer. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetImportWriter where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L16) | Collects the complete candidate output of one source import. | — | — | - -### `Inno.Assets.Pipeline.AssetLoader` - @@ -670,215 +480,74 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Action? Inno.Assets.Pipeline.AssetLoader.AssetReloaded`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L308) | Occurs after a loaded canonical asset is updated in place. | — | — | -| [`public Inno.Assets.Pipeline.AssetLoader.AssetLoader(Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IReadOnlyList mounts, string libraryRoot, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null, bool runtimeArtifactsOnly = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L197) | Creates an asset loader over one project source and zero or more isolated sources.
`types`:The immutable type-generation owner used to discover importers and resolve asset types.
`serialization`:The serialization registry used for catalog, metadata, and asset state persistence.
`identities`:The identity allocator that owns every canonical asset loaded by this instance.
`logs`:The explicitly owned router receiving importer and catalog diagnostics.
`diagnostics`:The diagnostic hub that owns import, build, catalog, and reference reports.
`mounts`:Complete isolated source mount snapshot.
`libraryRoot`:Absolute rebuildable Library root.
`sourcePolicy`:Source filtering policy, or null for defaults.
`runtimeArtifactsOnly`:Whether to trust a deployed read-only catalog and skip every source reconciliation operation. | — | — | -| [`public Inno.Assets.Pipeline.AssetLoader.AssetLoader(Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, string assetRoot, string libraryRoot, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L145) | Creates an asset loader for one source and Library root pair.
`types`:The immutable type-generation owner used to discover importers and resolve asset types.
`serialization`:The serialization registry used for catalog, metadata, and asset state persistence.
`identities`:The identity allocator that owns every canonical asset loaded by this instance.
`logs`:The explicitly owned router receiving importer and catalog diagnostics.
`diagnostics`:The diagnostic hub that owns import, build, catalog, and reference reports.
`assetRoot`:The absolute source root.
`libraryRoot`:The absolute rebuildable Library root.
`sourcePolicy`:The source filtering policy, or null for defaults. | — | — | -| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetLoader.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L109) | Acquires an immutable output while excluding concurrent artifact collection.
返回:A lease retaining this exact artifact key, even after reimport or source deletion.
`persistentId`:The source asset's persistent identity.
`outputName`:The required output protocol. | — | `System.InvalidOperationException`:No current output can be resolved. | -| [`public void Inno.Assets.Pipeline.AssetLoader.ApplySourceChanges(System.Collections.Generic.IReadOnlyList changes)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceChanges.cs#L60) | Applies normalized source file changes to the persistent catalog.
`changes`:The normalized source changes. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.BuildAsync(Inno.Assets.AssetObject definition, System.Collections.Generic.IReadOnlyList inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Importing.cs#L55) | Builds and validates the requested artifact asynchronously before publishing it.
返回:The content-addressed output bundle key.
`definition`:The build definition asset.
`inputs`:The immutable input catalog snapshots.
`cancellationToken`:Cancellation for the candidate build. | — | — | -| [`public Inno.Assets.Pipeline.AssetImportHealthSnapshot Inno.Assets.Pipeline.AssetLoader.CaptureWritableImportHealth()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Diagnostics.cs#L35) | Captures the current failure identity of every writable source without exposing catalog internals.
返回:An immutable health snapshot suitable for validating a later candidate generation. | — | — | -| [`public int Inno.Assets.Pipeline.AssetLoader.CollectArtifacts(System.TimeSpan gracePeriod, long maximumSizeBytes)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L41) | Collects unreachable content-addressed artifacts.
返回:The number of removed artifact bundles.
`gracePeriod`:The minimum age of an unreachable bundle.
`maximumSizeBytes`:The cache size limit, or zero for no limit. | — | — | -| [`public void Inno.Assets.Pipeline.AssetLoader.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Retirement.cs#L38) | Stops new loads and releases canonical assets before their registries and diagnostics. | — | `Inno.Core.Execution.RetirementPendingException`:A load, serialized operation or unload hook is still active. Retain the loader and retry after quiescence.
`System.AggregateException`:Completed releases failed; all quiescent resources have still been attempted. | -| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetLoader.ExportRuntimeArtifacts(string destinationLibraryRoot, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.RuntimeExport.cs#L66) | Exports the validated runtime artifact closure while observing cooperative cancellation between files.
返回:Counts and source identities represented by the exported snapshot.
`destinationLibraryRoot`:The empty directory that receives the runtime-only asset database.
`cancellationToken`:The token checked before every artifact file is copied. | — | `System.InvalidOperationException`:Thrown when artifacts are incomplete or transitive source inputs no longer certify the imported snapshot.
`System.OperationCanceledException`:Thrown when cancellation is requested before the snapshot finishes. | -| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetLoader.ExportRuntimeArtifacts(string destinationLibraryRoot)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.RuntimeExport.cs#L45) | Writes a source-free runtime catalog and its exact immutable artifact closure.
返回:Counts and source identities for the exported runtime snapshot.
`destinationLibraryRoot`:Empty destination that becomes the deployed content root. | — | `System.InvalidOperationException`:Thrown when runtime artifacts are incomplete, a deployed reference is authoring-only, or a transitive authoring input has failed or become stale.
`System.IO.IOException`:Thrown when the destination is not empty or content cannot be copied. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.FindIntroducedImportFailures(Inno.Assets.Pipeline.AssetImportHealthSnapshot baseline)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Diagnostics.cs#L59) | Finds writable-source failures that are new or changed relative to an earlier health snapshot.
返回:A deterministic path-ordered collection containing only introduced or changed failures.
`baseline`:The health snapshot captured before the candidate generation was activated. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L41) | Gets direct or transitive runtime dependencies of an asset.
返回:The persistent dependency descriptors.
`asset`:The asset to query.
`recursive`:Whether transitive dependencies should be included. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetImportDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L61) | Gets source import dependencies that invalidate an asset artifact.
返回:Canonical isolated source paths in stable order.
`asset`:The asset to query.
`recursive`:Whether transitive source dependencies should be included. | — | — | -| [`public Inno.Assets.Pipeline.AssetImportSettingsSnapshot Inno.Assets.Pipeline.AssetLoader.GetImportSettings(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.ImportSettings.cs#L31) | Reads a detached importer settings value without changing source metadata.
返回:The current settings and the metadata fingerprint required for a subsequent save.
`path`:The isolated source path, not its sidecar path. | — | `System.NotSupportedException`:The source has no registered importer.
`System.IO.InvalidDataException`:The persisted settings cannot be restored by the current importer. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetLoadedPaths()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L209) | Gets isolated source paths of all canonical loaded assets.
返回:A stable isolated path snapshot. | — | — | -| [`public Inno.Assets.AssetReferenceInfo Inno.Assets.Pipeline.AssetLoader.GetReferenceInfo(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L86) | Gets an engine-known reference diagnostic snapshot.
返回:The reference diagnostic snapshot.
`asset`:The asset to inspect. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.Import(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Importing.cs#L38) | Imports one isolated source file into metadata and a runtime artifact.
返回:true when an importer handled the source.
`path`:The isolated source path. | — | — | -| [`public Inno.Assets.AssetObject? Inno.Assets.Pipeline.AssetLoader.Load(Inno.Assets.AssetPath path, System.Type requestedAssetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L41) | Loads a canonical asset by isolated source path.
返回:The canonical asset, or null when unavailable or incompatible.
`path`:The isolated source path.
`requestedAssetType`:The required assignable asset type. | — | — | -| [`public Inno.Assets.AssetObject? Inno.Assets.Pipeline.AssetLoader.Load(System.Guid persistentId, System.Type requestedAssetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L85) | Loads a canonical asset by persistent identity.
返回:The canonical asset, or null when unavailable or incompatible.
`persistentId`:The persistent asset identity.
`requestedAssetType`:The required assignable asset type. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.LoadAsync(Inno.Assets.AssetPath path, System.Type requestedAssetType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L132) | Asynchronously loads a canonical asset by isolated source path.
返回:The canonical asset, or null when unavailable or incompatible.
`path`:The isolated source path.
`requestedAssetType`:The required assignable asset type.
`cancellationToken`:Cancellation for the current caller's wait. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.LoadAsync(System.Guid persistentId, System.Type requestedAssetType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L172) | Asynchronously loads a canonical asset by persistent identity.
返回:The canonical asset, or null when unavailable or incompatible.
`persistentId`:The persistent asset identity.
`requestedAssetType`:The required assignable asset type.
`cancellationToken`:Cancellation for the current caller's wait. | — | — | -| [`public Inno.Assets.Pipeline.AssetCatalogCandidate Inno.Assets.Pipeline.AssetLoader.PrepareCatalogCandidate(System.Collections.Generic.IReadOnlyList mounts, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L47) | Creates an isolated source-mount and catalog candidate without changing the active catalog.
返回:A candidate that owns its catalog staging storage and exposes the isolated loader to its owner.
`mounts`:The complete source-mount snapshot to validate.
`sourcePolicy`:The source policy for the candidate, or null to reuse this loader's policy. | — | `System.ArgumentException`:Thrown when the mount snapshot is empty, duplicated, or has no writable project source.
`System.IO.IOException`:Thrown when the active catalog cannot be copied into candidate storage. | -| [`public bool Inno.Assets.Pipeline.AssetLoader.RefreshRegistries()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L344) | Refreshes extension registries and reimports affected sources when their snapshot changed.
返回:true when the importer registry changed. | — | — | -| [`public void Inno.Assets.Pipeline.AssetLoader.Rescan(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceChanges.cs#L38) | Reconciles source files, metadata, artifacts and the in-memory catalog.
`cancellationToken`:Cancellation for reconciliation and importer work; unpublished candidates can be discarded after it drains. | — | `System.OperationCanceledException`:Reconciliation or an importer observes cancellation. | -| [`public Inno.Assets.AssetObject Inno.Assets.Pipeline.AssetLoader.ResolveReference(System.Guid persistentId, System.Guid stableTypeId, string lastKnownPath, System.Type expectedType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L214) | Resolves a serialized reference or creates a persistent missing placeholder.
返回:A compatible canonical asset or missing placeholder.
`persistentId`:The referenced persistent identity.
`stableTypeId`:The referenced stable asset type identity.
`lastKnownPath`:The last known source-relative path.
`expectedType`:The declared destination type. | — | — | -| [`public void Inno.Assets.Pipeline.AssetLoader.RestoreProperties(System.Guid stableTypeId, byte[] propertyData, TValue target) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L101) | Restores serialized properties to the existing asset object.
`stableTypeId`:The stable type id consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`propertyData`:The property data consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`target`:The existing target that receives the validated result. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.Save(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Saving.cs#L38) | Saves an asset back to its current source path.
返回:true when an importer exported the asset.
`asset`:The asset to export. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Saving.cs#L58) | Saves an asset to its initial or existing isolated source path while preserving an existing destination identity.
返回:true when an importer exported the asset.
`path`:The isolated source path.
`asset`:The asset to export. Detached replacement values do not replace an existing source's persistent identity. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.SaveImportSettings(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable? settings, string expectedFingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.ImportSettings.cs#L68) | Atomically saves importer settings and reimports the source. Failed imports retain both the saved settings and the previous successful artifact; callers must inspect the returned import status separately from saving.
返回:True when reimport succeeds; false when settings were saved but reimport failed.
`path`:The writable isolated source path.
`settings`:A value of the current importer's settings type, or null to reset to defaults.
`expectedFingerprint`:The fingerprint returned when the settings were read. | — | `System.IO.IOException`:The sidecar changed externally or could not be written.
`System.InvalidOperationException`:The source is read-only or belongs to an isolated candidate.
`System.ArgumentException`:The settings have a different type from the current importer. | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L74) | Tries to resolve a named output from the current artifact bundle.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`persistentId`:The stable persistent identity used for lookup.
`outputName`:The stable artifact output name used for lookup.
`artifact`:The resolved immutable artifact payload returned to the caller. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetAssetType(Inno.Assets.AssetPath path, out System.Type? assetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L189) | Tries to resolve the concrete asset type without loading it.
返回:true when the type can be resolved.
`path`:The isolated source path.
`assetType`:The resolved type. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L114) | Tries to get a catalog snapshot by isolated source path.
返回:true when the source is cataloged.
`path`:The isolated source path.
`info`:The immutable catalog snapshot when found. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetInfo(System.Guid persistentId, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L140) | Tries to get a catalog snapshot by persistent identity.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`persistentId`:The stable persistent identity used for lookup.
`info`:The resolved immutable metadata returned to the caller. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetPersistentId(Inno.Assets.AssetPath path, out System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L163) | Tries to resolve a persistent identity without loading the asset.
返回:true when catalog metadata exists.
`path`:The isolated source path.
`persistentId`:The resolved identity. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryLoad(Inno.Assets.AssetPath path, System.Type requestedAssetType, out Inno.Assets.AssetObject? asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L64) | Tries to load a canonical asset by isolated source path.
返回:true when a compatible asset was loaded.
`path`:The isolated source path.
`requestedAssetType`:The required assignable asset type.
`asset`:The canonical asset when successful. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetLoader.TryLoad(System.Guid persistentId, System.Type requestedAssetType, out Inno.Assets.AssetObject? asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L108) | Tries to load a canonical asset by persistent identity.
返回:true when a compatible asset was loaded.
`persistentId`:The persistent asset identity.
`requestedAssetType`:The required assignable asset type.
`asset`:The canonical asset when successful. | — | — | -| [`public int Inno.Assets.Pipeline.AssetLoader.UnloadUnusedAssets()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L126) | Collects canonical assets that have no external managed references.
返回:The number of released canonical assets. | — | — | -| [`public void Inno.Assets.Pipeline.AssetLoader.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L336) | Waits for pending import and build work. | — | — | -| [`public string Inno.Assets.Pipeline.AssetLoader.artifactRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L303) | Gets the derived content-addressed artifact root. | — | — | -| [`public string Inno.Assets.Pipeline.AssetLoader.assetRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L293) | Gets the absolute source root. | — | — | -| [`public string Inno.Assets.Pipeline.AssetLoader.libraryRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L298) | Gets the absolute rebuildable Library root. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetLoader : System.IDisposable, Inno.Assets.IAssetReferenceResolver, Inno.Assets.IAssetArtifactLookup, Inno.Assets.IAssetPropertyStateResolver`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L27) | Coordinates importing, persistent cataloging, canonical loading, reloading and collection for one source and artifact root pair. | — | — | - -### `Inno.Assets.Pipeline.AssetPipeline` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.AssetReloaded`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L163) | Occurs after a canonical loaded asset has been updated in place. | — | — | -| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.Changed`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L158) | Occurs after an asset database transaction has committed. | — | — | -| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.SourceMountsChanged`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L169) | Occurs after a complete isolated source-mount generation is atomically replaced. | — | — | -| [`public Inno.Assets.Pipeline.AssetPipeline.AssetPipeline(Inno.Extensibility.Modules.ModuleHost modules, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, Inno.Assets.Pipeline.AssetPipelineOptions options)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L199) | Creates one isolated authoring or deployed-runtime asset pipeline.
`modules`:The module host whose candidate generations coordinate importer refreshes.
`types`:The type catalog used to discover and resolve asset extensions.
`serialization`:The serialization registry used for metadata and canonical asset state.
`identities`:The identity allocator that owns every canonical asset in this pipeline.
`logs`:The explicitly owned router receiving asset pipeline diagnostics.
`diagnostics`:The diagnostic hub that owns source database and asset processing reports.
`options`:The asset source, artifact and watcher configuration. | — | `System.ArgumentNullException`:Thrown when a required service is null. | -| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetPipeline.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L61) | Acquires one verified authoring artifact generation for an explicit lifetime.
返回:A lease over the currently committed immutable artifact generation.
`persistentId`:Persistent identity of the artifact owner.
`outputName`:Stable artifact output name. | — | `System.InvalidOperationException`:Thrown when the requested artifact is unavailable. | -| [`public System.Threading.Tasks.ValueTask> Inno.Assets.Pipeline.AssetPipeline.AcquireAsync(Inno.Assets.AssetPath path, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L225) | Asynchronously acquires a canonical authoring asset by path for an explicit managed lifetime.
返回:A lease whose strong reference participates in the existing reachability sweep.
`path`:Mount-qualified logical asset path.
`cancellationToken`:Cancellation for this caller's asynchronous wait. | — | — | -| [`public System.Threading.Tasks.ValueTask> Inno.Assets.Pipeline.AssetPipeline.AcquireAsync(System.Guid persistentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L250) | Asynchronously acquires a canonical authoring asset by persistent identity.
返回:A lease whose strong reference participates in the existing reachability sweep.
`persistentId`:Persistent asset identity.
`cancellationToken`:Cancellation for this caller's asynchronous wait. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.BuildAsync(Inno.Assets.AssetObject definition, System.Collections.Generic.IReadOnlyList inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L81) | Runs an aggregate asset build using the processor registered for a definition.
返回:The content-addressed output bundle key.
`definition`:The build definition asset.
`inputs`:The immutable input catalog snapshots.
`cancellationToken`:Cancellation for the candidate build. | — | — | -| [`public Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.AssetPipeline.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L116) | Captures native settings and nested asset dependencies through this explicit authoring owner.
返回:Complete stable properties and dependencies without writing or importing an asset.
`value`:Typed settings, never retained by the returned snapshot. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.CompleteExtensionDiscovery()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L31) | Ends initial authoring extension discovery and strictly retries dependent imports. | Call on the initialization thread after successful extension activation. Failed compilation must not call this method. | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.CreateDirectory(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L298) | Creates a tracked source directory and its persistent metadata.
`path`:New isolated directory path. | — | `System.IO.DirectoryNotFoundException`:Thrown when the parent directory does not exist.
`System.IO.IOException`:Thrown when the target already exists. | -| [`public Inno.Assets.Pipeline.AssetSourceStore Inno.Assets.Pipeline.AssetPipeline.CreateSourceStore()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L102) | Creates a detached source editor sharing this owner's mounts, native converters and references.
返回:A source store that must not outlive this asset pipeline. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.Delete(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L230) | Deletes a source asset and its metadata while retaining a Library tombstone for existing references.
`path`:Existing isolated file or directory path. | — | `System.IO.FileNotFoundException`:Thrown when the source does not exist. | -| [`public void Inno.Assets.Pipeline.AssetPipeline.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Retirement.cs#L27) | Releases watchers, catalog participants, canonical objects, and rebuildable staging state. | — | — | -| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetPipeline.ExportRuntimeArtifacts(string destinationContentRoot)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L108) | Exports the current source-free runtime catalog and its exact artifact closure.
返回:Counts and source identities for the deployed runtime snapshot.
`destinationContentRoot`:Empty destination for the deployed asset database. | — | `System.InvalidOperationException`:Thrown when runtime artifacts are incomplete, a deployed reference is authoring-only, or a transitive authoring input has failed or changed since import. Last-good inputs do not certify an export.
`System.IO.IOException`:Thrown when the destination is not empty or cannot be written. | -| [`public System.Threading.Tasks.Task Inno.Assets.Pipeline.AssetPipeline.ExportRuntimeArtifactsAsync(string destinationContentRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L144) | Exports a runtime-only artifact snapshot on a worker without loading artifact files into memory.
返回:A task that completes with counts and source identities for the captured runtime snapshot.
`destinationContentRoot`:The empty destination that receives the runtime asset database.
`cancellationToken`:The token checked while individual artifact files are copied. | Keeps generation admission and serialization pinned until the worker completes, fails or cancels. A pending or faulted generation cannot start a new export. | `System.InvalidOperationException`:Thrown when runtime artifacts are incomplete, transitive authoring inputs have failed or become stale, or the asset service is not available.
`System.OperationCanceledException`:Thrown when cancellation is requested before export completes. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L304) | Gets direct or transitive runtime dependencies of an asset.
返回:The dependency descriptors.
`asset`:The asset to query.
`recursive`:Whether transitive dependencies should be included. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetFileSystemChildren(Inno.Assets.AssetPath parent)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L84) | Gets immediate indexed children of a source directory.
返回:The immediate child entry snapshot.
`parent`:The isolated parent path. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L72) | Gets indexed source entries.
返回:The stable source entry snapshot.
`includeDirectories`:Whether directories should be included. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetImportDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L322) | Gets source import dependencies that invalidate an asset artifact.
返回:Canonical isolated source paths in stable order.
`asset`:The asset to query.
`recursive`:Whether transitive source dependencies should be included. | — | — | -| [`public Inno.Assets.Pipeline.AssetImportSettingsSnapshot Inno.Assets.Pipeline.AssetPipeline.GetImportSettings(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L57) | Reads a detached settings copy for the currently registered importer.
返回:The settings copy and the fingerprint required for saving it.
`path`:The isolated source path. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetLoadedPaths()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L290) | Gets isolated source paths for all canonical loaded assets.
返回:A stable isolated path snapshot. | — | — | -| [`public Inno.Assets.AssetReferenceInfo Inno.Assets.Pipeline.AssetPipeline.GetReferenceInfo(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L337) | Gets an engine-known reference diagnostic snapshot.
返回:The reference diagnostic snapshot.
`asset`:The asset to inspect. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.Import(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L33) | Imports one source asset from an isolated source mount.
返回:true when an importer handled the source.
`path`:The isolated source path. | — | — | -| [`public Inno.Assets.AssetObject Inno.Assets.Pipeline.AssetPipeline.Load(Inno.Assets.AssetPath path, System.Type assetType)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L65) | Loads a canonical asset using a runtime asset type selected by an authoring workflow.
返回:The canonical asset instance assignable to assetType.
`path`:The isolated source path.
`assetType`:The required concrete or base asset type. | — | `System.ArgumentNullException`:Thrown when assetType is null.
`System.ArgumentException`:Thrown when assetType does not derive from Inno.Assets.AssetObject.
`System.InvalidOperationException`:Thrown when no compatible canonical asset can be loaded. | -| [`public TAsset Inno.Assets.Pipeline.AssetPipeline.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L36) | Loads a canonical asset by isolated source path.
返回:The canonical asset instance.
`path`:The isolated source path. | — | — | -| [`public TAsset Inno.Assets.Pipeline.AssetPipeline.Load(System.Guid persistentId) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L95) | Loads a canonical asset by persistent identity.
返回:The canonical asset instance.
`persistentId`:The persistent asset identity. | — | `System.InvalidOperationException`:Thrown when no compatible asset can be loaded. | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.LoadAsync(Inno.Assets.AssetPath path, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L167) | Asynchronously loads a canonical asset by isolated source path.
返回:The canonical asset instance.
`path`:The isolated source path.
`cancellationToken`:Cancellation for the current caller's wait. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.LoadAsync(System.Guid persistentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L196) | Asynchronously loads a canonical asset by persistent identity.
返回:The canonical asset instance.
`persistentId`:The persistent asset identity.
`cancellationToken`:Cancellation for the current caller's wait. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.Move(Inno.Assets.AssetPath source, Inno.Assets.AssetPath target)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L163) | Moves a source asset while preserving its persistent identity and generated metadata.
`source`:Existing isolated source path.
`target`:New isolated source path. | — | `System.IO.FileNotFoundException`:Thrown when the source does not exist.
`System.IO.IOException`:Thrown when the target source or metadata already exists. | -| [`public Inno.Assets.Pipeline.AssetSampleImportTransaction Inno.Assets.Pipeline.AssetPipeline.PrepareSampleImport(Inno.Assets.AssetPath source)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.Samples.cs#L48) | Starts a private background sample clone without waiting for file copying or transformation.
返回:The generation-pinned transaction to advance, validate and commit on this pipeline's owner thread.
`source`:An indexed installed Plugin sample directory whose final segment starts with ~. | — | `System.ArgumentException`:The source is not an installed sample directory.
`System.IO.IOException`:The destination name already exists.
`System.InvalidOperationException`:Another mutation or generation transaction is active, or this is not the initialization thread. | -| [`public Inno.Assets.Pipeline.AssetSourceMountTransaction Inno.Assets.Pipeline.AssetPipeline.PrepareSourceMounts(System.Collections.Generic.IReadOnlyList mounts)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.SourceMounts.cs#L172) | Builds and validates an isolated source-mount candidate without changing active AssetPipeline state.
返回:A transaction that can be inspected, activated, completed, or rolled back.
`mounts`:A writable project source followed by zero or more read-only sources. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.ReplaceSourceMounts(System.Collections.Generic.IReadOnlyList mounts)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.SourceMounts.cs#L155) | Validates and atomically replaces the complete source-mount generation while preserving the active generation after any candidate failure.
`mounts`:A writable project source followed by zero or more read-only sources. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.Rescan()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L27) | Reconciles source files, generated files and the persistent catalog. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.RestoreProperties(System.Guid stableTypeId, byte[] propertyData, TValue target) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L138) | Restores serialized properties to the existing asset object.
`stableTypeId`:The stable type id consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`propertyData`:The property data consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`target`:The existing target that receives the validated result. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.Save(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L107) | Saves an asset to its current source path.
返回:true when an importer exported the asset.
`asset`:The asset to save. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L125) | Saves an asset to a writable isolated source path, preserving the destination source identity when it exists.
返回:true when an importer exported the asset.
`path`:Writable isolated source path.
`asset`:Asset to save. A detached value replaces the destination content without replacing its persistent identity. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.SaveImportSettings(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable? settings, string expectedFingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L82) | Saves import settings with conflict detection and immediately attempts to reimport the source.
返回:True when settings were saved and reimport succeeded; false when the saved settings failed import.
`path`:The writable isolated source path.
`settings`:The edited settings, or null to reset to importer defaults.
`expectedFingerprint`:The fingerprint obtained when reading the settings. | — | `System.IO.IOException`:The settings changed externally or could not be written. | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L39) | Tries to resolve a named artifact output.
返回:true when the output exists.
`persistentId`:The artifact owner identity.
`outputName`:The named output.
`artifact`:The output information when available. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetAssetType(Inno.Assets.AssetPath path, out System.Type? assetType)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L51) | Tries to resolve an asset type without loading the asset.
返回:true when the type can be resolved.
`path`:The isolated source path.
`assetType`:The resolved concrete asset type. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetFileSystemEntry(Inno.Assets.AssetPath path, out Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L98) | Tries to resolve an indexed source entry.
返回:true when the entry exists and is not generated metadata.
`path`:The isolated source path.
`entry`:The resolved source entry. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L85) | Tries to get a catalog snapshot by source-relative path.
返回:true when the path is cataloged.
`path`:The isolated source path.
`info`:The catalog snapshot when available. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetInfo(System.Guid persistentId, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L102) | Tries to get a catalog snapshot by persistent identity.
返回:true when the identity is cataloged.
`persistentId`:The persistent asset identity.
`info`:The catalog snapshot when available. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetPersistentId(Inno.Assets.AssetPath path, out System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L68) | Tries to resolve a persistent identity without loading the asset.
返回:true when catalog metadata exists.
`path`:The isolated source path.
`persistentId`:The resolved persistent identity. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryLoad(Inno.Assets.AssetPath path, out TAsset? asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L117) | Tries to load a canonical asset by isolated source path.
返回:true when a compatible asset was loaded.
`path`:The isolated source path.
`asset`:The canonical asset when successful. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryLoad(System.Guid persistentId, out TAsset? asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L142) | Tries to load a canonical asset by persistent identity.
返回:true when a compatible asset was loaded.
`persistentId`:The persistent asset identity.
`asset`:The canonical asset when successful. | — | — | -| [`public int Inno.Assets.Pipeline.AssetPipeline.UnloadUnusedAssets()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Retirement.cs#L40) | Collects assets that have no external managed references.
返回:The number of released canonical assets. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.Update()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L39) | Applies queued source and build changes on the initialization thread. | — | — | -| [`public void Inno.Assets.Pipeline.AssetPipeline.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L294) | Waits until queued source watcher changes have been processed. | — | — | -| [`public string Inno.Assets.Pipeline.AssetPipeline.artifactRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L75) | Gets the absolute generated artifact root. | — | — | -| [`public string Inno.Assets.Pipeline.AssetPipeline.assetRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L65) | Gets the absolute source asset root. | — | — | -| [`public Inno.Core.Identity.IdentityAllocator Inno.Assets.Pipeline.AssetPipeline.identities { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L93) | Gets the authoring identity domain shared by canonical assets and addressable source entries. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipeline.isInitialized { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L60) | Gets whether asset services are initialized. | — | — | -| [`public string Inno.Assets.Pipeline.AssetPipeline.libraryRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L70) | Gets the absolute root containing rebuildable asset database data. | — | — | -| [`public long Inno.Assets.Pipeline.AssetPipeline.revision { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L81) | Gets the monotonic identity of the current committed asset and source-mount state. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.sourceMounts { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L87) | Gets the active isolated source mount snapshot. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetPipeline : Inno.Assets.AssetResidencyProvider, System.IDisposable, Inno.Assets.IAssetLookup, Inno.Assets.IAssetReferenceResolver, Inno.Assets.IAssetPropertyStateResolver, Inno.Assets.IAssetArtifactLookup, Inno.Assets.IAssetResidency, Inno.Assets.Pipeline.IAssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L22) | Provides the single application-level entry point for importing, loading, saving and collecting assets. | — | — | - -### `Inno.Assets.Pipeline.AssetPipelineMode` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Assets.Pipeline.AssetPipelineMode.Authoring = 0`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L11) | Reconciles writable source files and produces immutable artifacts. | — | — | -| [`Inno.Assets.Pipeline.AssetPipelineMode.RuntimeArtifacts = 1`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L16) | Loads a read-only deployed catalog and its content-addressed artifacts without source files. | — | — | -| [`public enum Inno.Assets.Pipeline.AssetPipelineMode : int`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L6) | Defines which side of the asset pipeline an Inno.Assets.Pipeline.AssetPipeline instance serves. | — | — | - -### `Inno.Assets.Pipeline.AssetPipelineOptions` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Assets.Pipeline.AssetPipelineOptions Inno.Assets.Pipeline.AssetPipelineOptions.Create(string assetRoot, string libraryRoot)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L73) | Creates options with sensible defaults for most projects.
返回:Initialized options value.
`assetRoot`:Source assets root folder.
`libraryRoot`:Rebuildable Library folder. | — | — | -| [`public string Inno.Assets.Pipeline.AssetPipelineOptions.assetRoot { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L29) | Gets the root folder containing source assets. | — | — | -| [`public Inno.Assets.Pipeline.AssetCacheOptions Inno.Assets.Pipeline.AssetPipelineOptions.cacheOptions { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L59) | Gets the rebuildable cache policy. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetPipelineOptions.deferUnavailableExtensions { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L19) | Gets whether the composition host must still publish its initial authoring extensions. | Only missing extensions are deferred. The host must call AssetPipeline.CompleteExtensionDiscovery after activation. | — | -| [`public bool Inno.Assets.Pipeline.AssetPipelineOptions.enableFileSystemWatcher { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L39) | Gets whether file-system watching is enabled. | — | — | -| [`public int Inno.Assets.Pipeline.AssetPipelineOptions.fileWatcherFlushDelayMs { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L44) | Gets the watcher change coalescing delay in milliseconds. | — | — | -| [`public string Inno.Assets.Pipeline.AssetPipelineOptions.libraryRoot { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L34) | Gets the root folder containing rebuildable project data. | — | — | -| [`public Inno.Assets.Pipeline.AssetPipelineMode Inno.Assets.Pipeline.AssetPipelineOptions.mode { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L24) | Gets whether the pipeline reconciles authoring sources or consumes a deployed artifact catalog. | — | — | -| [`public System.Collections.Generic.IReadOnlyList? Inno.Assets.Pipeline.AssetPipelineOptions.sourceMounts { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L54) | Gets the complete source mount snapshot, or null to mount only Inno.Assets.Pipeline.AssetPipelineOptions.assetRoot. | — | — | -| [`public Inno.Assets.Pipeline.AssetSourcePolicy? Inno.Assets.Pipeline.AssetPipelineOptions.sourcePolicy { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L49) | Gets the source filtering policy. | — | — | -| [`public readonly struct Inno.Assets.Pipeline.AssetPipelineOptions`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L11) | Initialization options for Inno.Assets.Pipeline.AssetPipeline. | — | — | - -### `Inno.Assets.Pipeline.AssetSample` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -886,19 +555,6 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const string Inno.Assets.Pipeline.AssetSample.fileType = ".isample"`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L16) | Gets the logical File Browser type used for a sample directory. | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetSample.Contains(Inno.Assets.AssetPath path, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L64) | Determines whether a source path is a sample directory or is contained by one.
返回:true when the path belongs to an installed Plugin source and any directory segment starts with ~. Project content is never hidden from authoring import by this method.
`path`:The isolated source path to inspect.
`isDirectory`:Whether the path itself represents a directory. | — | — | -| [`public static string Inno.Assets.Pipeline.AssetSample.GetImportName(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L84) | Gets the original sample directory name for a writable Project copy.
返回:The unchanged final directory segment, including every leading ~.
`path`:A sample directory path whose final segment starts with ~. | — | `System.ArgumentException`:Thrown when path does not identify a sample directory or has no usable name after its prefix. | -| [`public static bool Inno.Assets.Pipeline.AssetSample.HasSampleDirectoryName(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L28) | Determines whether the final path segment uses the sample-directory naming convention.
返回:true when the final segment starts with ~, regardless of whether the path belongs to the writable Project source or an installed Plugin source.
`path`:The isolated source path to inspect. | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetSample.IsRoot(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L46) | Determines whether the final segment identifies a sample directory.
返回:true when the path belongs to an installed Plugin source and its final segment starts with ~. A Project directory with the same name remains normal writable authoring content.
`path`:The isolated source path to inspect. | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetSample.IsRuntimeExcluded(Inno.Assets.AssetPath path, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L108) | Determines whether a path belongs to an authoring-only sample subtree that must not enter a Player runtime closure.
返回:true when any directory segment starts with ~, including in the writable Project source.
`path`:The isolated source path to inspect.
`isDirectory`:Whether the path itself represents a directory. | — | — | -| [`public static class Inno.Assets.Pipeline.AssetSample`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L11) | Defines authoring sample directories and their installed Plugin behavior. | — | — | - -### `Inno.Assets.Pipeline.AssetSampleImportTransaction` - @@ -909,34 +565,6 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.Advance()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L113) | Starts background indexing after cloning, then adopts its completed catalog without waiting.
返回:True when the candidate is indexed and ready for BeginValidation; false while preparation is pending. | — | `System.InvalidOperationException`:The transaction is finished or the caller is not its initialization thread.
`System.OperationCanceledException`:Preparation or indexing observed cancellation before a candidate could be adopted. | -| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.BeginValidation(System.Func validate)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L167) | Starts one authoring preflight after indexing, retaining its dependencies until it drains.
`validate`:Owner-thread capture followed by asynchronous validation. Continuations must not mutate live Assets. | — | `System.ArgumentNullException`:The validation callback is null.
`System.InvalidOperationException`:The candidate is not indexed, validation already started, or the transaction is finished. | -| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Cancel()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L222) | Requests cancellation of copying and validation while retaining all dependencies. | Cancellation does not wait. Continue owner-thread retirement until Rollback or Dispose completes. | — | -| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Commit(System.Action? beforePublish = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L203) | Publishes a successfully validated import exactly once, without waiting for unfinished work.
`beforePublish`:Optional owner-thread finalization, such as recording neutral History data. Failure leaves the transaction open for Rollback and no Changed notification is published. | — | `System.InvalidOperationException`:Validation is unfinished, the transaction is finished, or the caller is not its owner thread.
`System.OperationCanceledException`:The import or its validation was canceled before publication. | -| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L271) | Retires an uncommitted import on its owner thread after cancellation has drained. | — | `Inno.Core.Execution.RetirementPendingException`:Canceled work has not drained; disposal must be retried without releasing its owner. | -| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Rollback()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L230) | Cancels work and removes an uncommitted copy after all background consumers have drained. | — | `Inno.Core.Execution.RetirementPendingException`:Background work is still draining; retain the transaction and retry on its owner thread. | -| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.isFaulted { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L94) | Gets whether failed publication or retirement has faulted the host and requires a restart. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.isValidationComplete { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L99) | Gets whether background validation has drained and Commit can inspect its outcome. | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleImportTransaction.target { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L89) | Gets the destination path with the original sample directory name preserved. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSampleImportTransaction : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L22) | Owns a cancellable sample clone, its pinned generation and its owner-thread publication. | Advance and Commit run on the AssetPipeline initialization thread. A prepared copy is indexed in an isolated catalog for validation without publishing Changed; ordinary mutations and generation changes are deferred. Rollback must finish before the pipeline or any validation dependency can be released. | — | - -### `Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute.AssetSampleSourceRewriterAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L144) | Creates one ordered source-language transformer declaration.
`id`:Stable extension identity used for diagnostics and ordering. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L153) | Gets the stable extension identity. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L136) | Registers a source-language sample transformer through the reloadable type catalog. | — | — | - -### `Inno.Assets.Pipeline.AssetSampleTransformContext` - @@ -945,36 +573,11 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Assets.Pipeline.AssetSampleTransformContext.MapType(System.Guid oldId, System.Guid newId)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L102) | Registers one source-language type replacement for subsequent structured asset rewriting.
`oldId`:The type identity stored in installed sample assets.
`newId`:The type identity declared by the cloned source. | — | `System.IO.InvalidDataException`:A source identity is reused inconsistently. | -| [`public bool Inno.Assets.Pipeline.AssetSampleTransformContext.TryGetSourceIdentity(string relativePath, out System.Guid oldId, out System.Guid newId)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L73) | Resolves the source and clone identity of one file relative to the sample directory.
返回:Whether both identities were recorded from sample metadata.
`relativePath`:Slash-separated source-local file path below the sample directory.
`oldId`:Receives the installed source identity.
`newId`:Receives the writable clone identity. | — | — | -| [`public System.Threading.CancellationToken Inno.Assets.Pipeline.AssetSampleTransformContext.cancellationToken { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L51) | Gets cancellation for this private, generation-pinned transformation. | — | — | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Assets.Pipeline.AssetSampleTransformContext.identityMap { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L56) | Gets cloned source identities, including directory and file metadata. | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleTransformContext.source { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L41) | Gets the installed sample source path. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSampleTransformContext.stagedRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L36) | Gets the absolute private staging directory; files here are committed only after all transforms succeed. | — | — | -| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleTransformContext.target { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L46) | Gets the writable project destination path. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSampleTransformContext`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L12) | Gives a source-language extension access to a staged sample clone before serialized references are remapped. | — | — | - -### `Inno.Assets.Pipeline.AssetSerializationServices` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.AssetSerializationServices.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L69) | Captures typed properties and their references as one neutral, owner-independent value.
返回:Frozen native properties with exact stable type identity and collected direct dependencies.
`value`:Current generation settings object, never retained. | — | — | -| [`public TValue Inno.Assets.Pipeline.AssetSerializationServices.Deserialize(System.ReadOnlySpan bytes) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L126) | Deserializes one structured value against the active importer candidate generation.
返回:A restored value owned by the caller.
`bytes`:Complete structured bytes produced by the common serialization system. | — | `System.InvalidOperationException`:Thrown when the value contains an asset reference but no candidate resolver is available. | -| [`public System.Guid Inno.Assets.Pipeline.AssetSerializationServices.GetStableTypeId()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L55) | Resolves the stable persistent type identity of a registered serializable type.
返回:The stable type identifier owned by the active extension generation. | — | `System.InvalidOperationException`:Thrown when TValue has no stable type registration. | -| [`public byte[] Inno.Assets.Pipeline.AssetSerializationServices.Serialize(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L96) | Serializes one structured value and declares every encountered asset reference as a runtime dependency.
返回:Deterministic structured bytes owned by the caller.
`value`:The value to serialize through the active converter generation. | — | `System.ArgumentNullException`:Thrown when value is null. | -| [`public sealed class Inno.Assets.Pipeline.AssetSerializationServices`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L12) | Provides generation-bound structured serialization without exposing host registries to importer extensions. | — | — | - -### `Inno.Assets.Pipeline.AssetSourceMount` - + + + + + @@ -982,176 +585,369 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetSourceMount.AssetSourceMount(Inno.Assets.AssetSourceId id, string rootPath, bool isReadOnly, System.Collections.Generic.IEnumerable? dependencies = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L31) | Creates an asset source mount.
`id`:Stable source identity.
`rootPath`:Absolute or resolvable physical source root.
`isReadOnly`:Whether all source mutations must be rejected.
`dependencies`:Other read-only sources this source may reference. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSourceMount.Resolve(string localPath)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L77) | Resolves a source-local path and rejects physical root escape.
返回:An absolute path contained by this mount.
`localPath`:Source-local path. | — | — | -| [`public System.Collections.Generic.IReadOnlySet Inno.Assets.Pipeline.AssetSourceMount.dependencySourceIds { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L66) | Gets explicitly declared cross-source dependencies. | — | — | -| [`public Inno.Assets.AssetSourceId Inno.Assets.Pipeline.AssetSourceMount.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L51) | Gets the stable source identity. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetSourceMount.isReadOnly { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L61) | Gets whether source mutations are forbidden. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSourceMount.rootPath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L56) | Gets the controlled physical source root. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSourceMount`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L14) | Maps one isolated asset source to a controlled physical root. | — | — | - -### `Inno.Assets.Pipeline.AssetSourceMountTransaction` - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetSourceMountTransaction.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L136) | Retains one verified output so cache collection cannot remove an active generation.
返回:A caller-owned immutable artifact lease.
`persistentId`:The persistent asset identity.
`outputName`:The required output protocol. | — | `System.InvalidOperationException`:The requested output is unavailable or invalid. | -| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Activate()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L173) | Activates this candidate without releasing the previous generation or notifying observers. | — | — | -| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Complete()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L197) | Commits an activated candidate, notifies observers, and retires the previous generation. | — | `Inno.Core.Execution.RetirementPendingException`:Retirement exceeded its deadline. Both generations remain owned and the host must restart.
`System.AggregateException`:Publication or completed retirement failed; the host generation gate is faulted. | -| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L249) | Discards an unfinished candidate and releases its rebuildable staging storage. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L83) | Gets candidate source entries without publishing them to active AssetPipeline consumers.
返回:A stable candidate entry snapshot.
`includeDirectories`:Whether directory entries should be included. | — | — | -| [`public TAsset Inno.Assets.Pipeline.AssetSourceMountTransaction.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L105) | Loads one candidate asset by isolated source path.
返回:The candidate asset instance.
`path`:Candidate source path. | — | `System.InvalidOperationException`:Thrown when no compatible candidate asset exists. | -| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Rollback()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L223) | Discards the candidate or restores the previous generation after provisional activation. | — | `Inno.Core.Execution.RetirementPendingException`:Candidate retirement exceeded its deadline; its owners remain retained until host restart.
`System.AggregateException`:Compensation or completed retirement failed. | -| [`public bool Inno.Assets.Pipeline.AssetSourceMountTransaction.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L160) | Tries to resolve one named candidate artifact.
返回:True when the candidate output exists.
`persistentId`:Candidate asset identity.
`outputName`:Named artifact output.
`artifact`:Artifact information when available. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetSourceMountTransaction.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L126) | Tries to get candidate catalog information by isolated source path.
返回:True when the candidate path is cataloged.
`path`:Candidate source path.
`info`:Candidate catalog information when available. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.recoveryChanges { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L71) | Gets neutral canonical reference outcomes after activation, or an empty set before activation or after rollback. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.sourceMounts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L65) | Gets the complete isolated mount snapshot represented by this candidate. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSourceMountTransaction : System.IDisposable, Inno.Assets.Pipeline.IAssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L15) | Holds an isolated source-mount candidate that can be inspected before it atomically replaces the active Asset Database. | — | — | - -### `Inno.Assets.Pipeline.AssetSourcePolicy` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Assets.Pipeline.AssetSourcePolicy.AssetSourcePolicy()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L47) | Creates a source policy using the engine's default noise filters. | — | — | -| [`public Inno.Assets.Pipeline.AssetSourcePolicy.AssetSourcePolicy(System.Collections.Generic.IEnumerable? ignoredFileNames, System.Collections.Generic.IEnumerable? ignoredDirectoryNames, System.Collections.Generic.IEnumerable? ignoredPrefixes, System.Collections.Generic.IEnumerable? ignoredSuffixes)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L67) | Creates a source policy with additional ignored names and affixes.
`ignoredFileNames`:Additional case-insensitive file names excluded from every source mount.
`ignoredDirectoryNames`:Additional case-insensitive directory names excluded with their descendants.
`ignoredPrefixes`:Additional file-name prefixes treated as transient authoring noise.
`ignoredSuffixes`:Additional file-name suffixes treated as transient authoring noise. | — | — | -| [`public static bool Inno.Assets.Pipeline.AssetSourcePolicy.IsGeneratedPath(string relativePath)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L131) | Determines whether a path is generated asset metadata.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`relativePath`:The normalized path relative to its owning source mount. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetSourcePolicy.IsIgnored(string relativePath, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L98) | Determines whether an entry is excluded from the source database.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`relativePath`:The normalized path relative to its owning source mount.
`isDirectory`:Whether the source entry represents a directory. | — | — | -| [`public static Inno.Assets.Pipeline.AssetSourcePolicy Inno.Assets.Pipeline.AssetSourcePolicy.defaultPolicy { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L84) | Gets the default source policy. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSourcePolicy`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L10) | Defines which physical entries are excluded from an asset source tree. | — | — | - -### `Inno.Assets.Pipeline.AssetSourceSnapshot` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public byte[] Inno.Assets.Pipeline.AssetSourceSnapshot.bytes { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L161) | Gets a copy of the captured native source bytes. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSourceSnapshot.contentHash { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L165) | Gets the source fingerprint required by a subsequent save. | — | — | -| [`public bool Inno.Assets.Pipeline.AssetSourceSnapshot.isReadOnly { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L169) | Gets whether the installation source cannot be edited in place. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L146) | Contains detached authoring bytes and conflict-detection state. | — | — | - -### `Inno.Assets.Pipeline.AssetSourceStore` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public TAsset Inno.Assets.Pipeline.AssetSourceStore.Decode(System.ReadOnlySpan bytes) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L67) | Restores a detached native value using current-generation asset references.
返回:A detached value; referenced assets remain canonical read-only inputs.
`bytes`:Native source bytes. | — | — | -| [`public byte[] Inno.Assets.Pipeline.AssetSourceStore.Encode(TAsset asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L53) | Encodes native asset properties with the owner's reference context and dependency capture.
返回:Native source bytes, with no canonical asset mutation.
`asset`:Detached editable value. | — | — | -| [`public Inno.Assets.Pipeline.AssetSourceSnapshot Inno.Assets.Pipeline.AssetSourceStore.Read(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L34) | Reads a mounted source without modifying its canonical asset or compiled artifacts.
返回:A detached source and optimistic concurrency fingerprint.
`path`:Exact mounted source identity. | — | — | -| [`public string Inno.Assets.Pipeline.AssetSourceStore.Save(Inno.Assets.AssetPath path, byte[] bytes, string? expectedHash)`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L85) | Atomically saves bytes after checking the source fingerprint; import is a separate operation.
返回:The saved fingerprint.
`path`:Writable mounted destination.
`bytes`:Complete serializable source, including invalid authoring states.
`expectedHash`:Last read/save fingerprint; null requires a nonexistent destination. | — | — | -| [`public sealed class Inno.Assets.Pipeline.AssetSourceStore`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L12) | Reads detached authoring bytes and saves them independently of successful import. | — | — | - -### `Inno.Assets.Pipeline.EditorAssets` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.EditorAssets.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L64) | Captures reload-safe settings using the currently bound authoring owner's converters and references.
返回:Native properties, stable type identity and automatically collected dependencies.
`value`:Settings to capture without saving or mutating their referenced assets. | — | — | -| [`public static TAsset Inno.Assets.Pipeline.EditorAssets.DecodeNative(byte[] bytes) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L43) | Decodes detached native asset bytes with the current authoring pipeline's converters and reference context.
返回:A detached asset value that can be edited without publishing it.
`bytes`:Bytes previously produced by the native asset source codec. | — | — | -| [`public static byte[] Inno.Assets.Pipeline.EditorAssets.EncodeNative(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L22) | Encodes a detached native asset with the current authoring pipeline's converters and reference context.
返回:Reload-safe native asset bytes suitable for draft state and History payloads.
`asset`:Complete detached value to encode without saving or importing it. | — | — | -| [`public static bool Inno.Assets.Pipeline.EditorAssets.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L88) | Creates or replaces a writable project asset source and imports the committed result.
返回:true when the source was committed and imported successfully.
`path`:The writable project asset path.
`asset`:The complete source value to persist. Saving over an existing source retains that source's persistent identity. | — | `System.ArgumentNullException`:Thrown when asset is null.
`System.InvalidOperationException`:Thrown when the current execution context is not bound to an authoring asset pipeline. | -| [`public static class Inno.Assets.Pipeline.EditorAssets`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L11) | Provides Editor-script asset mutations through the authoring pipeline bound by the current host. | — | — | - -### `Inno.Assets.Pipeline.IAssetSampleSourceRewriter` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Assets.Pipeline.IAssetSampleSourceRewriter.Transform(Inno.Assets.Pipeline.AssetSampleTransformContext context)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L129) | Rewrites staged source files and registers any changed serialized type identities.
`context`:The transaction-owned staged clone. | Runs on a worker with a pinned extension generation. Access only transaction-owned files and context data, observe cancellation, and do not access live Assets or Editor state. | — | -| [`public interface Inno.Assets.Pipeline.IAssetSampleSourceRewriter`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L117) | Rewrites a source language in a private sample clone before source assets are published. | — | — | - -### `Inno.Assets.Pipeline.IAssetSourceSnapshot` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.IAssetSourceSnapshot.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L31) | Captures the indexed entries represented by this view.
返回:A stable entry collection for owner-thread input discovery.
`includeDirectories`:Whether directory entries are included. | — | — | -| [`TAsset Inno.Assets.Pipeline.IAssetSourceSnapshot.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L48) | Loads an authoring asset without publishing a candidate catalog.
返回:The resolved authoring asset.
`path`:The isolated source path. | — | `System.InvalidOperationException`:No compatible asset can be loaded from this view. | -| [`bool Inno.Assets.Pipeline.IAssetSourceSnapshot.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L62) | Resolves immutable catalog information by source path.
返回:True when catalog information exists.
`path`:The isolated source path.
`info`:The catalog information, or null when the source is absent. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.IAssetSourceSnapshot.sourceMounts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L20) | Gets the source mounts represented by this view. | — | — | -| [`public interface Inno.Assets.Pipeline.IAssetSourceSnapshot : Inno.Assets.IAssetArtifactLookup`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L15) | Provides read-only authoring source access for active or isolated candidate compilation. | Capture inputs on the owner thread and retain the owning generation transaction until all asynchronous consumers drain. This view neither activates nor commits its underlying catalog. | — | - -### `Inno.Assets.Pipeline.NativeAssetSourceSerialization` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static byte[] Inno.Assets.Pipeline.NativeAssetSourceSerialization.Export(TAsset asset, Inno.Assets.Pipeline.AssetSerializationServices services) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Serialization/NativeAssetSourceSerialization.cs#L30) | Serializes one asset's editable properties and direct asset dependencies.
返回:Deterministic native source bytes.
`asset`:Unsaved or loaded asset whose editable state should be captured.
`services`:The generation-bound serialization services supplied by the export context. | — | — | -| [`public static TAsset Inno.Assets.Pipeline.NativeAssetSourceSerialization.Import(System.ReadOnlySpan bytes, Inno.Assets.Pipeline.AssetSerializationServices services, out System.Collections.Generic.IReadOnlyList dependencies) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Serialization/NativeAssetSourceSerialization.cs#L73) | Restores one concrete asset and its declared direct dependencies.
返回:A detached asset ready for an import transaction.
`bytes`:Native source bytes.
`dependencies`:Receives persistent dependencies discovered during export.
`services`:The generation-bound serialization and reference services supplied by the import context. | — | `System.InvalidOperationException`:Thrown when the source type identity is incompatible. | +| [`public readonly struct Inno.Assets.Pipeline.AssetCacheOptions`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L8) | Controls cleanup of rebuildable asset database data. | — | — | +| [`public long Inno.Assets.Pipeline.AssetCacheOptions.maximumSizeBytes { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L13) | Gets the maximum artifact cache size in bytes, or zero for no size limit. | — | — | +| [`public System.TimeSpan Inno.Assets.Pipeline.AssetCacheOptions.garbageCollectionGracePeriod { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L18) | Gets the minimum age of an unreachable artifact before it can be collected. | — | — | +| [`public static Inno.Assets.Pipeline.AssetCacheOptions Inno.Assets.Pipeline.AssetCacheOptions.CreateDefault()`](../../src/content/assets/Inno.Assets.Pipeline/AssetCacheOptions.cs#L26) | Creates the default cache policy. | The default cache options. | — | +| [`public sealed class Inno.Assets.Pipeline.AssetPipeline : Inno.Assets.AssetResidencyProvider, System.IDisposable, Inno.Assets.IAssetLookup, Inno.Assets.IAssetReferenceResolver, Inno.Assets.IAssetPropertyStateResolver, Inno.Assets.IAssetArtifactLookup, Inno.Assets.IAssetResidency, Inno.Assets.Pipeline.IAssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L22) | Provides the single application-level entry point for importing, loading, saving and collecting assets. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L39) | Tries to resolve a named artifact output.
`persistentId`: The artifact owner identity.
`outputName`: The named output.
`artifact`: The output information when available. | when the output exists. | — | +| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetPipeline.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L61) | Acquires one verified authoring artifact generation for an explicit lifetime.
`persistentId`: Persistent identity of the artifact owner.
`outputName`: Stable artifact output name. | A lease over the currently committed immutable artifact generation. | `System.InvalidOperationException`: Thrown when the requested artifact is unavailable. | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.BuildAsync(Inno.Assets.AssetObject definition, System.Collections.Generic.IReadOnlyList inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L81) | Runs an aggregate asset build using the processor registered for a definition.
`definition`: The build definition asset.
`inputs`: The immutable input catalog snapshots.
`cancellationToken`: Cancellation for the candidate build. | The content-addressed output bundle key. | — | +| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetPipeline.ExportRuntimeArtifacts(string destinationContentRoot)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L108) | Exports the current source-free runtime catalog and its exact artifact closure.
`destinationContentRoot`: Empty destination for the deployed asset database. | Counts and source identities for the deployed runtime snapshot. | `System.InvalidOperationException`: Thrown when runtime artifacts are incomplete, a deployed reference is authoring-only, or a transitive authoring input has failed or changed since import. Last-good inputs do not certify an export.
`System.IO.IOException`: Thrown when the destination is not empty or cannot be written. | +| [`public System.Threading.Tasks.Task Inno.Assets.Pipeline.AssetPipeline.ExportRuntimeArtifactsAsync(string destinationContentRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Artifacts.cs#L144) | Exports a runtime-only artifact snapshot on a worker without loading artifact files into memory.
`destinationContentRoot`: The empty destination that receives the runtime asset database.
`cancellationToken`: The token checked while individual artifact files are copied. | A task that completes with counts and source identities for the captured runtime snapshot. | `System.InvalidOperationException`: Thrown when runtime artifacts are incomplete, transitive authoring inputs have failed or become stale, or the asset service is not available.
`System.OperationCanceledException`: Thrown when cancellation is requested before export completes. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.Rescan()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L27) | Reconciles source files, generated files and the persistent catalog. | — | — | +| [`public void Inno.Assets.Pipeline.AssetPipeline.Update()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L39) | Applies queued source and build changes on the initialization thread. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L72) | Gets indexed source entries.
`includeDirectories`: Whether directories should be included. | The stable source entry snapshot. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetFileSystemChildren(Inno.Assets.AssetPath parent)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L84) | Gets immediate indexed children of a source directory.
`parent`: The isolated parent path. | The immediate child entry snapshot. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetFileSystemEntry(Inno.Assets.AssetPath path, out Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Changes.cs#L98) | Tries to resolve an indexed source entry.
`path`: The isolated source path.
`entry`: The resolved source entry. | when the entry exists and is not generated metadata. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.isInitialized { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L60) | Gets whether asset services are initialized. | — | — | +| [`public string Inno.Assets.Pipeline.AssetPipeline.assetRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L65) | Gets the absolute source asset root. | — | — | +| [`public string Inno.Assets.Pipeline.AssetPipeline.libraryRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L70) | Gets the absolute root containing rebuildable asset database data. | — | — | +| [`public string Inno.Assets.Pipeline.AssetPipeline.artifactRoot { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L75) | Gets the absolute generated artifact root. | — | — | +| [`public long Inno.Assets.Pipeline.AssetPipeline.revision { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L81) | Gets the monotonic identity of the current committed asset and source-mount state. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.sourceMounts { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L87) | Gets the active isolated source mount snapshot. | — | — | +| [`public Inno.Core.Identity.IdentityAllocator Inno.Assets.Pipeline.AssetPipeline.identities { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L93) | Gets the authoring identity domain shared by canonical assets and addressable source entries. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourceStore Inno.Assets.Pipeline.AssetPipeline.CreateSourceStore()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L102) | Creates a detached source editor sharing this owner's mounts, native converters and references. | A source store that must not outlive this asset pipeline. | — | +| [`public Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.AssetPipeline.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L116) | Captures native settings and nested asset dependencies through this explicit authoring owner.
`value`: Typed settings, never retained by the returned snapshot.
`TValue`: Current settings type. | Complete stable properties and dependencies without writing or importing an asset. | — | +| [`public void Inno.Assets.Pipeline.AssetPipeline.RestoreProperties(System.Guid stableTypeId, byte[] propertyData, TValue target) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L138) | Restores serialized properties to the existing asset object.
`stableTypeId`: The stable type id consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`propertyData`: The property data consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`target`: The existing target that receives the validated result.
`TValue`: Serialized asset object type receiving restored properties. | — | — | +| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.Changed`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L158) | Occurs after an asset database transaction has committed. | — | — | +| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.AssetReloaded`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L163) | Occurs after a canonical loaded asset has been updated in place. | — | — | +| [`public System.Action? Inno.Assets.Pipeline.AssetPipeline.SourceMountsChanged`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L169) | Occurs after a complete isolated source-mount generation is atomically replaced. | — | — | +| [`public Inno.Assets.Pipeline.AssetPipeline.AssetPipeline(Inno.Extensibility.Modules.ModuleHost modules, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, Inno.Assets.Pipeline.AssetPipelineOptions options)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L199) | Creates one isolated authoring or deployed-runtime asset pipeline.
`modules`: The module host whose candidate generations coordinate importer refreshes.
`types`: The type catalog used to discover and resolve asset extensions.
`serialization`: The serialization registry used for metadata and canonical asset state.
`identities`: The identity allocator that owns every canonical asset in this pipeline.
`logs`: The explicitly owned router receiving asset pipeline diagnostics.
`diagnostics`: The diagnostic hub that owns source database and asset processing reports.
`options`: The asset source, artifact and watcher configuration. | — | `System.ArgumentNullException`: Thrown when a required service is null. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.cs#L294) | Waits until queued source watcher changes have been processed. | — | — | +| [`public void Inno.Assets.Pipeline.AssetPipeline.CompleteExtensionDiscovery()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L31) | Ends initial authoring extension discovery and strictly retries dependent imports. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetAssetType(Inno.Assets.AssetPath path, out System.Type? assetType)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L51) | Tries to resolve an asset type without loading the asset.
`path`: The isolated source path.
`assetType`: The resolved concrete asset type. | when the type can be resolved. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetPersistentId(Inno.Assets.AssetPath path, out System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L68) | Tries to resolve a persistent identity without loading the asset.
`path`: The isolated source path.
`persistentId`: The resolved persistent identity. | when catalog metadata exists. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L85) | Tries to get a catalog snapshot by source-relative path.
`path`: The isolated source path.
`info`: The catalog snapshot when available. | when the path is cataloged. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryGetInfo(System.Guid persistentId, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Discovery.cs#L102) | Tries to get a catalog snapshot by persistent identity.
`persistentId`: The persistent asset identity.
`info`: The catalog snapshot when available. | when the identity is cataloged. | — | +| [`public TAsset Inno.Assets.Pipeline.AssetPipeline.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L36) | Loads a canonical asset by isolated source path.
`path`: The isolated source path.
`TAsset`: The required asset type. | The canonical asset instance. | — | +| [`public Inno.Assets.AssetObject Inno.Assets.Pipeline.AssetPipeline.Load(Inno.Assets.AssetPath path, System.Type assetType)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L65) | Loads a canonical asset using a runtime asset type selected by an authoring workflow.
`path`: The isolated source path.
`assetType`: The required concrete or base asset type. | The canonical asset instance assignable to . | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when does not derive from .
`System.InvalidOperationException`: Thrown when no compatible canonical asset can be loaded. | +| [`public TAsset Inno.Assets.Pipeline.AssetPipeline.Load(System.Guid persistentId) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L95) | Loads a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`TAsset`: The required asset type. | The canonical asset instance. | `System.InvalidOperationException`: Thrown when no compatible asset can be loaded. | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryLoad(Inno.Assets.AssetPath path, out TAsset? asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L117) | Tries to load a canonical asset by isolated source path.
`path`: The isolated source path.
`asset`: The canonical asset when successful.
`TAsset`: The required asset type. | when a compatible asset was loaded. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.TryLoad(System.Guid persistentId, out TAsset? asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L142) | Tries to load a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`asset`: The canonical asset when successful.
`TAsset`: The required asset type. | when a compatible asset was loaded. | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.LoadAsync(Inno.Assets.AssetPath path, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L167) | Asynchronously loads a canonical asset by isolated source path.
`path`: The isolated source path.
`cancellationToken`: Cancellation for the current caller's wait.
`TAsset`: The required asset type. | The canonical asset instance. | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetPipeline.LoadAsync(System.Guid persistentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L196) | Asynchronously loads a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`cancellationToken`: Cancellation for the current caller's wait.
`TAsset`: The required asset type. | The canonical asset instance. | — | +| [`public System.Threading.Tasks.ValueTask> Inno.Assets.Pipeline.AssetPipeline.AcquireAsync(Inno.Assets.AssetPath path, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L225) | Asynchronously acquires a canonical authoring asset by path for an explicit managed lifetime.
`path`: Mount-qualified logical asset path.
`cancellationToken`: Cancellation for this caller's asynchronous wait.
`TAsset`: Required asset contract. | A lease whose strong reference participates in the existing reachability sweep. | — | +| [`public System.Threading.Tasks.ValueTask> Inno.Assets.Pipeline.AssetPipeline.AcquireAsync(System.Guid persistentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L250) | Asynchronously acquires a canonical authoring asset by persistent identity.
`persistentId`: Persistent asset identity.
`cancellationToken`: Cancellation for this caller's asynchronous wait.
`TAsset`: Required asset contract. | A lease whose strong reference participates in the existing reachability sweep. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetLoadedPaths()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L290) | Gets isolated source paths for all canonical loaded assets. | A stable isolated path snapshot. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L304) | Gets direct or transitive runtime dependencies of an asset.
`asset`: The asset to query.
`recursive`: Whether transitive dependencies should be included. | The dependency descriptors. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetPipeline.GetImportDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L322) | Gets source import dependencies that invalidate an asset artifact.
`asset`: The asset to query.
`recursive`: Whether transitive source dependencies should be included. | Canonical isolated source paths in stable order. | — | +| [`public Inno.Assets.AssetReferenceInfo Inno.Assets.Pipeline.AssetPipeline.GetReferenceInfo(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Loading.cs#L337) | Gets an engine-known reference diagnostic snapshot.
`asset`: The asset to inspect. | The reference diagnostic snapshot. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.Import(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L33) | Imports one source asset from an isolated source mount.
`path`: The isolated source path. | when an importer handled the source. | — | +| [`public Inno.Assets.Pipeline.AssetImportSettingsSnapshot Inno.Assets.Pipeline.AssetPipeline.GetImportSettings(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L57) | Reads a detached settings copy for the currently registered importer.
`path`: The isolated source path. | The settings copy and the fingerprint required for saving it. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.SaveImportSettings(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable? settings, string expectedFingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L82) | Saves import settings with conflict detection and immediately attempts to reimport the source.
`path`: The writable isolated source path.
`settings`: The edited settings, or null to reset to importer defaults.
`expectedFingerprint`: The fingerprint obtained when reading the settings. | True when settings were saved and reimport succeeded; false when the saved settings failed import. | `System.IO.IOException`: The settings changed externally or could not be written. | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.Save(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L107) | Saves an asset to its current source path.
`asset`: The asset to save. | when an importer exported the asset. | — | +| [`public bool Inno.Assets.Pipeline.AssetPipeline.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L125) | Saves an asset to a writable isolated source path, preserving the destination source identity when it exists.
`path`: Writable isolated source path.
`asset`: Asset to save. A detached value replaces the destination content without replacing its persistent identity. | when an importer exported the asset. | — | +| [`public void Inno.Assets.Pipeline.AssetPipeline.Move(Inno.Assets.AssetPath source, Inno.Assets.AssetPath target)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L163) | Moves a source asset while preserving its persistent identity and generated metadata.
`source`: Existing isolated source path.
`target`: New isolated source path. | — | `System.IO.FileNotFoundException`: Thrown when the source does not exist.
`System.IO.IOException`: Thrown when the target source or metadata already exists. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.Delete(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L230) | Deletes a source asset and its metadata while retaining a Library tombstone for existing references.
`path`: Existing isolated file or directory path. | — | `System.IO.FileNotFoundException`: Thrown when the source does not exist. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.CreateDirectory(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Mutations.cs#L298) | Creates a tracked source directory and its persistent metadata.
`path`: New isolated directory path. | — | `System.IO.DirectoryNotFoundException`: Thrown when the parent directory does not exist.
`System.IO.IOException`: Thrown when the target already exists. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Retirement.cs#L27) | Releases watchers, catalog participants, canonical objects, and rebuildable staging state. | — | — | +| [`public int Inno.Assets.Pipeline.AssetPipeline.UnloadUnusedAssets()`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipeline.Retirement.cs#L40) | Collects assets that have no external managed references. | The number of released canonical assets. | — | +| [`public Inno.Assets.Pipeline.AssetSampleImportTransaction Inno.Assets.Pipeline.AssetPipeline.PrepareSampleImport(Inno.Assets.AssetPath source)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.Samples.cs#L48) | Starts a private background sample clone without waiting for file copying or transformation.
`source`: An indexed installed Plugin sample directory whose final segment starts with ~. | The generation-pinned transaction to advance, validate and commit on this pipeline's owner thread. | `System.ArgumentException`: The source is not an installed sample directory.
`System.IO.IOException`: The destination name already exists.
`System.InvalidOperationException`: Another mutation or generation transaction is active, or this is not the initialization thread. | +| [`public void Inno.Assets.Pipeline.AssetPipeline.ReplaceSourceMounts(System.Collections.Generic.IReadOnlyList mounts)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.SourceMounts.cs#L155) | Validates and atomically replaces the complete source-mount generation while preserving the active generation after any candidate failure.
`mounts`: A writable project source followed by zero or more read-only sources. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourceMountTransaction Inno.Assets.Pipeline.AssetPipeline.PrepareSourceMounts(System.Collections.Generic.IReadOnlyList mounts)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetPipeline.SourceMounts.cs#L172) | Builds and validates an isolated source-mount candidate without changing active AssetPipeline state.
`mounts`: A writable project source followed by zero or more read-only sources. | A transaction that can be inspected, activated, completed, or rolled back. | — | +| [`public enum Inno.Assets.Pipeline.AssetPipelineMode : int`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L6) | Defines which side of the asset pipeline an instance serves. | — | — | +| [`Inno.Assets.Pipeline.AssetPipelineMode.Authoring = 0`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L11) | Reconciles writable source files and produces immutable artifacts. | — | — | +| [`Inno.Assets.Pipeline.AssetPipelineMode.RuntimeArtifacts = 1`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineMode.cs#L16) | Loads a read-only deployed catalog and its content-addressed artifacts without source files. | — | — | +| [`public readonly struct Inno.Assets.Pipeline.AssetPipelineOptions`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L11) | Initialization options for . | — | — | +| [`public bool Inno.Assets.Pipeline.AssetPipelineOptions.deferUnavailableExtensions { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L19) | Gets whether the composition host must still publish its initial authoring extensions. | — | — | +| [`public Inno.Assets.Pipeline.AssetPipelineMode Inno.Assets.Pipeline.AssetPipelineOptions.mode { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L24) | Gets whether the pipeline reconciles authoring sources or consumes a deployed artifact catalog. | — | — | +| [`public string Inno.Assets.Pipeline.AssetPipelineOptions.assetRoot { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L29) | Gets the root folder containing source assets. | — | — | +| [`public string Inno.Assets.Pipeline.AssetPipelineOptions.libraryRoot { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L34) | Gets the root folder containing rebuildable project data. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetPipelineOptions.enableFileSystemWatcher { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L39) | Gets whether file-system watching is enabled. | — | — | +| [`public int Inno.Assets.Pipeline.AssetPipelineOptions.fileWatcherFlushDelayMs { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L44) | Gets the watcher change coalescing delay in milliseconds. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourcePolicy? Inno.Assets.Pipeline.AssetPipelineOptions.sourcePolicy { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L49) | Gets the source filtering policy. | — | — | +| [`public System.Collections.Generic.IReadOnlyList? Inno.Assets.Pipeline.AssetPipelineOptions.sourceMounts { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L54) | Gets the complete source mount snapshot, or null to mount only . | — | — | +| [`public Inno.Assets.Pipeline.AssetCacheOptions Inno.Assets.Pipeline.AssetPipelineOptions.cacheOptions { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L59) | Gets the rebuildable cache policy. | — | — | +| [`public static Inno.Assets.Pipeline.AssetPipelineOptions Inno.Assets.Pipeline.AssetPipelineOptions.Create(string assetRoot, string libraryRoot)`](../../src/content/assets/Inno.Assets.Pipeline/AssetPipelineOptions.cs#L73) | Creates options with sensible defaults for most projects.
`assetRoot`: Source assets root folder.
`libraryRoot`: Rebuildable Library folder. | Initialized options value. | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSourceMountTransaction : System.IDisposable, Inno.Assets.Pipeline.IAssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L15) | Holds an isolated source-mount candidate that can be inspected before it atomically replaces the active Asset Database. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.sourceMounts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L65) | Gets the complete isolated mount snapshot represented by this candidate. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.recoveryChanges { get; }`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L71) | Gets neutral canonical reference outcomes after activation, or an empty set before activation or after rollback. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetSourceMountTransaction.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L83) | Gets candidate source entries without publishing them to active AssetPipeline consumers.
`includeDirectories`: Whether directory entries should be included. | A stable candidate entry snapshot. | — | +| [`public TAsset Inno.Assets.Pipeline.AssetSourceMountTransaction.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L105) | Loads one candidate asset by isolated source path.
`path`: Candidate source path.
`TAsset`: Required asset type. | The candidate asset instance. | `System.InvalidOperationException`: Thrown when no compatible candidate asset exists. | +| [`public bool Inno.Assets.Pipeline.AssetSourceMountTransaction.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L126) | Tries to get candidate catalog information by isolated source path.
`path`: Candidate source path.
`info`: Candidate catalog information when available. | True when the candidate path is cataloged. | — | +| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetSourceMountTransaction.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L136) | Retains one verified output so cache collection cannot remove an active generation.
`persistentId`: The persistent asset identity.
`outputName`: The required output protocol. | A caller-owned immutable artifact lease. | `System.InvalidOperationException`: The requested output is unavailable or invalid. | +| [`public bool Inno.Assets.Pipeline.AssetSourceMountTransaction.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L160) | Tries to resolve one named candidate artifact.
`persistentId`: Candidate asset identity.
`outputName`: Named artifact output.
`artifact`: Artifact information when available. | True when the candidate output exists. | — | +| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Activate()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L173) | Activates this candidate without releasing the previous generation or notifying observers. | — | — | +| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Complete()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L197) | Commits an activated candidate, notifies observers, and retires the previous generation. | — | `Inno.Core.Execution.RetirementPendingException`: Retirement exceeded its deadline. Both generations remain owned and the host must restart.
`System.AggregateException`: Publication or completed retirement failed; the host generation gate is faulted. | +| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Rollback()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L223) | Discards the candidate or restores the previous generation after provisional activation. | — | `Inno.Core.Execution.RetirementPendingException`: Candidate retirement exceeded its deadline; its owners remain retained until host restart.
`System.AggregateException`: Compensation or completed retirement failed. | +| [`public void Inno.Assets.Pipeline.AssetSourceMountTransaction.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/AssetSourceMountTransaction.cs#L249) | Discards an unfinished candidate and releases its rebuildable staging storage. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSourceStore`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L12) | Reads detached authoring bytes and saves them independently of successful import. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourceSnapshot Inno.Assets.Pipeline.AssetSourceStore.Read(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L34) | Reads a mounted source without modifying its canonical asset or compiled artifacts.
`path`: Exact mounted source identity. | A detached source and optimistic concurrency fingerprint. | — | +| [`public byte[] Inno.Assets.Pipeline.AssetSourceStore.Encode(TAsset asset) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L53) | Encodes native asset properties with the owner's reference context and dependency capture.
`asset`: Detached editable value.
`TAsset`: Native asset source type. | Native source bytes, with no canonical asset mutation. | — | +| [`public TAsset Inno.Assets.Pipeline.AssetSourceStore.Decode(System.ReadOnlySpan bytes) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L67) | Restores a detached native value using current-generation asset references.
`bytes`: Native source bytes.
`TAsset`: Native asset source type. | A detached value; referenced assets remain canonical read-only inputs. | — | +| [`public string Inno.Assets.Pipeline.AssetSourceStore.Save(Inno.Assets.AssetPath path, byte[] bytes, string? expectedHash)`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L85) | Atomically saves bytes after checking the source fingerprint; import is a separate operation.
`path`: Writable mounted destination.
`bytes`: Complete serializable source, including invalid authoring states.
`expectedHash`: Last read/save fingerprint; null requires a nonexistent destination. | The saved fingerprint. | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSourceSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L146) | Contains detached authoring bytes and conflict-detection state. | — | — | +| [`public byte[] Inno.Assets.Pipeline.AssetSourceSnapshot.bytes { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L161) | Gets a copy of the captured native source bytes. | — | — | +| [`public string Inno.Assets.Pipeline.AssetSourceSnapshot.contentHash { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L165) | Gets the source fingerprint required by a subsequent save. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSourceSnapshot.isReadOnly { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Editing/AssetSourceStore.cs#L169) | Gets whether the installation source cannot be edited in place. | — | — | +| [`public static class Inno.Assets.Pipeline.EditorAssets`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L11) | Provides Editor-script asset mutations through the authoring pipeline bound by the current host. | — | — | +| [`public static byte[] Inno.Assets.Pipeline.EditorAssets.EncodeNative(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L22) | Encodes a detached native asset with the current authoring pipeline's converters and reference context.
`asset`: Complete detached value to encode without saving or importing it. | Reload-safe native asset bytes suitable for draft state and History payloads. | — | +| [`public static TAsset Inno.Assets.Pipeline.EditorAssets.DecodeNative(byte[] bytes) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L43) | Decodes detached native asset bytes with the current authoring pipeline's converters and reference context.
`bytes`: Bytes previously produced by the native asset source codec.
`TAsset`: Expected native asset type. | A detached asset value that can be edited without publishing it. | — | +| [`public static Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.EditorAssets.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L64) | Captures reload-safe settings using the currently bound authoring owner's converters and references.
`value`: Settings to capture without saving or mutating their referenced assets.
`TValue`: Current serializable settings type. | Native properties, stable type identity and automatically collected dependencies. | — | +| [`public static bool Inno.Assets.Pipeline.EditorAssets.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/EditorAssets.cs#L88) | Creates or replaces a writable project asset source and imports the committed result.
`path`: The writable project asset path.
`asset`: The complete source value to persist. Saving over an existing source retains that source's persistent identity. | when the source was committed and imported successfully. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the current execution context is not bound to an authoring asset pipeline. | +| [`public sealed class Inno.Assets.Pipeline.AssetArtifactWriter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | Collects immutable named outputs for an aggregate asset build. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetArtifactWriter.WriteAsync(string outputName, System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope = Inno.Assets.Pipeline.AssetDeploymentScope.Runtime)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L36) | Writes one named build output.
`outputName`: The stable output name.
`bytes`: The output content.
`cancellationToken`: Cancellation for the write operation.
`deploymentScope`: Whether runtime packages retain this output. | A completed operation after the output has been staged. | — | +| [`public void Inno.Assets.Pipeline.AssetArtifactWriter.ReportDiagnostic(string message)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L60) | Adds a build diagnostic.
`message`: The diagnostic message. | — | — | +| [`public Inno.Assets.Pipeline.AssetArtifactWriter.AssetArtifactWriter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetCatalogCandidate : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L14) | Owns the isolated catalog storage used to validate one candidate Asset Pipeline generation. | — | — | +| [`public Inno.Assets.Pipeline.AssetLoader Inno.Assets.Pipeline.AssetCatalogCandidate.loader { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L34) | Gets the isolated loader whose catalog and in-memory records represent the candidate generation. | — | — | +| [`public void Inno.Assets.Pipeline.AssetCatalogCandidate.Commit()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L48) | Publishes staged source metadata and atomically promotes the validated catalog, compensating metadata on failure. | — | `System.InvalidOperationException`: Thrown when the candidate has already been committed or disposed.
`System.IO.IOException`: Source metadata changed externally, a sidecar cannot be written, or the catalog cannot be promoted.
`System.UnauthorizedAccessException`: The operating system rejects a source sidecar or catalog write, including a directory at a file destination. | +| [`public void Inno.Assets.Pipeline.AssetCatalogCandidate.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetCatalogCandidate.cs#L60) | Removes the candidate catalog staging storage without disposing the candidate loader. | — | — | +| [`public enum Inno.Assets.Pipeline.AssetDeploymentScope : int`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L6) | Defines whether an imported asset participates in deployed runtime content. | — | — | +| [`Inno.Assets.Pipeline.AssetDeploymentScope.Runtime = 0`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L11) | The asset is deployed and must produce a named runtime artifact output. | — | — | +| [`Inno.Assets.Pipeline.AssetDeploymentScope.AuthoringOnly = 1`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetDeploymentScope.cs#L16) | The asset exists only for authoring workflows and is omitted from deployed catalogs. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetExportContext`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L12) | Provides generation-bound services for one editable asset source export. | — | — | +| [`public Inno.Extensibility.Types.TypeCatalog Inno.Assets.Pipeline.AssetExportContext.types { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L29) | Gets the type catalog bound to the active export generation. | — | — | +| [`public Inno.Core.Serialization.SerializationRegistry Inno.Assets.Pipeline.AssetExportContext.serialization { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L35) | Gets the serialization registry bound to the active export generation. | — | — | +| [`public Inno.Assets.Pipeline.AssetSerializationServices Inno.Assets.Pipeline.AssetExportContext.services { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L40) | Gets the narrow structured serialization API bound to this export generation. | — | — | +| [`public Inno.Assets.IAssetArtifactLookup Inno.Assets.Pipeline.AssetExportContext.artifacts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetExportContext.cs#L46) | Gets the owner-bound immutable outputs used to reconstruct editable sources. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImportContext`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L16) | Collects source data and dependency declarations for one import operation. | — | — | +| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetImportContext.AcquireArtifact(System.Guid assetId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L106) | Acquires an immutable dependency output and records its invalidation dependency automatically.
`assetId`: Persistent dependency identity.
`outputName`: Stable named output protocol. | A caller-owned lease that must be disposed after reading. | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetImportContext.assetPath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L117) | Gets the isolated source path. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportContext.absolutePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L122) | Gets the absolute source path. | — | — | +| [`public System.ReadOnlyMemory Inno.Assets.Pipeline.AssetImportContext.sourceBytes { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L127) | Gets the raw source bytes. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportContext.sourceHash { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L132) | Gets the deterministic source hash. | — | — | +| [`public System.Guid Inno.Assets.Pipeline.AssetImportContext.persistentId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L137) | Gets the persistent identity assigned to the source asset. | — | — | +| [`public Inno.Extensibility.Types.TypeCatalog Inno.Assets.Pipeline.AssetImportContext.types { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L143) | Gets the type catalog bound to the active import generation. | — | — | +| [`public Inno.Core.Serialization.SerializationRegistry Inno.Assets.Pipeline.AssetImportContext.serialization { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L149) | Gets the serialization registry bound to the active import generation. | — | — | +| [`public Inno.Assets.IAssetReferenceResolver Inno.Assets.Pipeline.AssetImportContext.references { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L155) | Gets the asset-reference resolver bound to the isolated candidate generation. | — | — | +| [`public Inno.Assets.Pipeline.AssetSerializationServices Inno.Assets.Pipeline.AssetImportContext.services { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L160) | Gets the narrow structured serialization API bound to this importer candidate. | — | — | +| [`public Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImportContext.importSettings { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L166) | Gets the settings restored from this source's sidecar against the isolated import generation. The value is detached and changes made during import are not saved to the sidecar. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportContext.extension { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L171) | Gets the normalized lower-case source extension. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportContext.ReadUtf8Text()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L179) | Reads the source bytes as UTF-8 text. | The decoded text without an optional byte-order mark. | — | +| [`public System.ReadOnlyMemory Inno.Assets.Pipeline.AssetImportContext.ReadSourceBytes(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L199) | Reads another source from the same candidate mount snapshot and records it as an import dependency.
`path`: An isolated path whose mount dependency was declared when it crosses Plugin boundaries. | An immutable source snapshot owned by the import operation. | `System.InvalidOperationException`: Thrown when the source crosses an undeclared mount boundary or references the writable Project from a Plugin.
`System.IO.FileNotFoundException`: Thrown when the resolved source does not exist. | +| [`public string Inno.Assets.Pipeline.AssetImportContext.ReadSourceUtf8Text(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L216) | Reads another source as UTF-8 from the current candidate mount snapshot and records the dependency.
`path`: An isolated source path. | Decoded UTF-8 text without an optional byte-order mark. | — | +| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnAsset(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L230) | Declares a direct runtime dependency by isolated source path.
`path`: The isolated dependency path. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnAsset(Inno.Assets.AssetDependency dependency)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L243) | Declares a direct runtime dependency by persistent descriptor.
`dependency`: The persistent dependency descriptor. | — | — | +| [`public TAsset Inno.Assets.Pipeline.AssetImportContext.ResolveDependency(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L263) | Resolves and declares a strongly typed runtime asset dependency during import.
`path`: Isolated dependency path.
`TAsset`: Expected asset type. | The currently committed dependency asset. | `System.InvalidOperationException`: Thrown when the dependency cannot be imported or has another type. | +| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnSource(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L285) | Declares a source file that invalidates this imported asset.
`path`: The isolated dependency path. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnArtifact(System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L301) | Declares an imported artifact that invalidates this imported asset.
`persistentId`: The persistent identity of the artifact owner. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportContext.DependsOnCustomInput(string key, string fingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportContext.cs#L320) | Declares a custom deterministic input that invalidates this asset.
`key`: The input identifier.
`fingerprint`: The current deterministic input fingerprint. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImporterAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L15) | Declares the immutable protocol identity of an automatically discovered asset importer. | — | — | +| [`public Inno.Assets.Pipeline.AssetImporterAttribute.AssetImporterAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L23) | Creates importer discovery metadata.
`id`: Globally stable importer protocol identifier. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImporterAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L32) | Gets the globally stable importer protocol identifier. | — | — | +| [`public abstract class Inno.Assets.Pipeline.AssetImporter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L38) | Defines metadata shared by automatically discovered asset importers. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImporter.importerId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L45) | Gets the stable importer implementation identifier. | — | — | +| [`public virtual Inno.Assets.Pipeline.AssetDeploymentScope Inno.Assets.Pipeline.AssetImporter.deploymentScope { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L52) | Gets whether imported assets are deployed or retained only for authoring workflows. | — | — | +| [`public abstract System.Type Inno.Assets.Pipeline.AssetImporter.targetAssetType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L57) | Gets the concrete asset type produced by this importer. | — | — | +| [`public abstract System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetImporter.supportedExtensions { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L62) | Gets the normalized source extensions accepted by this importer. | — | — | +| [`public virtual Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImporter.CreateImportSettings()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L71) | Creates a detached settings value for this importer in the current extension generation. | A fresh serializable value with a registered stable type identity, or null when this importer has no settings. Defaults must be deterministic; implementations must not retain the returned instance. | — | +| [`protected Inno.Assets.Pipeline.AssetImporter.AssetImporter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L38) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Assets.Pipeline.AssetImporter where TAsset : Inno.Assets.AssetObject : Inno.Assets.Pipeline.AssetImporter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L101) | Provides the strongly typed implementation base for an asset importer.
`TAsset`: The concrete imported asset type. | — | — | +| [`public override sealed System.Type Inno.Assets.Pipeline.AssetImporter.targetAssetType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L106) | Gets the runtime asset type accepted by this importer implementation. | — | — | +| [`protected abstract System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetImporter.ImportAsync(Inno.Assets.Pipeline.AssetImportContext context, Inno.Assets.Pipeline.AssetImportWriter output, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L123) | Imports one source into a managed asset and named artifact outputs.
`context`: The import transaction context.
`output`: The candidate output writer.
`cancellationToken`: Cancellation for the import. | An operation that completes when the candidate has been fully staged. | — | +| [`protected virtual System.Threading.Tasks.ValueTask?> Inno.Assets.Pipeline.AssetImporter.ExportAsync(Inno.Assets.Pipeline.AssetExportContext context, TAsset asset, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L144) | Exports an asset back into source bytes.
`context`: The generation-bound source export services.
`asset`: The asset to export.
`cancellationToken`: Cancellation for the export. | The source bytes, or when export is unsupported. | — | +| [`protected Inno.Assets.Pipeline.AssetImporter.AssetImporter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImporter.cs#L101) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImportExtensionUnavailableException : System.InvalidOperationException`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L11) | Reports a required authoring extension absent from the current generation. | — | — | +| [`public Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.AssetImportExtensionUnavailableException(string extensionKind, string extensionId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L22) | Describes a missing extension using generation-neutral identities.
`extensionKind`: The stable extension protocol identity.
`extensionId`: The required implementation's stable identity. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.extensionKind { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L37) | Gets the stable extension protocol identity. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportExtensionUnavailableException.extensionId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportExtensionUnavailableException.cs#L42) | Gets the required implementation's stable identity. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImportHealthSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L14) | Represents an immutable observation of writable-source import failures at one point in time. | — | — | +| [`public static Inno.Assets.Pipeline.AssetImportHealthSnapshot Inno.Assets.Pipeline.AssetImportHealthSnapshot.empty { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L32) | Gets the immutable snapshot used when no Asset Pipeline generation is active. | — | — | +| [`public readonly record struct Inno.Assets.Pipeline.AssetImportFailure : System.IEquatable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Describes a writable-source import failure introduced after an earlier health snapshot.
`assetPath`: The isolated path of the source that failed to import.
`diagnostics`: The deterministic importer diagnostics associated with the failure. | — | — | +| [`public Inno.Assets.Pipeline.AssetImportFailure.AssetImportFailure(string assetPath, string diagnostics)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Describes a writable-source import failure introduced after an earlier health snapshot.
`assetPath`: The isolated path of the source that failed to import.
`diagnostics`: The deterministic importer diagnostics associated with the failure. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportFailure.assetPath { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L45) | The isolated path of the source that failed to import. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportFailure.diagnostics { get; init; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L46) | The deterministic importer diagnostics associated with the failure. | — | — | +| [`public override string Inno.Assets.Pipeline.AssetImportFailure.ToString()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Assets.Pipeline.AssetImportFailure.operator !=(Inno.Assets.Pipeline.AssetImportFailure left, Inno.Assets.Pipeline.AssetImportFailure right)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Assets.Pipeline.AssetImportFailure.operator ==(Inno.Assets.Pipeline.AssetImportFailure left, Inno.Assets.Pipeline.AssetImportFailure right)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Assets.Pipeline.AssetImportFailure.GetHashCode()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Assets.Pipeline.AssetImportFailure.Equals(object obj)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Assets.Pipeline.AssetImportFailure.Equals(Inno.Assets.Pipeline.AssetImportFailure other)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Assets.Pipeline.AssetImportFailure.Deconstruct(out string assetPath, out string diagnostics)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportHealthSnapshot.cs#L44) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImportSettingsSnapshot`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L12) | Provides a detached settings value and an optimistic concurrency token for its source sidecar. | — | — | +| [`public Inno.Core.Serialization.ISerializable? Inno.Assets.Pipeline.AssetImportSettingsSnapshot.value { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L25) | Gets the editable settings copy, or null for an importer without settings. | — | — | +| [`public string Inno.Assets.Pipeline.AssetImportSettingsSnapshot.fingerprint { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetImportSettingsSnapshot.cs#L30) | Gets the source metadata fingerprint required when saving this copy. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetLoader : System.IDisposable, Inno.Assets.IAssetReferenceResolver, Inno.Assets.IAssetArtifactLookup, Inno.Assets.IAssetPropertyStateResolver`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L27) | Coordinates importing, persistent cataloging, canonical loading, reloading and collection for one source and artifact root pair. | — | — | +| [`public Inno.Assets.Pipeline.AssetCatalogCandidate Inno.Assets.Pipeline.AssetLoader.PrepareCatalogCandidate(System.Collections.Generic.IReadOnlyList mounts, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L47) | Creates an isolated source-mount and catalog candidate without changing the active catalog.
`mounts`: The complete source-mount snapshot to validate.
`sourcePolicy`: The source policy for the candidate, or to reuse this loader's policy. | A candidate that owns its catalog staging storage and exposes the isolated loader to its owner. | `System.ArgumentException`: Thrown when the mount snapshot is empty, duplicated, or has no writable project source.
`System.IO.IOException`: Thrown when the active catalog cannot be copied into candidate storage. | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L114) | Tries to get a catalog snapshot by isolated source path.
`path`: The isolated source path.
`info`: The immutable catalog snapshot when found. | when the source is cataloged. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetInfo(System.Guid persistentId, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L140) | Tries to get a catalog snapshot by persistent identity.
`persistentId`: The stable persistent identity used for lookup.
`info`: The resolved immutable metadata returned to the caller. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetPersistentId(Inno.Assets.AssetPath path, out System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L163) | Tries to resolve a persistent identity without loading the asset.
`path`: The isolated source path.
`persistentId`: The resolved identity. | when catalog metadata exists. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetAssetType(Inno.Assets.AssetPath path, out System.Type? assetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L189) | Tries to resolve the concrete asset type without loading it.
`path`: The isolated source path.
`assetType`: The resolved type. | when the type can be resolved. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetLoadedPaths()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Catalog.cs#L209) | Gets isolated source paths of all canonical loaded assets. | A stable isolated path snapshot. | — | +| [`public void Inno.Assets.Pipeline.AssetLoader.RestoreProperties(System.Guid stableTypeId, byte[] propertyData, TValue target) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L101) | Restores serialized properties to the existing asset object.
`stableTypeId`: The stable type id consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`propertyData`: The property data consumed by restore properties; ownership remains with the caller unless explicitly stated otherwise.
`target`: The existing target that receives the validated result.
`TValue`: Serialized asset object type receiving restored properties. | — | — | +| [`public Inno.Assets.Pipeline.AssetLoader.AssetLoader(Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, string assetRoot, string libraryRoot, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L145) | Creates an asset loader for one source and Library root pair.
`types`: The immutable type-generation owner used to discover importers and resolve asset types.
`serialization`: The serialization registry used for catalog, metadata, and asset state persistence.
`identities`: The identity allocator that owns every canonical asset loaded by this instance.
`logs`: The explicitly owned router receiving importer and catalog diagnostics.
`diagnostics`: The diagnostic hub that owns import, build, catalog, and reference reports.
`assetRoot`: The absolute source root.
`libraryRoot`: The absolute rebuildable Library root.
`sourcePolicy`: The source filtering policy, or for defaults. | — | — | +| [`public Inno.Assets.Pipeline.AssetLoader.AssetLoader(Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Identity.IdentityAllocator identities, Inno.Core.Diagnostics.DiagnosticHub diagnostics, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IReadOnlyList mounts, string libraryRoot, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null, bool runtimeArtifactsOnly = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L197) | Creates an asset loader over one project source and zero or more isolated sources.
`types`: The immutable type-generation owner used to discover importers and resolve asset types.
`serialization`: The serialization registry used for catalog, metadata, and asset state persistence.
`identities`: The identity allocator that owns every canonical asset loaded by this instance.
`logs`: The explicitly owned router receiving importer and catalog diagnostics.
`diagnostics`: The diagnostic hub that owns import, build, catalog, and reference reports.
`mounts`: Complete isolated source mount snapshot.
`libraryRoot`: Absolute rebuildable Library root.
`sourcePolicy`: Source filtering policy, or for defaults.
`runtimeArtifactsOnly`: Whether to trust a deployed read-only catalog and skip every source reconciliation operation. | — | — | +| [`public string Inno.Assets.Pipeline.AssetLoader.assetRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L293) | Gets the absolute source root. | — | — | +| [`public string Inno.Assets.Pipeline.AssetLoader.libraryRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L298) | Gets the absolute rebuildable Library root. | — | — | +| [`public string Inno.Assets.Pipeline.AssetLoader.artifactRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L303) | Gets the derived content-addressed artifact root. | — | — | +| [`public System.Action? Inno.Assets.Pipeline.AssetLoader.AssetReloaded`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L308) | Occurs after a loaded canonical asset is updated in place. | — | — | +| [`public void Inno.Assets.Pipeline.AssetLoader.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L336) | Waits for pending import and build work. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.RefreshRegistries()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.cs#L344) | Refreshes extension registries and reimports affected sources when their snapshot changed. | when the importer registry changed. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L41) | Gets direct or transitive runtime dependencies of an asset.
`asset`: The asset to query.
`recursive`: Whether transitive dependencies should be included. | The persistent dependency descriptors. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.GetImportDependencies(Inno.Assets.AssetObject asset, bool recursive = false)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L61) | Gets source import dependencies that invalidate an asset artifact.
`asset`: The asset to query.
`recursive`: Whether transitive source dependencies should be included. | Canonical isolated source paths in stable order. | — | +| [`public Inno.Assets.AssetReferenceInfo Inno.Assets.Pipeline.AssetLoader.GetReferenceInfo(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs#L86) | Gets an engine-known reference diagnostic snapshot.
`asset`: The asset to inspect. | The reference diagnostic snapshot. | — | +| [`public Inno.Assets.Pipeline.AssetImportHealthSnapshot Inno.Assets.Pipeline.AssetLoader.CaptureWritableImportHealth()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Diagnostics.cs#L35) | Captures the current failure identity of every writable source without exposing catalog internals. | An immutable health snapshot suitable for validating a later candidate generation. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetLoader.FindIntroducedImportFailures(Inno.Assets.Pipeline.AssetImportHealthSnapshot baseline)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Diagnostics.cs#L59) | Finds writable-source failures that are new or changed relative to an earlier health snapshot.
`baseline`: The health snapshot captured before the candidate generation was activated. | A deterministic path-ordered collection containing only introduced or changed failures. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.Import(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Importing.cs#L38) | Imports one isolated source file into metadata and a runtime artifact.
`path`: The isolated source path. | when an importer handled the source. | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.BuildAsync(Inno.Assets.AssetObject definition, System.Collections.Generic.IReadOnlyList inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Importing.cs#L55) | Builds and validates the requested artifact asynchronously before publishing it.
`definition`: The build definition asset.
`inputs`: The immutable input catalog snapshots.
`cancellationToken`: Cancellation for the candidate build. | The content-addressed output bundle key. | — | +| [`public Inno.Assets.Pipeline.AssetImportSettingsSnapshot Inno.Assets.Pipeline.AssetLoader.GetImportSettings(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.ImportSettings.cs#L31) | Reads a detached importer settings value without changing source metadata.
`path`: The isolated source path, not its sidecar path. | The current settings and the metadata fingerprint required for a subsequent save. | `System.NotSupportedException`: The source has no registered importer.
`System.IO.InvalidDataException`: The persisted settings cannot be restored by the current importer. | +| [`public bool Inno.Assets.Pipeline.AssetLoader.SaveImportSettings(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable? settings, string expectedFingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.ImportSettings.cs#L68) | Atomically saves importer settings and reimports the source. Failed imports retain both the saved settings and the previous successful artifact; callers must inspect the returned import status separately from saving.
`path`: The writable isolated source path.
`settings`: A value of the current importer's settings type, or null to reset to defaults.
`expectedFingerprint`: The fingerprint returned when the settings were read. | True when reimport succeeds; false when settings were saved but reimport failed. | `System.IO.IOException`: The sidecar changed externally or could not be written.
`System.InvalidOperationException`: The source is read-only or belongs to an isolated candidate.
`System.ArgumentException`: The settings have a different type from the current importer. | +| [`public Inno.Assets.AssetObject? Inno.Assets.Pipeline.AssetLoader.Load(Inno.Assets.AssetPath path, System.Type requestedAssetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L46) | Loads a canonical asset by isolated source path.
`path`: The isolated source path.
`requestedAssetType`: The required assignable asset type. | The canonical asset, including retained last-good content after a diagnosed import failure, or when unavailable or incompatible. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryLoad(Inno.Assets.AssetPath path, System.Type requestedAssetType, out Inno.Assets.AssetObject? asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L69) | Tries to load a canonical asset by isolated source path.
`path`: The isolated source path.
`requestedAssetType`: The required assignable asset type.
`asset`: The canonical asset when successful. | when a compatible asset was loaded. | — | +| [`public Inno.Assets.AssetObject? Inno.Assets.Pipeline.AssetLoader.Load(System.Guid persistentId, System.Type requestedAssetType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L90) | Loads a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`requestedAssetType`: The required assignable asset type. | The canonical asset, or when unavailable or incompatible. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryLoad(System.Guid persistentId, System.Type requestedAssetType, out Inno.Assets.AssetObject? asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L113) | Tries to load a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`requestedAssetType`: The required assignable asset type.
`asset`: The canonical asset when successful. | when a compatible asset was loaded. | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.LoadAsync(Inno.Assets.AssetPath path, System.Type requestedAssetType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L137) | Asynchronously loads a canonical asset by isolated source path.
`path`: The isolated source path.
`requestedAssetType`: The required assignable asset type.
`cancellationToken`: Cancellation for the current caller's wait. | The canonical asset, or when unavailable or incompatible. | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetLoader.LoadAsync(System.Guid persistentId, System.Type requestedAssetType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L177) | Asynchronously loads a canonical asset by persistent identity.
`persistentId`: The persistent asset identity.
`requestedAssetType`: The required assignable asset type.
`cancellationToken`: Cancellation for the current caller's wait. | The canonical asset, or when unavailable or incompatible. | — | +| [`public Inno.Assets.AssetObject Inno.Assets.Pipeline.AssetLoader.ResolveReference(System.Guid persistentId, System.Guid stableTypeId, string lastKnownPath, System.Type expectedType)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs#L219) | Resolves a serialized reference or creates a persistent missing placeholder.
`persistentId`: The referenced persistent identity.
`stableTypeId`: The referenced stable asset type identity.
`lastKnownPath`: The last known source-relative path.
`expectedType`: The declared destination type. | A compatible canonical asset or missing placeholder. | — | +| [`public int Inno.Assets.Pipeline.AssetLoader.CollectArtifacts(System.TimeSpan gracePeriod, long maximumSizeBytes)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L41) | Collects unreachable content-addressed artifacts.
`gracePeriod`: The minimum age of an unreachable bundle.
`maximumSizeBytes`: The cache size limit, or zero for no limit. | The number of removed artifact bundles. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.TryGetArtifact(System.Guid persistentId, string outputName, out Inno.Assets.AssetArtifactInfo? artifact)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L74) | Tries to resolve a named output from the current artifact bundle.
`persistentId`: The stable persistent identity used for lookup.
`outputName`: The stable artifact output name used for lookup.
`artifact`: The resolved immutable artifact payload returned to the caller. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`public Inno.Assets.ArtifactLease Inno.Assets.Pipeline.AssetLoader.AcquireArtifact(System.Guid persistentId, string outputName)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L109) | Acquires an immutable output while excluding concurrent artifact collection.
`persistentId`: The source asset's persistent identity.
`outputName`: The required output protocol. | A lease retaining this exact artifact key, even after reimport or source deletion. | `System.InvalidOperationException`: No current output can be resolved. | +| [`public int Inno.Assets.Pipeline.AssetLoader.UnloadUnusedAssets()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Residency.cs#L126) | Collects canonical assets that have no external managed references. | The number of released canonical assets. | — | +| [`public void Inno.Assets.Pipeline.AssetLoader.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Retirement.cs#L38) | Stops new loads and releases canonical assets before their registries and diagnostics. | — | `Inno.Core.Execution.RetirementPendingException`: A load, serialized operation or unload hook is still active. Retain the loader and retry after quiescence.
`System.AggregateException`: Completed releases failed; all quiescent resources have still been attempted. | +| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetLoader.ExportRuntimeArtifacts(string destinationLibraryRoot)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.RuntimeExport.cs#L45) | Writes a source-free runtime catalog and its exact immutable artifact closure.
`destinationLibraryRoot`: Empty destination that becomes the deployed content root. | Counts and source identities for the exported runtime snapshot. | `System.InvalidOperationException`: Thrown when runtime artifacts are incomplete, a deployed reference is authoring-only, or a transitive authoring input has failed or become stale.
`System.IO.IOException`: Thrown when the destination is not empty or content cannot be copied. | +| [`public Inno.Assets.AssetRuntimeContentInfo Inno.Assets.Pipeline.AssetLoader.ExportRuntimeArtifacts(string destinationLibraryRoot, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.RuntimeExport.cs#L66) | Exports the validated runtime artifact closure while observing cooperative cancellation between files.
`destinationLibraryRoot`: The empty directory that receives the runtime-only asset database.
`cancellationToken`: The token checked before every artifact file is copied. | Counts and source identities represented by the exported snapshot. | `System.InvalidOperationException`: Thrown when artifacts are incomplete or transitive source inputs no longer certify the imported snapshot.
`System.OperationCanceledException`: Thrown when cancellation is requested before the snapshot finishes. | +| [`public bool Inno.Assets.Pipeline.AssetLoader.Save(Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Saving.cs#L38) | Saves an asset back to its current source path.
`asset`: The asset to export. | when an importer exported the asset. | — | +| [`public bool Inno.Assets.Pipeline.AssetLoader.Save(Inno.Assets.AssetPath path, Inno.Assets.AssetObject asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Saving.cs#L58) | Saves an asset to its initial or existing isolated source path while preserving an existing destination identity.
`path`: The isolated source path.
`asset`: The asset to export. Detached replacement values do not replace an existing source's persistent identity. | when an importer exported the asset. | — | +| [`public void Inno.Assets.Pipeline.AssetLoader.Rescan(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceChanges.cs#L38) | Reconciles source files, metadata, artifacts and the in-memory catalog.
`cancellationToken`: Cancellation for reconciliation and importer work; unpublished candidates can be discarded after it drains. | — | `System.OperationCanceledException`: Reconciliation or an importer observes cancellation. | +| [`public void Inno.Assets.Pipeline.AssetLoader.ApplySourceChanges(System.Collections.Generic.IReadOnlyList changes)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceChanges.cs#L60) | Applies normalized source file changes to the persistent catalog.
`changes`: The normalized source changes. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSerializationServices`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L12) | Provides generation-bound structured serialization without exposing host registries to importer extensions. | — | — | +| [`public System.Guid Inno.Assets.Pipeline.AssetSerializationServices.GetStableTypeId()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L55) | Resolves the stable persistent type identity of a registered serializable type.
`TValue`: The registered type whose stable identity is required. | The stable type identifier owned by the active extension generation. | `System.InvalidOperationException`: Thrown when has no stable type registration. | +| [`public Inno.Assets.AssetPropertySnapshot Inno.Assets.Pipeline.AssetSerializationServices.CaptureProperties(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L69) | Captures typed properties and their references as one neutral, owner-independent value.
`value`: Current generation settings object, never retained.
`TValue`: Current serializable settings contract. | Frozen native properties with exact stable type identity and collected direct dependencies. | — | +| [`public byte[] Inno.Assets.Pipeline.AssetSerializationServices.Serialize(TValue value) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L96) | Serializes one structured value and declares every encountered asset reference as a runtime dependency.
`value`: The value to serialize through the active converter generation.
`TValue`: The serializable value contract. | Deterministic structured bytes owned by the caller. | `System.ArgumentNullException`: Thrown when is . | +| [`public TValue Inno.Assets.Pipeline.AssetSerializationServices.Deserialize(System.ReadOnlySpan bytes) where TValue : class, Inno.Core.Serialization.ISerializable`](../../src/content/assets/Inno.Assets.Pipeline/Importing/AssetSerializationServices.cs#L126) | Deserializes one structured value against the active importer candidate generation.
`bytes`: Complete structured bytes produced by the common serialization system.
`TValue`: The serializable value contract. | A restored value owned by the caller. | `System.InvalidOperationException`: Thrown when the value contains an asset reference but no candidate resolver is available. | +| [`public sealed class Inno.Assets.Pipeline.AssetBuildContext where TDefinition : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L14) | Provides a stable input snapshot to an aggregate asset build.
`TDefinition`: The build definition asset type. | — | — | +| [`public Inno.Assets.Pipeline.AssetBuildContext.AssetBuildContext(TDefinition definition, System.Collections.Generic.IReadOnlyList inputs)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L25) | Creates a build context.
`definition`: The build definition.
`inputs`: The immutable input catalog snapshots. | — | — | +| [`public TDefinition Inno.Assets.Pipeline.AssetBuildContext.definition { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L36) | Gets the build definition. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetBuildContext.inputs { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildContext.cs#L41) | Gets the input catalog snapshots. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetBuildProcessorAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L14) | Declares the immutable cache protocol identity of an automatically discovered asset build processor. | — | — | +| [`public Inno.Assets.Pipeline.AssetBuildProcessorAttribute.AssetBuildProcessorAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L22) | Creates build-processor discovery metadata.
`id`: Globally stable build processor identifier. | — | — | +| [`public string Inno.Assets.Pipeline.AssetBuildProcessorAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L31) | Gets the globally stable build processor identifier. | — | — | +| [`public abstract class Inno.Assets.Pipeline.AssetBuildProcessor`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L37) | Defines an automatically discovered aggregate asset build processor. | — | — | +| [`public string Inno.Assets.Pipeline.AssetBuildProcessor.processorId { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L44) | Gets the stable processor identifier used by build cache keys. | — | — | +| [`public abstract System.Type Inno.Assets.Pipeline.AssetBuildProcessor.definitionType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L51) | Gets the definition type accepted by this processor. | — | — | +| [`protected Inno.Assets.Pipeline.AssetBuildProcessor.AssetBuildProcessor()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L37) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Assets.Pipeline.AssetBuildProcessor where TDefinition : Inno.Assets.AssetObject : Inno.Assets.Pipeline.AssetBuildProcessor`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L78) | Provides a strongly typed aggregate asset build processor.
`TDefinition`: The definition asset type. | — | — | +| [`public override sealed System.Type Inno.Assets.Pipeline.AssetBuildProcessor.definitionType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L84) | Gets the concrete type handled by this extension implementation. | — | — | +| [`protected abstract System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetBuildProcessor.BuildAsync(Inno.Assets.Pipeline.AssetBuildContext context, Inno.Assets.Pipeline.AssetArtifactWriter output, System.Threading.CancellationToken cancellationToken)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L101) | Builds immutable outputs from a consistent asset snapshot.
`context`: The build context.
`output`: The candidate output writer.
`cancellationToken`: Cancellation for the build. | An operation that completes when all outputs are staged. | — | +| [`protected Inno.Assets.Pipeline.AssetBuildProcessor.AssetBuildProcessor()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Building/AssetBuildProcessor.cs#L78) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetImportWriter where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L16) | Collects the complete candidate output of one source import.
`TAsset`: The imported asset type. | — | — | +| [`public TAsset? Inno.Assets.Pipeline.AssetImportWriter.asset { get; private set; }`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L33) | Gets the candidate asset assigned by the importer. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.SetDeploymentScope(Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L43) | Selects the deployment scope for this imported asset. This permits one importer to produce runtime assets and editor-only assets from different source documents without changing their asset type.
`deploymentScope`: The scope of the complete imported asset, including its generated state. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.SetAsset(TAsset asset)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L58) | Assigns the managed asset produced by the importer.
`asset`: The imported asset. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetImportWriter.WriteArtifactAsync(string outputName, System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope = Inno.Assets.Pipeline.AssetDeploymentScope.Runtime)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L84) | Writes one immutable named artifact output.
`outputName`: The stable output name.
`bytes`: The output content.
`cancellationToken`: Cancellation for the write operation.
`deploymentScope`: Whether this output is retained in runtime packages. Runtime payload and asset state cannot be authoring-only. | A completed operation after the output has been staged. | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnAsset(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L108) | Declares a runtime dependency by isolated source path.
`path`: The normalized file-system path used by this operation. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnAsset(Inno.Assets.AssetDependency dependency)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L116) | Declares a runtime dependency by persistent descriptor.
`dependency`: The dependency consumed by depends on asset; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnSource(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L124) | Declares a source input that invalidates this import.
`path`: The normalized file-system path used by this operation. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnArtifact(System.Guid persistentId)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L132) | Declares an asset artifact input that invalidates this import.
`persistentId`: The stable persistent identity used for lookup. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.DependsOnCustomInput(string key, string fingerprint)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L143) | Declares a custom deterministic import input.
`key`: The input identifier.
`fingerprint`: The deterministic input fingerprint. | — | — | +| [`public void Inno.Assets.Pipeline.AssetImportWriter.ReportDiagnostic(string message)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Importing/AssetImportWriter.cs#L154) | Adds a non-fatal import diagnostic.
`message`: The diagnostic message. | — | — | | [`public static class Inno.Assets.Pipeline.NativeAssetSourceSerialization`](../../src/content/assets/Inno.Assets.Pipeline/Serialization/NativeAssetSourceSerialization.cs#L13) | Imports and exports editable asset source state through the common native serializer. | — | — | +| [`public static byte[] Inno.Assets.Pipeline.NativeAssetSourceSerialization.Export(TAsset asset, Inno.Assets.Pipeline.AssetSerializationServices services) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Serialization/NativeAssetSourceSerialization.cs#L30) | Serializes one asset's editable properties and direct asset dependencies.
`asset`: Unsaved or loaded asset whose editable state should be captured.
`services`: The generation-bound serialization services supplied by the export context.
`TAsset`: Concrete asset source type. | Deterministic native source bytes. | — | +| [`public static TAsset Inno.Assets.Pipeline.NativeAssetSourceSerialization.Import(System.ReadOnlySpan bytes, Inno.Assets.Pipeline.AssetSerializationServices services, out System.Collections.Generic.IReadOnlyList dependencies) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Serialization/NativeAssetSourceSerialization.cs#L73) | Restores one concrete asset and its declared direct dependencies.
`bytes`: Native source bytes.
`dependencies`: Receives persistent dependencies discovered during export.
`services`: The generation-bound serialization and reference services supplied by the import context.
`TAsset`: Concrete asset source type. | A detached asset ready for an import transaction. | `System.InvalidOperationException`: Thrown when the source type identity is incompatible. | +| [`public readonly struct Inno.Assets.Pipeline.AssetChangedEvent`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L17) | Batched file-system change event for asset source files.
`relativePath`: Changed path relative to watched root.
`changeType`: File-system change type.
`oldRelativePath`: Old path for rename operations. | — | — | +| [`public Inno.Assets.Pipeline.AssetChangedEvent.AssetChangedEvent(string relativePath, System.IO.WatcherChangeTypes changeType, string oldRelativePath = "")`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L17) | Batched file-system change event for asset source files.
`relativePath`: Changed path relative to watched root.
`changeType`: File-system change type.
`oldRelativePath`: Old path for rename operations. | — | — | +| [`public string Inno.Assets.Pipeline.AssetChangedEvent.relativePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L25) | Changed path relative to watched root. | — | — | +| [`public System.IO.WatcherChangeTypes Inno.Assets.Pipeline.AssetChangedEvent.changeType { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L30) | Underlying file-system change type. | — | — | +| [`public string Inno.Assets.Pipeline.AssetChangedEvent.oldRelativePath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetChangedEvent.cs#L36) | Old path relative to watched root for rename operations. Empty for non-rename changes. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetFileEntry : Inno.Core.Identity.IdentityObject`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L11) | One node in the asset source filesystem index. | — | — | +| [`public string Inno.Assets.Pipeline.AssetFileEntry.name { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L16) | Gets the final source path segment, or the semantic mount-root label when this entry is a source root. | — | — | +| [`public string Inno.Assets.Pipeline.AssetFileEntry.nameWithoutExtension { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L25) | Gets the final source path segment without its last extension. | — | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetFileEntry.assetPath { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L30) | Gets the isolated source path. | — | — | +| [`public Inno.Assets.AssetSourceId Inno.Assets.Pipeline.AssetFileEntry.source { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L35) | Gets the owning source mount identity. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isReadOnly { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L40) | Gets whether source mutations are forbidden. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isSample { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L45) | Gets whether this entry is the root of an installed Plugin sample awaiting Project import. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isSampleContent { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L50) | Gets whether this entry belongs to an installed Plugin sample subtree awaiting Project import. | — | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetFileEntry.parentAssetPath { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L55) | Gets the isolated parent directory path. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetFileEntry.isDirectory { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L60) | Gets whether the entry represents a directory. | — | — | +| [`public string Inno.Assets.Pipeline.AssetFileEntry.extension { get; internal set; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L64) | Gets the normalized lower-case file extension. | — | — | +| [`public Inno.Assets.Pipeline.AssetFileEntry.AssetFileEntry()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileEntry.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetFileSystem : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L18) | Indexed asset source filesystem model backed by . | — | — | +| [`public string Inno.Assets.Pipeline.AssetFileSystem.assetRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L44) | Gets the absolute source asset root. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetFileSystem.isWatching { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L49) | Gets whether source file watching is active. | — | — | +| [`public Inno.Assets.Pipeline.AssetFileSystem.AssetFileSystem(string assetRoot, bool autoStart = true, int flushDelayMs = 80, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L66) | Creates an indexed source file system.
`assetRoot`: The absolute source root.
`autoStart`: Whether file watching should start immediately.
`flushDelayMs`: The watcher batch delay in milliseconds.
`sourcePolicy`: The source filtering policy, or for defaults. | — | — | +| [`public Inno.Assets.Pipeline.AssetFileSystem.AssetFileSystem(System.Collections.Generic.IReadOnlyList mounts, bool autoStart = true, int flushDelayMs = 80, Inno.Assets.Pipeline.AssetSourcePolicy? sourcePolicy = null, bool requireWritableProject = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L103) | Creates an indexed file system over isolated source mounts.
`mounts`: Complete source mount snapshot.
`autoStart`: Whether writable project source watching starts immediately.
`flushDelayMs`: Watcher batch delay in milliseconds.
`sourcePolicy`: Source filtering policy, or for defaults.
`requireWritableProject`: Whether the project mount must support authoring mutations. Runtime catalogs may set this to when every mounted identity directory is read-only. | — | — | +| [`public void Inno.Assets.Pipeline.AssetFileSystem.Start()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L175) | Starts source file watching. | — | — | +| [`public void Inno.Assets.Pipeline.AssetFileSystem.Stop()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L184) | Stops source file watching. | — | — | +| [`public void Inno.Assets.Pipeline.AssetFileSystem.Refresh()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L210) | Reconciles a complete source snapshot while preserving unchanged entry identities. | — | `System.ObjectDisposedException`: The source filesystem has been disposed.
`System.IO.IOException`: Source enumeration fails; the previous complete index and its identities remain active.
`System.UnauthorizedAccessException`: A source directory cannot be read; the previous complete index remains active.
`System.IO.InvalidDataException`: Multiple source paths claim the same persistent entry identity.
`System.InvalidOperationException`: A new entry identity cannot be registered; the previous index remains active. | +| [`public bool Inno.Assets.Pipeline.AssetFileSystem.Exists(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L267) | Determines whether an indexed source entry exists.
`path`: The isolated source path. | when the entry exists. | — | +| [`public bool Inno.Assets.Pipeline.AssetFileSystem.TryGetEntry(Inno.Assets.AssetPath path, out Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L289) | Tries to resolve an indexed source entry.
`path`: The isolated source path.
`entry`: The resolved entry when available. | when the entry exists. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.GetEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L318) | Gets a stable snapshot of indexed entries.
`includeDirectories`: Whether directory entries should be included. | The indexed entries. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.GetChildren(Inno.Assets.AssetPath parent)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L342) | Gets immediate children of an indexed directory.
`parent`: The isolated parent directory path. | The immediate child entries. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.PollChanges()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L365) | Polls normalized changes and refreshes the indexed source snapshot. | The changes observed since the previous poll. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.PollChanges(out bool requiresFullRescan)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L376) | Polls changes and reports whether watcher recovery requires a full rescan.
`requiresFullRescan`: Whether the watcher reported an unreliable event stream. | The changes observed since the previous poll. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.WaitForIdle()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L392) | Waits for a quiet watcher window, refreshes the index, and returns queued changes. | An immutable snapshot of the values selected by the operation. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.AssetFileSystem.WaitForIdle(out bool requiresFullRescan)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L403) | Waits for queued changes and reports whether a full rescan is required.
`requiresFullRescan`: Whether the watcher reported an unreliable event stream. | The normalized queued changes. | — | +| [`public void Inno.Assets.Pipeline.AssetFileSystem.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs#L423) | Releases the resources owned by this implementation. | — | — | +| [`public static class Inno.Assets.Pipeline.AssetSample`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L11) | Defines authoring sample directories and their installed Plugin behavior. | — | — | +| [`public const string Inno.Assets.Pipeline.AssetSample.fileType = ".isample"`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L16) | Gets the logical File Browser type used for a sample directory. | — | — | +| [`public static bool Inno.Assets.Pipeline.AssetSample.HasSampleDirectoryName(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L28) | Determines whether the final path segment uses the sample-directory naming convention.
`path`: The isolated source path to inspect. | when the final segment starts with ~, regardless of whether the path belongs to the writable Project source or an installed Plugin source. | — | +| [`public static bool Inno.Assets.Pipeline.AssetSample.IsRoot(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L46) | Determines whether the final segment identifies a sample directory.
`path`: The isolated source path to inspect. | when the path belongs to an installed Plugin source and its final segment starts with ~. A Project directory with the same name remains normal writable authoring content. | — | +| [`public static bool Inno.Assets.Pipeline.AssetSample.Contains(Inno.Assets.AssetPath path, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L64) | Determines whether a source path is a sample directory or is contained by one.
`path`: The isolated source path to inspect.
`isDirectory`: Whether the path itself represents a directory. | when the path belongs to an installed Plugin source and any directory segment starts with ~. Project content is never hidden from authoring import by this method. | — | +| [`public static string Inno.Assets.Pipeline.AssetSample.GetImportName(Inno.Assets.AssetPath path)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L84) | Gets the original sample directory name for a writable Project copy.
`path`: A sample directory path whose final segment starts with ~. | The unchanged final directory segment, including every leading ~. | `System.ArgumentException`: Thrown when does not identify a sample directory or has no usable name after its prefix. | +| [`public static bool Inno.Assets.Pipeline.AssetSample.IsRuntimeExcluded(Inno.Assets.AssetPath path, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSample.cs#L108) | Determines whether a path belongs to an authoring-only sample subtree that must not enter a Player runtime closure.
`path`: The isolated source path to inspect.
`isDirectory`: Whether the path itself represents a directory. | when any directory segment starts with ~, including in the writable Project source. | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSampleImportTransaction : System.IDisposable`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L22) | Owns a cancellable sample clone, its pinned generation and its owner-thread publication. | — | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleImportTransaction.target { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L89) | Gets the destination path with the original sample directory name preserved. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.isFaulted { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L94) | Gets whether failed publication or retirement has faulted the host and requires a restart. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.isValidationComplete { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L99) | Gets whether background validation has drained and Commit can inspect its outcome. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSampleImportTransaction.Advance()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L113) | Starts background indexing after cloning, then adopts its completed catalog without waiting. | True when the candidate is indexed and ready for BeginValidation; false while preparation is pending. | `System.InvalidOperationException`: The transaction is finished or the caller is not its initialization thread.
`System.OperationCanceledException`: Preparation or indexing observed cancellation before a candidate could be adopted. | +| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.BeginValidation(System.Func validate)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L167) | Starts one authoring preflight after indexing, retaining its dependencies until it drains.
`validate`: Owner-thread capture followed by asynchronous validation. Continuations must not mutate live Assets. | — | `System.ArgumentNullException`: The validation callback is null.
`System.InvalidOperationException`: The candidate is not indexed, validation already started, or the transaction is finished. | +| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Commit(System.Action? beforePublish = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L203) | Publishes a successfully validated import exactly once, without waiting for unfinished work.
`beforePublish`: Optional owner-thread finalization, such as recording neutral History data. Failure leaves the transaction open for Rollback and no Changed notification is published. | — | `System.InvalidOperationException`: Validation is unfinished, the transaction is finished, or the caller is not its owner thread.
`System.OperationCanceledException`: The import or its validation was canceled before publication. | +| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Cancel()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L222) | Requests cancellation of copying and validation while retaining all dependencies. | — | — | +| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Rollback()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L230) | Cancels work and removes an uncommitted copy after all background consumers have drained. | — | `Inno.Core.Execution.RetirementPendingException`: Background work is still draining; retain the transaction and retry on its owner thread. | +| [`public void Inno.Assets.Pipeline.AssetSampleImportTransaction.Dispose()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleImportTransaction.cs#L271) | Retires an uncommitted import on its owner thread after cancellation has drained. | — | `Inno.Core.Execution.RetirementPendingException`: Canceled work has not drained; disposal must be retried without releasing its owner. | +| [`public sealed class Inno.Assets.Pipeline.AssetSampleTransformContext`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L12) | Gives a source-language extension access to a staged sample clone before serialized references are remapped. | — | — | +| [`public string Inno.Assets.Pipeline.AssetSampleTransformContext.stagedRoot { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L36) | Gets the absolute private staging directory; files here are committed only after all transforms succeed. | — | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleTransformContext.source { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L41) | Gets the installed sample source path. | — | — | +| [`public Inno.Assets.AssetPath Inno.Assets.Pipeline.AssetSampleTransformContext.target { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L46) | Gets the writable project destination path. | — | — | +| [`public System.Threading.CancellationToken Inno.Assets.Pipeline.AssetSampleTransformContext.cancellationToken { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L51) | Gets cancellation for this private, generation-pinned transformation. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Assets.Pipeline.AssetSampleTransformContext.identityMap { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L56) | Gets cloned source identities, including directory and file metadata. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSampleTransformContext.TryGetSourceIdentity(string relativePath, out System.Guid oldId, out System.Guid newId)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L73) | Resolves the source and clone identity of one file relative to the sample directory.
`relativePath`: Slash-separated source-local file path below the sample directory.
`oldId`: Receives the installed source identity.
`newId`: Receives the writable clone identity. | Whether both identities were recorded from sample metadata. | — | +| [`public void Inno.Assets.Pipeline.AssetSampleTransformContext.MapType(System.Guid oldId, System.Guid newId)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L102) | Registers one source-language type replacement for subsequent structured asset rewriting.
`oldId`: The type identity stored in installed sample assets.
`newId`: The type identity declared by the cloned source. | — | `System.IO.InvalidDataException`: A source identity is reused inconsistently. | +| [`public interface Inno.Assets.Pipeline.IAssetSampleSourceRewriter`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L117) | Rewrites a source language in a private sample clone before source assets are published. | — | — | +| [`void Inno.Assets.Pipeline.IAssetSampleSourceRewriter.Transform(Inno.Assets.Pipeline.AssetSampleTransformContext context)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L129) | Rewrites staged source files and registers any changed serialized type identities.
`context`: The transaction-owned staged clone. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute : System.Attribute`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L136) | Registers a source-language sample transformer through the reloadable type catalog. | — | — | +| [`public Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute.AssetSampleSourceRewriterAttribute(string id)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L144) | Creates one ordered source-language transformer declaration.
`id`: Stable extension identity used for diagnostics and ordering. | — | — | +| [`public string Inno.Assets.Pipeline.AssetSampleSourceRewriterAttribute.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSampleSourceRewriter.cs#L153) | Gets the stable extension identity. | — | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSourceMount`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L14) | Maps one isolated asset source to a controlled physical root. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourceMount.AssetSourceMount(Inno.Assets.AssetSourceId id, string rootPath, bool isReadOnly, System.Collections.Generic.IEnumerable? dependencies = null)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L31) | Creates an asset source mount.
`id`: Stable source identity.
`rootPath`: Absolute or resolvable physical source root.
`isReadOnly`: Whether all source mutations must be rejected.
`dependencies`: Other read-only sources this source may reference. | — | — | +| [`public Inno.Assets.AssetSourceId Inno.Assets.Pipeline.AssetSourceMount.id { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L51) | Gets the stable source identity. | — | — | +| [`public string Inno.Assets.Pipeline.AssetSourceMount.rootPath { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L56) | Gets the controlled physical source root. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSourceMount.isReadOnly { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L61) | Gets whether source mutations are forbidden. | — | — | +| [`public System.Collections.Generic.IReadOnlySet Inno.Assets.Pipeline.AssetSourceMount.dependencySourceIds { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L66) | Gets explicitly declared cross-source dependencies. | — | — | +| [`public string Inno.Assets.Pipeline.AssetSourceMount.Resolve(string localPath)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/AssetSourceMount.cs#L77) | Resolves a source-local path and rejects physical root escape.
`localPath`: Source-local path. | An absolute path contained by this mount. | — | +| [`public sealed class Inno.Assets.Pipeline.AssetSourcePolicy`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L10) | Defines which physical entries are excluded from an asset source tree. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourcePolicy.AssetSourcePolicy()`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L47) | Creates a source policy using the engine's default noise filters. | — | — | +| [`public Inno.Assets.Pipeline.AssetSourcePolicy.AssetSourcePolicy(System.Collections.Generic.IEnumerable? ignoredFileNames, System.Collections.Generic.IEnumerable? ignoredDirectoryNames, System.Collections.Generic.IEnumerable? ignoredPrefixes, System.Collections.Generic.IEnumerable? ignoredSuffixes)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L67) | Creates a source policy with additional ignored names and affixes.
`ignoredFileNames`: Additional case-insensitive file names excluded from every source mount.
`ignoredDirectoryNames`: Additional case-insensitive directory names excluded with their descendants.
`ignoredPrefixes`: Additional file-name prefixes treated as transient authoring noise.
`ignoredSuffixes`: Additional file-name suffixes treated as transient authoring noise. | — | — | +| [`public static Inno.Assets.Pipeline.AssetSourcePolicy Inno.Assets.Pipeline.AssetSourcePolicy.defaultPolicy { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L84) | Gets the default source policy. | — | — | +| [`public bool Inno.Assets.Pipeline.AssetSourcePolicy.IsIgnored(string relativePath, bool isDirectory)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L98) | Determines whether an entry is excluded from the source database.
`relativePath`: The normalized path relative to its owning source mount.
`isDirectory`: Whether the source entry represents a directory. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`public static bool Inno.Assets.Pipeline.AssetSourcePolicy.IsGeneratedPath(string relativePath)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs#L131) | Determines whether a path is generated asset metadata.
`relativePath`: The normalized path relative to its owning source mount. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`public interface Inno.Assets.Pipeline.IAssetSourceSnapshot : Inno.Assets.IAssetArtifactLookup`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L15) | Provides read-only authoring source access for active or isolated candidate compilation. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.IAssetSourceSnapshot.sourceMounts { get; }`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L20) | Gets the source mounts represented by this view. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Assets.Pipeline.IAssetSourceSnapshot.GetFileSystemEntries(bool includeDirectories = true)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L31) | Captures the indexed entries represented by this view.
`includeDirectories`: Whether directory entries are included. | A stable entry collection for owner-thread input discovery. | — | +| [`TAsset Inno.Assets.Pipeline.IAssetSourceSnapshot.Load(Inno.Assets.AssetPath path) where TAsset : Inno.Assets.AssetObject`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L48) | Loads an authoring asset without publishing a candidate catalog.
`path`: The isolated source path.
`TAsset`: The required authoring asset type. | The resolved authoring asset. | `System.InvalidOperationException`: No compatible asset can be loaded from this view. | +| [`bool Inno.Assets.Pipeline.IAssetSourceSnapshot.TryGetInfo(Inno.Assets.AssetPath path, out Inno.Assets.AssetInfo? info)`](../../src/content/assets/Inno.Assets.Pipeline/Sources/IAssetSourceSnapshot.cs#L62) | Resolves immutable catalog information by source path.
`path`: The isolated source path.
`info`: The catalog information, or null when the source is absent. | True when catalog information exists. | — | ## 项目依赖 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Collections](../core/Inno.Core.Collections.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):公开引用边界由实际签名核对。 -- [Inno.References](../references/Inno.References.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 - -## Artifact 与部署摘要补充契约 - - - - - - -| 声明 | 行为与结果 | 所有权 / 线程 | 失败或限制 | -| --- | --- | --- | --- | -| [`public sealed class Inno.Assets.Pipeline.AssetArtifactWriter`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | 建立本次 aggregate build 的输出/诊断收集器。 | 内部结果由 Pipeline 使用;公开 Writer 不提供物理路径。 | 输出不能重复写入。 | -| [`public System.Threading.Tasks.ValueTask Inno.Assets.Pipeline.AssetArtifactWriter.WriteAsync(string outputName, System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.AssetDeploymentScope deploymentScope = Inno.Assets.Pipeline.AssetDeploymentScope.Runtime)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L36) | 复制 bytes,暂存唯一的命名输出;返回已完成的 ValueTask。AuthoringOnly 输出不进入运行闭包。 | Writer 属于当前 aggregate build;调用后可释放/修改传入缓冲区。 | 空名称、非法 scope、将 runtime/asset-state 标为 AuthoringOnly、重复名称或预取消均失败。 | -| [`public void Inno.Assets.Pipeline.AssetArtifactWriter.ReportDiagnostic(string message)`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L60) | 向本次构建加入诊断文本。 | 不开始导入、发布或文件 IO。 | 空白诊断会抛 ArgumentException。 | -| [`public Inno.Assets.Pipeline.AssetArtifactWriter.AssetArtifactWriter()`](../../src/content/assets/Inno.Assets.Pipeline/Importing/Artifacts/AssetArtifactWriter.cs#L11) | 建立本次 aggregate build 的输出/诊断收集器。 | 内部结果由 Pipeline 使用;公开 Writer 不提供物理路径。 | 输出不能重复写入。 | +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Core.Collections` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Core.Identity` | 公开签名依赖 | +| `Inno.References` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Shaders.md b/docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Shaders.md index fef46333b..6db77ece2 100644 --- a/docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Shaders.md +++ b/docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Shaders.md @@ -4,16 +4,18 @@ ## 职责与公开 API -Shader 图离线编译库,没有 Program。唯一 public 入口 `ShaderArtifactBuilder.Compile(assetRoot, shaderPath, platform, backend, outputFile)` 通过现有 Asset Pipeline、Shader IR、后端前端与 Artifact Codec 编译一个图。公开参数的目标平台来自 BGFX 工具链,GraphicsApi 属于中立 Rendering。没有 protected 扩展点。 +Shader 图离线编译库,没有 Program。公开组合边界为 `ShaderBuildInputs` 捕获/校验及 `ShaderArtifactBuilder.Compile` / `CompileGraphicsProgram` 通过现有 Asset Pipeline、Shader IR、后端前端与 Artifact Codec 编译一个图。公开参数的目标平台来自 BGFX 工具链,GraphicsApi 属于中立 Rendering。没有 protected 扩展点。 ```csharp using System.Threading; using Inno.Build.Toolchains.Bgfx.Shaders; using Inno.Build.Toolchains.Bgfx.Tools; using Inno.Rendering; +using Inno.Core.IO; static void CompileGraph( string assetRoot, + ProcessOwnedDirectory workspace, string sourceLocalShaderPath, BgfxShaderTargetProfile target, GraphicsApi graphicsApi, @@ -21,13 +23,16 @@ static void CompileGraph( ToolRunner tools, CancellationToken cancellationToken ) { - ShaderArtifactBuilder.Compile(assetRoot, sourceLocalShaderPath, + ShaderBuildInputs inputs = ShaderBuildInputs.Capture(assetRoot, cancellationToken); + ShaderArtifactBuilder.Compile(inputs, workspace, sourceLocalShaderPath, target, graphicsApi, outputFile, tools, cancellationToken); } ``` 变量由调用者提供。MSBuild 内置 ImGui、Composition 和 OutputTransfer 使用同一个 CompileShaderTask;命令行使用统一 CLI 的 shader 命令。 +`ShaderBuildInputs.Capture` 固定真实源文件和永久 `.imeta` 输入;临时 atomic publication、派生 `.abin` 和 source policy 噪声不参与。Task 的指纹和编译器消费同一个捕获对象。调用方通过 `StandardBuildCaches.CreateShaderWorkspaceAsync` 持有已登记的 Core 进程目录,编译前真实复制到该操作 owner,导入只修改副本;复制后和发布前重新核对完整清单及内容,新增、删除和保留时间戳的修改均会拒绝发布。输入声明复用中立 `NativeBuildInput` / `NativeBuildFingerprint`;不削弱通用 Native 输入校验。 + ## 失败、生命周期和热重载 新源导入或编译失败明确抛出 InvalidDataException,保留既有目标文件;只有完整产物成功才通过 AtomicFile 替换。临时 Asset/Module/诊断 owner 使用隔离 scratch,退出释放并清理。没有第二套 Shader 源编译旁路;此库不能进入 Player closure。 @@ -38,22 +43,30 @@ static void CompileGraph( ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public static void Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder.Compile(string assetRoot, string shaderPath, Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile platform, Inno.Rendering.GraphicsApi backend, string outputFile, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs#L58) | Imports and compiles one shader graph for an explicitly selected graphics target.
`assetRoot`:The directory containing shader authoring sources.
`shaderPath`:The source-local shader graph path.
`platform`:The native compiler's target platform.
`backend`:The graphics API for the compiled artifact.
`outputFile`:The artifact destination, replaced atomically after successful compilation.
`tools`:The host-selected executable distribution, or null for the application's deployed tools.
`cancellationToken`:Cancels import and native compilation before replacing the destination artifact. | — | `System.IO.InvalidDataException`:Import or shader compilation fails.
`System.ArgumentException`:A required authoring path or output path is blank. | -| [`public static void Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder.CompileGraphicsProgram(string assetRoot, string shaderPath, Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile platform, Inno.Rendering.GraphicsApi backend, string outputFile, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs#L103) | Compiles one raster graph into device-only program facts for an embedded adapter distribution.
`assetRoot`:The directory containing the graph and its source dependencies.
`shaderPath`:The source-local graph whose sole raster pass becomes the device program.
`platform`:The compiler's native target platform.
`backend`:The graphics API for the program binaries.
`outputFile`:The device artifact destination, atomically replaced after successful compilation.
`tools`:The host-selected executable distribution, or null for the application's deployed tools.
`cancellationToken`:Cancels import and native compilation before replacing the destination artifact. | — | `System.ArgumentException`:A required authoring or output path is blank.
`System.IO.InvalidDataException`:Compilation fails or the graph does not define exactly one raster program. | | [`public static class Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs#L26) | Compiles authored shader graphs through the standard import, IR and artifact pipeline. | — | — | +| [`public static void Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder.Compile(Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs inputs, Inno.Core.IO.ProcessOwnedDirectory workspace, string shaderPath, Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile platform, Inno.Rendering.GraphicsApi backend, string outputFile, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs#L70) | Imports and compiles one shader graph for an explicitly selected graphics target.
`inputs`: The operation's fixed source and permanent import-setting identity, copied before import.
`workspace`: A fresh caller-owned operation directory retained until compilation returns. The caller releases it after all consumers finish.
`shaderPath`: The source-local shader graph path.
`platform`: The native compiler's target platform.
`backend`: The graphics API for the compiled artifact.
`outputFile`: The artifact destination, replaced atomically after successful compilation.
`tools`: The host-selected executable distribution, or null for the application's deployed tools.
`cancellationToken`: Cancels import and native compilation before replacing the destination artifact. | — | `System.IO.InvalidDataException`: Import or shader compilation fails.
`System.ArgumentException`: A required shader path or output path is blank.
`System.ArgumentNullException`: The fixed input identity or workspace is null.
`System.InvalidOperationException`: The borrowed workspace has been released or is not empty.
`System.OperationCanceledException`: Import, copying, verification or compilation was canceled before publication. | +| [`public static void Inno.Build.Toolchains.Bgfx.Shaders.ShaderArtifactBuilder.CompileGraphicsProgram(Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs inputs, Inno.Core.IO.ProcessOwnedDirectory workspace, string shaderPath, Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile platform, Inno.Rendering.GraphicsApi backend, string outputFile, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderArtifactBuilder.cs#L129) | Compiles one raster graph into device-only program facts for an embedded adapter distribution.
`inputs`: The operation's fixed source and permanent import-setting identity, copied before import.
`workspace`: A fresh caller-owned operation directory retained until compilation returns. The caller releases it after all consumers finish.
`shaderPath`: The source-local graph whose sole raster pass becomes the device program.
`platform`: The compiler's native target platform.
`backend`: The graphics API for the program binaries.
`outputFile`: The device artifact destination, atomically replaced after successful compilation.
`tools`: The host-selected executable distribution, or null for the application's deployed tools.
`cancellationToken`: Cancels import and native compilation before replacing the destination artifact. | — | `System.ArgumentException`: A required shader path or output path is blank.
`System.IO.InvalidDataException`: Compilation fails or the graph does not define exactly one raster program.
`System.ArgumentNullException`: The fixed input identity or workspace is null.
`System.InvalidOperationException`: The borrowed workspace has been released or is not empty.
`System.OperationCanceledException`: Import, copying, verification or compilation was canceled before publication. | +| [`public sealed class Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs#L20) | Fixes the complete authored shader file inventory and content identity for one compilation operation. | — | — | +| [`public string Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs.fingerprint { get; }`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs#L40) | Gets the complete logical directory, file and content identity used by the enclosing build. | — | — | +| [`public static Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs.Capture(string assetRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs#L63) | Captures permanent authoring files and their import metadata without consuming temporary publications.
`assetRoot`: The existing physical authoring root, containing no source links.
`cancellationToken`: Cancels discovery and complete content hashing. | A fixed input inventory and content identity; physical source files remain owned by their author. | `System.ArgumentException`: The source root is blank.
`System.IO.IOException`: A source cannot be read, does not exist, or crosses a link.
`System.OperationCanceledException`: Capture was canceled. | +| [`public void Inno.Build.Toolchains.Bgfx.Shaders.ShaderBuildInputs.VerifyUnchanged(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../../backends/Bgfx/build/Inno.Build.Toolchains.Bgfx.Shaders/ShaderBuildInputs.cs#L87) | Rechecks the complete current inventory and bytes against this operation's captured identity.
`cancellationToken`: Cancels discovery and complete content verification. | — | `System.IO.InvalidDataException`: A source or permanent metadata file was added, removed or changed.
`System.IO.IOException`: The source tree cannot be safely inspected or read.
`System.OperationCanceledException`: Verification was canceled. | ## 项目依赖 +- [Inno.Build.Toolchains](../../build/Inno.Build.Toolchains.md):实现依赖;复用内容指纹机制,输入文件清单不越过编译库边界。 + - [Inno.Build.Toolchains.Bgfx.Tools](Inno.Build.Toolchains.Bgfx.Tools.md):公开引用边界由实际签名核对。 - [Inno.Assets.Pipeline](../../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 - [Inno.Core.Diagnostics](../../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 diff --git a/docs/backends/DotNet/Inno.Adapter.Serialization.DotNet.md b/docs/backends/DotNet/Inno.Adapter.Serialization.DotNet.md index 08c872348..3d6ea5425 100644 --- a/docs/backends/DotNet/Inno.Adapter.Serialization.DotNet.md +++ b/docs/backends/DotNet/Inno.Adapter.Serialization.DotNet.md @@ -29,6 +29,8 @@ static ISerializationMetadataSource CreateMetadata() 来源按 `Type` 弱缓存元数据,不把 collectible 类型保存在强静态表中。成员顺序、重复键检查、属性可见性和恢复回调验证与静态来源遵循相同契约。恢复通知在完整操作成功后提交;构造或回调失败传播到调用方事务。 +可选 `SerializationRevisionAttribute` 由本来源解析为 Core 的 `WithCaptureRevision` 声明:只接受 sealed serializable Core identity 对象的可读非静态 ulong/nullable ulong 成员,禁止持久化该计数。属性读取失败传播原始异常。纯值证明只来自实际 primitive 字段,不把任意 getter 当作无副作用读取;自定义 Converter 的资格仍由 Core 当前 generation 判定。缺少证明只影响捕获复用,不改变普通序列化。Core 不读取反射成员,不引用本 adapter。 + ## 验证入口 `Inno.Core.Serialization.Tests` 验证动态来源的现有序列化行为;`Inno.Runtime.Generators.Tests` 验证生成访问器的编译、私有成员、结构体、闭合集合、跨程序集继承及无效声明诊断。 diff --git a/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Bgfx.md b/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Bgfx.md index 93e8becd3..228689b58 100644 --- a/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Bgfx.md +++ b/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Bgfx.md @@ -28,15 +28,14 @@ RetireViewport 清除该视口后续绘制并委托 Device.RetireWindowSurface ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + @@ -51,57 +50,48 @@ RetireViewport 清除该视口后续绘制并委托 Device.RetireWindowSurface - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.BgfxImGuiRenderer(Inno.Rendering.IRenderDevice device, Inno.Rendering.GraphicsPipelineDescriptor pipelineDescriptor)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L78) | Creates a frame-contributing ImGui renderer around a backend-neutral shader artifact.
`device`:Active BGFX-backed neutral device.
`pipelineDescriptor`:Compiled ImGui shader stages and reflected interface. | — | `System.ArgumentException`:Thrown when the shader artifact does not use the required ImGui layout.
`System.NotSupportedException`:Thrown when device is not BGFX-backed. | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.AddRenderPasses(Inno.Rendering.RenderGraphBuilder graph, ulong frameIndex)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L367) | Adds the renderer's frame passes and resource declarations to the render graph.
`graph`:The graph consumed by add render passes; ownership remains with the caller unless explicitly stated otherwise.
`frameIndex`:The monotonic frame identity associated with this operation. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.CreateViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L219) | Creates a viewport using this implementation's validated inputs.
`target`:The existing target that receives the validated result. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L423) | Marks renderer-owned GPU resources for release at the next frame safety point or device teardown. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.DrainViewportRetirements()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L315) | Drains requested retirements at a closed-frame owner-thread point outside native UI callbacks. | Failure or timeout retains all unfinished viewport and renderer owners. | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.PrepareFrame(ulong frameIndex)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L323) | Prepares frame-owned resources before render graph recording begins.
`frameIndex`:The monotonic frame identity associated with this operation. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.PresentViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L283) | Presents the completed frame for the supplied auxiliary viewport.
`target`:The existing target that receives the validated result. | — | — | -| [`public Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RegisterTexture(Inno.Rendering.PersistentTextureHandle texture)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L132) | Registers a persistent render texture as an opaque ImGui texture token.
返回:An opaque token accepted by Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.DrawImage(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle,System.Numerics.Vector2,System.Numerics.Vector2,System.Numerics.Vector2).
`texture`:Texture owned by the active device generation. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RenderMain(nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L176) | Records ImGui draw data for the main application viewport.
`drawData`:The draw data consumed by render main; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RenderViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target, nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L260) | Records ImGui draw data for the supplied auxiliary viewport.
`target`:The existing target that receives the validated result.
`drawData`:The draw data consumed by render viewport; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.ReplaceShaderArtifact(Inno.Rendering.GraphicsPipelineDescriptor pipelineDescriptor)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L112) | Queues a compiled shader replacement that commits atomically at a frame safety point.
`pipelineDescriptor`:Complete candidate artifact. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.ResizeViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L238) | Resizes an auxiliary viewport surface to its current platform dimensions.
`target`:The existing target that receives the validated result. | — | — | -| [`public System.Threading.Tasks.Task Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RetireViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L297) | Destroys the auxiliary viewport and releases its rendering resources.
返回:Completion of render-thread window release, or an already completed task for an unallocated viewport.
`target`:The existing target that receives the validated result. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.SynchronizeMainOutput(int pixelWidth, int pixelHeight)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L199) | Invalidates a captured main packet when its drawable extent changes. The Shell owns device presentation availability and publishes it at frame boundaries.
`pixelWidth`:The pixel width consumed by synchronize main output; ownership remains with the caller unless explicitly stated otherwise.
`pixelHeight`:The pixel height consumed by synchronize main output; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.UnregisterTexture(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle texture)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L157) | Unregisters an opaque texture token without taking ownership of the source texture.
返回:true when the token was an external registration.
`texture`:Previously allocated token. | — | — | -| [`public string? Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.lastShaderError { get; private set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L99) | Gets the last recoverable shader replacement error while the last-good pipeline remains active. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.supportsViewports { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L104) | Gets whether supports viewports is enabled for this implementation. | — | — | -| [`public static Inno.Rendering.RenderVertexLayout Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.vertexLayout { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L94) | Gets the exact interleaved vertex layout required by the built-in ImGui shaders. | — | — | -| [`public sealed class Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer : Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer, Inno.Rendering.IRenderFrameGraphContributor`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L17) | Copies Dear ImGui command lists into frame-owned data and composites them through BGFX RenderGraph passes. | — | — | - -### `Inno.Adapter.Presentation.ImGui.BgfxImGuiShaderArtifacts` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Rendering.GraphicsPipelineDescriptor Inno.Adapter.Presentation.ImGui.BgfxImGuiShaderArtifacts.Load(Inno.Rendering.GraphicsApi api)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiShaderArtifacts.cs#L25) | Reads the precompiled program for the declared product and requested graphics API without invoking a compiler.
返回:The validated texture binding, stages and render state required by ImGui.
`api`:The active device API. | — | `System.IO.InvalidDataException`:The installed adapter lacks a required artifact or its binding contract is invalid. | -| [`public static class Inno.Adapter.Presentation.ImGui.BgfxImGuiShaderArtifacts`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiShaderArtifacts.cs#L11) | Loads the graph-built, source-free ImGui program distributed with this presentation adapter. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider.ImGuiPresentationProvider(Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions interaction)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationProvider.cs#L22) | Registers the bundled presentation implementation without creating a native context.
`interaction`:The borrowed immutable conventions explicitly selected by the Editor product. | — | `System.ArgumentNullException`:Interaction configuration is absent. | -| [`public override Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider.CreateContext(Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationProvider.cs#L29) | Creates a new caller-owned presentation context over the supplied host resources.
返回:A new context whose caller must dispose it before releasing the supplied resources. Providers must not return null.
`options`:Externally owned platform and rendering resources, together with the requested presentation policy. | — | `System.NotSupportedException`:The host resources or requested features are incompatible with this implementation. | +| [`public sealed class Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer : Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer, Inno.Rendering.IRenderFrameGraphContributor`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L18) | Copies Dear ImGui command lists into frame-owned data and composites them through BGFX RenderGraph passes. | — | — | +| [`public Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.BgfxImGuiRenderer(Inno.Rendering.IRenderDevice device, Inno.Rendering.GraphicsPipelineDescriptor pipelineDescriptor)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L79) | Creates a frame-contributing ImGui renderer around a backend-neutral shader artifact.
`device`: Active BGFX-backed neutral device.
`pipelineDescriptor`: Compiled ImGui shader stages and reflected interface. | — | `System.ArgumentException`: Thrown when the shader artifact does not use the required ImGui layout.
`System.NotSupportedException`: Thrown when is not BGFX-backed. | +| [`public static Inno.Rendering.RenderVertexLayout Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.vertexLayout { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L95) | Gets the exact interleaved vertex layout required by the built-in ImGui shaders. | — | — | +| [`public string? Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.lastShaderError { get; private set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L100) | Gets the last recoverable shader replacement error while the last-good pipeline remains active. | — | — | +| [`public bool Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.supportsViewports { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L105) | Gets whether supports viewports is enabled for this implementation. | — | — | +| [`public bool Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.supportsVertexOffsets { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L108) | Gets whether every draw-data consumer applies per-command vertex offsets to 16-bit indices. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.ReplaceShaderArtifact(Inno.Rendering.GraphicsPipelineDescriptor pipelineDescriptor)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L116) | Queues a compiled shader replacement that commits atomically at a frame safety point.
`pipelineDescriptor`: Complete candidate artifact. | — | — | +| [`public Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RegisterTexture(Inno.Rendering.PersistentTextureHandle texture)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L136) | Registers a persistent render texture as an opaque ImGui texture token.
`texture`: Texture owned by the active device generation. | An opaque token accepted by . | — | +| [`public bool Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.UnregisterTexture(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle texture)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L161) | Unregisters an opaque texture token without taking ownership of the source texture.
`texture`: Previously allocated token. | when the token was an external registration. | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RenderMain(nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L180) | Records ImGui draw data for the main application viewport.
`drawData`: The draw data consumed by render main; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.SynchronizeMainOutput(int pixelWidth, int pixelHeight)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L203) | Invalidates a captured main packet when its drawable extent changes. The Shell owns device presentation availability and publishes it at frame boundaries.
`pixelWidth`: The pixel width consumed by synchronize main output; ownership remains with the caller unless explicitly stated otherwise.
`pixelHeight`: The pixel height consumed by synchronize main output; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.CreateViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L223) | Creates a viewport using this implementation's validated inputs.
`target`: The existing target that receives the validated result. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.ResizeViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L242) | Resizes an auxiliary viewport surface to its current platform dimensions.
`target`: The existing target that receives the validated result. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RenderViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target, nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L264) | Records ImGui draw data for the supplied auxiliary viewport.
`target`: The existing target that receives the validated result.
`drawData`: The draw data consumed by render viewport; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.PresentViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L287) | Presents the completed frame for the supplied auxiliary viewport.
`target`: The existing target that receives the validated result. | — | — | +| [`public System.Threading.Tasks.Task Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.RetireViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L301) | Destroys the auxiliary viewport and releases its rendering resources.
`target`: The existing target that receives the validated result. | Completion of render-thread window release, or an already completed task for an unallocated viewport. | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.DrainViewportRetirements()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L319) | Drains requested retirements at a closed-frame owner-thread point outside native UI callbacks. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.PrepareFrame(ulong frameIndex)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L327) | Prepares frame-owned resources before render graph recording begins.
`frameIndex`: The monotonic frame identity associated with this operation. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.AddRenderPasses(Inno.Rendering.RenderGraphBuilder graph, ulong frameIndex)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L371) | Adds the renderer's frame passes and resource declarations to the render graph.
`graph`: The graph consumed by add render passes; ownership remains with the caller unless explicitly stated otherwise.
`frameIndex`: The monotonic frame identity associated with this operation. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.BgfxImGuiRenderer.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiRenderer.cs#L427) | Marks renderer-owned GPU resources for release at the next frame safety point or device teardown. | — | — | +| [`public static class Inno.Adapter.Presentation.ImGui.BgfxImGuiShaderArtifacts`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiShaderArtifacts.cs#L11) | Loads the graph-built, source-free ImGui program distributed with this presentation adapter. | — | — | +| [`public static Inno.Rendering.GraphicsPipelineDescriptor Inno.Adapter.Presentation.ImGui.BgfxImGuiShaderArtifacts.Load(Inno.Rendering.GraphicsApi api)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/BgfxImGuiShaderArtifacts.cs#L25) | Reads the precompiled program for the declared product and requested graphics API without invoking a compiler.
`api`: The active device API. | The validated texture binding, stages and render state required by ImGui. | `System.IO.InvalidDataException`: The installed adapter lacks a required artifact or its binding contract is invalid. | | [`public sealed class Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider : Inno.Adapter.Presentation.PresentationBackendProvider`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationProvider.cs#L9) | Registers SDL3 window integration with BGFX ImGui presentation. | — | — | +| [`public Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider.ImGuiPresentationProvider(Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions interaction)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationProvider.cs#L22) | Registers the bundled presentation implementation without creating a native context.
`interaction`: The borrowed immutable conventions explicitly selected by the Editor product. | — | `System.ArgumentNullException`: Interaction configuration is absent. | +| [`public override Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.ImGui.ImGuiPresentationProvider.CreateContext(Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Bgfx/ImGuiPresentationProvider.cs#L29) | Creates a new caller-owned presentation context over the supplied host resources.
`options`: Externally owned platform and rendering resources, together with the requested presentation policy. | A new context whose caller must dispose it before releasing the supplied resources. Providers must not return null. | `System.NotSupportedException`: The host resources or requested features are incompatible with this implementation. | ## 项目依赖 -- [Inno.Native.ImGui](Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Rendering.Bgfx](../Bgfx/Inno.Adapter.Rendering.Bgfx.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation](../../platform/Inno.Adapter.Presentation.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](Inno.Adapter.Presentation.ImGui.Sdl3.md):公开引用边界由实际签名核对。 -- [Inno.Rendering](../../rendering/Inno.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Platform.Sdl3](../Sdl3/Inno.Adapter.Platform.Sdl3.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Native.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Rendering.Bgfx` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Presentation` | 公开签名依赖 | +| `Inno.Adapter.Presentation.ImGui.Sdl3` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Adapter.Platform.Sdl3` | 公开签名依赖 | diff --git a/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md b/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md index a323875f7..8c7b8c499 100644 --- a/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md +++ b/docs/backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md @@ -4,6 +4,8 @@ Viewport 位置与尺寸 setter 经共享 Native ImGui 的标量 callback adapte [分类索引](README.md) · [Editor ImGui](../../editor/Inno.Editor.ImGui.md) · [BGFX ImGui](Inno.Adapter.Presentation.ImGui.Bgfx.md) · [Wiki 首页](../../README.md) +`RenderFrame` 的 preparation callback 在选择所属 Context、更新显示 metrics 后执行,并早于 Native NewFrame。准备、绘制和发布期间拒绝重入或 Dispose。准备失败不创建 Native frame;绘制 callback 失败只结束所属帧,不向 renderer 发布部分 draw data,原异常继续传播。调用方仍负责平衡自己的窗口和 Push/Pop;后端不会清空其他 Context 的栈。返回的 draw-data 指针仅借用至下一帧或 Context 退休。 + ## 公开 API Viewport 的位置与逻辑尺寸由 ImGui 的平台 Move/Resize 请求及 getter/setter 协议协调。 @@ -40,64 +42,75 @@ CreateImGuiContext 必须显式传入 ImGuiInteractionOptions,窗口必须属 关闭回调只解除路由/登记和 callback 数据,调用 RetireViewport 后保留原生 SDL 窗口。在下一 managed 安全点检查 Task,成功才销毁一次;失败保留 owner 并报告。Context 成功创建后接管 renderer;失败创建由调用者保留 renderer,重复创建明确拒绝。最终 Context Dispose 调用 drain,成功后释放 Native Context 与 renderer。Application 在成功退休前保留 context 登记。内部文件按 Context/Viewports/Rendering/Interop 组织;不存在另一份窗口目录或事件总线。 -## 当前源码公开 API 清单 +## 文本字体与 Context 退休 + +四种默认字形保留 JetBrains Mono 与 Font Awesome 图标,并合并随产品分发的 Noto Sans CJK SC 2.004。补充字体的来源、SIL Open Font License 与内容身份位于 `BuiltIn/Fonts/Supplemental`。只读取产品自己的字体资源,不探测系统安装目录;缺失、长度错误或内容身份错误立即拒绝创建 Context。 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 +每个 Context 只持有一份固定内存的补充字体 bytes,四种 face 共享借用至 Native Context 销毁。应用不长期缓存字体数据;自定义字体注册同样补齐 CJK。该资源属于共享呈现 backend,不把文字支持下放为平台分支。 + +直接调用 Context 的 `Dispose()` 与 `DestroyImGuiContext()` 使用同一完成路径。全部 viewport、renderer 和 native callback 成功退休后才移除该 Context 的精确应用登记并释放字体;Pending 或失败保留登记和依赖,供合法的后续退休操作重试。应用退出先冻结其 Context 集合,成功退休逐个解除登记,退休开始后拒绝新建 Context。关闭的 Context 不会因仍存活的另一个窗口而长期驻留。 + +## 当前源码公开 API 清单 -### `Inno.Adapter.Presentation.ImGui.ImGuiContextFlags` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableDocking = 2`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L22) | The enable docking key. | — | — | -| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableSmoothResize = 4`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L26) | The enable smooth resize key. | — | — | -| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableViewports = 1`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L18) | The enable viewports key. | — | — | -| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.None = 0`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L14) | The none key. | — | — | -| [`public enum Inno.Adapter.Presentation.ImGui.ImGuiContextFlags : int`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L9) | Identifies the supported im gui context flags values for this contract. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiFont` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiFont.IsAvailable(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L26) | Gets whether an exact font style is available in the current ImGui context.
返回:true when the exact face is registered.
`style`:The composable style to query. | — | — | -| [`public static Inno.Adapter.Presentation.ImGui.ImGuiFontScope Inno.Adapter.Presentation.ImGui.ImGuiFont.PushStyle(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L49) | Pushes a font style for the current ImGui context and returns a scope that restores it.
返回:A disposable scope. Missing faces gracefully fall back to the nearest registered face.
`style`:The composable style to use. | — | — | -| [`public static class Inno.Adapter.Presentation.ImGui.ImGuiFont`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L12) | Provides scoped access to font faces registered for the current ImGui context. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiFontScope` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Adapter.Presentation.ImGui.ImGuiFontScope.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L173) | Restores the font that was active before this scope. | — | — | -| [`public ref struct Inno.Adapter.Presentation.ImGui.ImGuiFontScope`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L157) | Restores the previous ImGui font when a scoped style ends. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiFontStyle` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Bold = 1`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L19) | Uses a bold font face. | — | — | -| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Italic = 2`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L24) | Uses an italic font face. | — | — | -| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Regular = 0`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L14) | Uses the regular font face. | — | — | -| [`public enum Inno.Adapter.Presentation.ImGui.ImGuiFontStyle : int`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L9) | Describes composable visual variants of an ImGui font family. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiIcon` - @@ -1503,1541 +1516,1479 @@ CreateImGuiContext 必须显式传入 ImGuiInteractionOptions,窗口必须属 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.A = "A"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L26) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AddressBook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L27) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AddressCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L28) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L29) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignJustify = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L30) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L31) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L32) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Anchor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L33) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L34) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L35) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L36) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L37) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L38) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L39) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L40) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L42) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L43) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L44) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L45) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L41) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ankh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L46) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AppleWhole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L47) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Archway = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L48) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L49) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown19 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L50) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown91 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L51) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownAZ = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L52) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L53) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownShortWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L54) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownUpAcrossLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L55) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownUpLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L56) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownWideShort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L57) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownZA = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L58) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L59) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowLeftLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L60) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowPointer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L61) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L62) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L63) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L64) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L65) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightToBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L66) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightToCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L67) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L68) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRotateRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L69) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L87) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsDownToPeople = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L88) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L89) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsLeftRightToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L90) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L91) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsSpin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L92) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsSplitUpAndLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L93) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToCircle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L94) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L95) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToEye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L96) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsTurnRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L97) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsTurnToDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L98) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L99) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpDownLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L100) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L101) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTrendDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L70) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTrendUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L71) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTurnDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L72) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTurnUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L73) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L74) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp19 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L75) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp91 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L76) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpAZ = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L77) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L78) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromGroundWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L79) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromWaterPump = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L80) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L81) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpRightDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L82) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpRightFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L83) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpShortWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L84) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpWideShort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L85) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpZA = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L86) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Asterisk = "*"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L102) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.At = "@"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L103) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Atom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L104) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AudioDescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L105) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AustralSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L106) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Award = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L107) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.B = "B"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L108) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Baby = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L109) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BabyCarriage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L110) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Backward = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L111) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BackwardFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L112) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BackwardStep = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L113) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L114) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacteria = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L115) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacterium = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L116) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BagShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L117) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bahai = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L118) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BahtSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L119) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ban = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L120) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bandage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L122) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BangladeshiTakaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L123) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BanSmoking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L121) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Barcode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L124) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L125) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BarsProgress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L126) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BarsStaggered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L127) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Baseball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L128) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BaseballBatBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L129) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Basketball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L131) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BasketShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L130) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bath = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L132) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L133) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L134) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L135) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryQuarter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L136) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryThreeQuarters = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L137) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L138) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BedPulse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L139) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BeerMugEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L140) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L141) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BellConcierge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L142) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BellSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L143) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BezierCurve = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L144) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bicycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L145) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Binoculars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L146) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Biohazard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L147) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BitcoinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L148) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Blender = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L149) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BlenderPhone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L150) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Blog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L151) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bold = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L152) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L153) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoltLightning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L154) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bomb = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L155) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L156) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L157) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Book = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L158) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookAtlas = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L159) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookBible = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L160) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookBookmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L161) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookJournalWhills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L162) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bookmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L169) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L163) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L164) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookOpenReader = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L165) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookQuran = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L166) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookSkull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L167) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookTanakh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L168) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderAll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L170) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderNone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L171) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderTopLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L172) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoreHole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L173) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BottleDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L174) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BottleWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L175) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlFood = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L176) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlingBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L178) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlRice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L177) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Box = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L179) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxArchive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L180) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxesPacking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L183) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxesStacked = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L184) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L181) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxTissue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L182) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Braille = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L185) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Brain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L186) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BrazilianRealSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L187) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BreadSlice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L188) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bridge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L189) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L190) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L191) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L192) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L193) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L194) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Briefcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L195) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BriefcaseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L196) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Broom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L197) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BroomBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L198) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Brush = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L199) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bucket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L200) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L201) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bugs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L203) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BugSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L202) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Building = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L204) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L205) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L206) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L207) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L208) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingColumns = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L209) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L210) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L211) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingNgo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L212) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L213) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L214) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L215) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L216) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bullhorn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L217) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bullseye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L218) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Burger = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L219) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Burst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L220) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L221) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BusinessTime = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L223) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BusSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L222) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.C = "C"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L224) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CableCar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L225) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CakeCandles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L226) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Calculator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L227) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Calendar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L228) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L229) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarDay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L230) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarDays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L231) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L232) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L233) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarWeek = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L234) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L235) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Camera = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L236) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CameraRetro = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L237) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CameraRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L238) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Campground = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L239) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CandyCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L240) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cannabis = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L241) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Capsules = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L242) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Car = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L243) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Caravan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L250) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarBattery = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L244) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L245) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L251) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L252) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L253) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L254) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L246) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarRear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L247) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Carrot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L255) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarSide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L248) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L256) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartFlatbed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L257) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartFlatbedSuitcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L258) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L259) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L260) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarTunnel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L249) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CashRegister = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L261) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L262) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CediSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L263) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CentSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L264) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Certificate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L265) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chair = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L266) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chalkboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L267) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChalkboardUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L268) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChampagneGlasses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L269) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChargingStation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L270) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartArea = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L271) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartBar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L272) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartColumn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L273) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartDiagram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L274) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartGantt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L275) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L276) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartPie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L277) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L278) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Check = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L279) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CheckDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L280) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CheckToSlot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L281) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cheese = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L282) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chess = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L283) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessBishop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L284) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessBoard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L285) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessKing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L286) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessKnight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L287) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessPawn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L288) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessQueen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L289) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessRook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L290) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L291) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L292) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L293) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L294) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Child = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L295) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildCombatant = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L296) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L297) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildReaching = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L298) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Children = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L299) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Church = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L300) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Circle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L301) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L302) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L303) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L304) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L305) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L306) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L307) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L308) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L309) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L310) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDollarToSlot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L311) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L312) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L313) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L314) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleH = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L315) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleHalfStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L316) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleInfo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L317) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L318) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L319) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L320) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleNotch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L321) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePause = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L322) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePlay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L323) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L324) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L325) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleRadiation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L326) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L327) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleStop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L328) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L329) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L330) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L331) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.City = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L332) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clapperboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L333) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clipboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L334) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L335) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L336) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L337) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L338) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L339) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClockRotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L340) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L341) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClosedCaptioning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L342) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cloud = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L343) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L344) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L345) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L346) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMeatball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L347) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMoon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L348) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMoonRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L349) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L350) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudShowersHeavy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L351) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudShowersWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L352) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudSun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L353) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudSunRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L354) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clover = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L355) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Code = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L356) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeBranch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L357) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeCommit = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L358) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeCompare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L359) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeFork = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L360) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeMerge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L361) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodePullRequest = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L362) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Coins = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L363) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ColonSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L364) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Comment = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L365) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L366) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L367) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L368) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L369) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Comments = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L372) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentsDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L373) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L370) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentSms = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L371) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CompactDisc = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L374) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Compass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L375) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CompassDrafting = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L376) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Compress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L377) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Computer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L378) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ComputerMouse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L379) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cookie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L380) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CookieBite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L381) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Copy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L382) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Copyright = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L383) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Couch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L384) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L385) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CreditCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L386) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L387) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CropSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L388) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cross = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L389) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crosshairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L390) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L391) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L392) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crutch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L393) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CruzeiroSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L394) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cube = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L395) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cubes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L396) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CubesStacked = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L397) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.D = "D"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L398) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Database = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L399) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DeleteLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L400) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Democrat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L401) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Desktop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L402) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dharmachakra = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L403) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramNext = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L404) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramPredecessor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L405) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramProject = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L406) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramSuccessor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L407) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Diamond = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L408) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiamondTurnRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L409) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L410) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceD20 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L411) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceD6 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L412) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceFive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L413) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceFour = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L414) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceOne = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L415) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceSix = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L416) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceThree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L417) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceTwo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L418) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Disease = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L419) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Display = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L420) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Divide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L421) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dna = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L422) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L423) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DollarSign = "$"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L424) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dolly = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L425) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DongSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L426) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DoorClosed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L427) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DoorOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L428) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dove = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L429) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DownLeftAndUpRightToCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L430) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Download = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L432) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DownLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L431) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dragon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L433) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrawPolygon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L434) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Droplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L435) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DropletSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L436) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Drum = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L437) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrumSteelpan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L438) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrumstickBite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L439) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dumbbell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L440) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dumpster = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L441) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DumpsterFire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L442) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dungeon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L443) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.E = "E"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L444) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarDeaf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L445) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarListen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L446) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAfrica = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L447) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAmericas = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L448) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAsia = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L449) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthEurope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L450) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthOceania = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L451) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Egg = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L452) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eject = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L453) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Elevator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L454) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ellipsis = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L455) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EllipsisVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L456) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Envelope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L457) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L458) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L459) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeOpenText = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L460) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopesBulk = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L461) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Equals = "="`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L462) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eraser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L463) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ethernet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L464) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EuroSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L465) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Exclamation = "!"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L466) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Expand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L467) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Explosion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L468) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L469) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeDropper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L470) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeLowVision = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L471) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L472) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.F = "F"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L473) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceAngry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L474) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceDizzy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L475) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFlushed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L476) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFrown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L477) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFrownOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L478) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrimace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L479) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L480) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L481) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinBeamSweat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L482) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinHearts = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L483) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L484) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinSquintTears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L485) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinStars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L486) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L487) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L488) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongueSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L489) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongueWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L490) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L491) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L492) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKiss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L493) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKissBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L494) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKissWinkHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L495) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaugh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L496) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L497) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L498) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L499) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceMeh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L500) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceMehBlank = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L501) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceRollingEyes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L502) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSadCry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L503) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSadTear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L504) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L505) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmileBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L506) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmileWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L507) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSurprise = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L508) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceTired = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L509) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L510) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Faucet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L511) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaucetDrip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L512) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fax = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L513) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Feather = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L514) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FeatherPointed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L515) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ferry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L516) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.File = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L517) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L518) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L519) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileAudio = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L520) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L521) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L522) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L523) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L524) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L525) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L526) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L527) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileContract = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L528) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCsv = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L529) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileExcel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L530) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileExport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L531) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileFragment = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L532) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileHalfDashed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L533) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileImage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L534) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileImport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L535) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileInvoice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L536) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileInvoiceDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L537) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L538) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L539) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePdf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L540) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L541) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePowerpoint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L542) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePrescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L543) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L544) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileSignature = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L545) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileVideo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L546) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileWaveform = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L547) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileWord = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L548) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileZipper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L549) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fill = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L550) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FillDrip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L551) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Film = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L552) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Filter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L553) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilterCircleDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L554) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilterCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L555) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fingerprint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L556) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L557) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireBurner = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L558) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireExtinguisher = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L559) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireFlameCurved = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L560) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireFlameSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L561) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fish = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L562) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FishFins = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L563) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Flag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L564) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlagCheckered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L565) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlagUsa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L566) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Flask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L567) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlaskVial = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L568) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FloppyDisk = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L569) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlorinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L570) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Folder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L571) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderClosed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L572) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L573) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L574) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L575) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderTree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L576) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Font = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L577) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FontAwesome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L578) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Football = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L579) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Forward = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L580) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ForwardFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L581) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ForwardStep = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L582) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FrancSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L583) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Frog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L584) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Futbol = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L585) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.G = "G"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L586) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gamepad = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L587) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GasPump = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L588) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gauge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L589) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L590) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L591) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeSimpleHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L592) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gavel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L593) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L594) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L595) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gem = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L596) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Genderless = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L597) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ghost = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L598) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gift = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L599) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gifts = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L600) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Glasses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L603) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GlassWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L601) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GlassWaterDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L602) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Globe = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L604) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GolfBallTee = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L605) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gopuram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L606) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GraduationCap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L607) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GreaterThan = ">"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L608) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GreaterThanEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L609) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Grip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L610) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L611) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripLinesVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L612) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L613) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GroupArrowsRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L614) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GuaraniSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L615) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Guitar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L616) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L617) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.H = "H"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L618) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hammer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L619) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hamsa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L620) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L621) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandBackFist = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L622) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Handcuffs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L642) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L623) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandFist = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L624) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHolding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L625) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L626) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L627) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingHand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L628) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L629) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L630) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandLizard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L631) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandMiddleFinger = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L632) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPeace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L633) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L634) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L638) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L635) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L636) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L637) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hands = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L643) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsAslInterpreting = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L644) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsBound = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L645) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsBubbles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L646) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandScissors = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L639) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsClapping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L647) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Handshake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L652) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeAngle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L653) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L654) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSimpleSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L655) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L656) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHolding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L648) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHoldingChild = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L649) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHoldingCircle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L650) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L640) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandSpock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L641) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsPraying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L651) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hanukiah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L657) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HardDrive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L658) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hashtag = "#"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L659) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatCowboy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L660) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatCowboySide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L661) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatWizard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L662) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Heading = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L667) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Headphones = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L668) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadphonesSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L669) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Headset = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L670) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideCough = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L663) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideCoughSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L664) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideMask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L665) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L666) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Heart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L671) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L672) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L673) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L674) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L675) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L676) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L677) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L678) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartPulse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L679) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Helicopter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L680) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelicopterSymbol = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L681) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelmetSafety = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L682) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelmetUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L683) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HexagonNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L684) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HexagonNodesBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L685) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Highlighter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L686) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HillAvalanche = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L687) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HillRockslide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L688) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hippo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L689) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HockeyPuck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L690) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HollyBerry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L691) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Horse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L692) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HorseHead = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L693) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hospital = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L694) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HospitalUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L695) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hotdog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L697) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hotel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L698) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HotTubPerson = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L696) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hourglass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L699) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassEnd = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L700) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L701) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassStart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L702) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.House = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L703) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimney = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L704) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L705) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L706) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L707) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyWindow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L708) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L709) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L710) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L711) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L712) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L713) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L714) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFloodWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L715) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFloodWaterCircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L716) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseLaptop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L717) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L718) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L719) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L720) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L721) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L722) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L723) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseSignal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L724) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseTsunami = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L725) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L726) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HryvniaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L727) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hurricane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L728) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.I = "I"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L729) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IceCream = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L731) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Icicles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L732) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Icons = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L733) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ICursor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L730) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdBadge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L734) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L735) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdCardClip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L736) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Igloo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L737) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Image = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L738) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ImagePortrait = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L739) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Images = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L740) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Inbox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L741) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Indent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L742) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IndianRupeeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L743) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Industry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L744) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Infinity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L745) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Info = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L746) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Italic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L747) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.J = "J"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L748) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Jar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L749) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JarWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L750) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Jedi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L751) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JetFighter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L752) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JetFighterUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L753) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Joint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L754) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JugDetergent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L755) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.K = "K"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L756) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Kaaba = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L757) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Key = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L758) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Keyboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L759) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Khanda = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L760) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KipSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L761) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KitchenSet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L763) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KitMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L762) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KiwiBird = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L764) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.L = "L"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L765) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Landmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L767) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandmarkDome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L768) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandmarkFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L769) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandMineOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L766) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Language = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L770) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Laptop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L771) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopCode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L772) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopFile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L773) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L774) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LariSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L775) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LayerGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L776) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Leaf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L777) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LeftLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L778) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L779) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lemon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L780) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LessThan = "<"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L781) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LessThanEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L782) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LifeRing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L783) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lightbulb = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L784) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LinesLeaning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L785) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Link = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L786) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LinkSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L787) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LiraSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L788) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.List = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L789) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L790) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListOl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L791) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListUl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L792) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LitecoinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L793) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationArrow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L794) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationCrosshairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L795) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L796) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationPin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L797) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationPinLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L798) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L799) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LockOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L800) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Locust = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L801) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lungs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L802) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LungsVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L803) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.M = "M"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L804) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Magnet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L805) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L806) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L807) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassChart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L808) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L809) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassLocation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L810) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L811) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L812) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ManatSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L813) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Map = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L814) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapLocation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L815) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapLocationDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L816) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapPin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L817) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Marker = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L818) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L819) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsAndVenus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L820) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsAndVenusBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L821) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L822) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L823) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStrokeRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L824) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStrokeUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L825) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L826) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlassCitrus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L827) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlassEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L828) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L829) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MaskFace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L830) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MasksTheater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L832) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MaskVentilator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L831) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MattressPillow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L833) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Maximize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L834) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Medal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L835) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Memory = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L836) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Menorah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L837) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mercury = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L838) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Message = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L839) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Meteor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L840) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microchip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L841) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microphone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L842) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L843) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneLinesSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L844) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L845) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microscope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L846) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MillSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L847) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Minimize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L848) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Minus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L849) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mitten = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L850) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mobile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L851) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L852) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileRetro = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L853) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileScreen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L854) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileScreenButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L855) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L856) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill1 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L857) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill1Wave = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L858) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L863) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillTransfer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L859) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillTrendUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L860) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillWave = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L861) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L862) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L864) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyCheckDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L865) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Monument = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L866) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Moon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L867) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MortarPestle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L868) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mosque = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L869) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mosquito = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L870) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MosquitoNet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L871) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Motorcycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L872) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mound = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L873) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mountain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L874) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MountainCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L875) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MountainSun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L876) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MugHot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L877) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MugSaucer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L878) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Music = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L879) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.N = "N"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L880) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NairaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L881) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NetworkWired = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L882) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Neuter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L883) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Newspaper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L884) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Notdef = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L886) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NotEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L885) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NotesMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L888) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NoteSticky = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L887) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num0 = "0"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L16) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num1 = "1"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L17) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num2 = "2"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L18) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num3 = "3"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L19) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num4 = "4"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L20) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num5 = "5"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L21) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num6 = "6"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L22) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num7 = "7"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L23) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num8 = "8"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L24) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num9 = "9"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L25) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.O = "O"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L889) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ObjectGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L890) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ObjectUngroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L891) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.OilCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L892) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.OilWell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L893) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Om = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L894) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Otter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L895) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Outdent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L896) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.P = "P"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L897) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pager = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L898) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paintbrush = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L900) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PaintRoller = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L899) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Palette = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L901) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pallet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L902) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Panorama = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L903) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paperclip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L905) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PaperPlane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L904) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ParachuteBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L906) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paragraph = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L907) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Passport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L908) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paste = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L909) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pause = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L910) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paw = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L911) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Peace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L912) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L913) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pencil = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L919) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenClip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L914) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenFancy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L915) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenNib = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L916) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenRuler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L917) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenToSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L918) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleArrows = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L920) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleCarryBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L921) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L922) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L923) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeoplePulling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L924) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleRobbery = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L925) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleRoof = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L926) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PepperHot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L927) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Percent = "%"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L928) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Person = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L929) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonArrowDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L930) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonArrowUpFromLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L931) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBiking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L932) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBooth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L933) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBreastfeeding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L934) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L935) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L936) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonChalkboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L937) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L938) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L939) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L940) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L941) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L942) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L943) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDigging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L944) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDotsFromLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L945) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L946) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDressBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L947) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDrowning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L948) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonFalling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L949) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonFallingBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L950) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHalfDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L951) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHarassing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L952) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHiking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L953) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryPointing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L954) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryRifle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L955) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryToPerson = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L956) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonPraying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L957) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonPregnant = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L958) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L959) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRifle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L960) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRunning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L961) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonShelter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L962) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkating = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L963) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkiing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L964) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkiingNordic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L965) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSnowboarding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L966) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSwimming = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L967) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonThroughWindow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L968) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L969) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingArrowLoopLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L970) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L971) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingDashedLineArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L972) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingLuggage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L973) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingWithCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L974) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PesetaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L975) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PesoSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L976) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Phone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L977) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L978) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L979) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneVolume = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L980) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhotoFilm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L981) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PiggyBank = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L982) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L983) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PizzaSlice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L984) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaceOfWorship = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L985) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L986) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneArrival = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L987) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L988) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L989) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L990) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneDeparture = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L991) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L992) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L993) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L994) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlantWilt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L995) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlateWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L996) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Play = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L997) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L998) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L999) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1000) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1001) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1002) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1003) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1004) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plus = "+"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1005) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlusMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1006) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Podcast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1007) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Poo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1008) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Poop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1010) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PooStorm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1009) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PowerOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1011) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Prescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1012) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PrescriptionBottle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1013) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PrescriptionBottleMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1014) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Print = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1015) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PumpMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1016) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PumpSoap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1017) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PuzzlePiece = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1018) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Q = "Q"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1019) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Qrcode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1020) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Question = "?"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1021) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.QuoteLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1022) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.QuoteRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1023) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.R = "R"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1024) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Radiation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1025) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Radio = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1026) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rainbow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1027) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RankingStar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1028) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Receipt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1029) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RecordVinyl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1030) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleAd = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1031) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1032) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1033) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Recycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1034) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Registered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1035) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Repeat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1036) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Reply = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1037) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ReplyAll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1038) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Republican = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1039) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Restroom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1040) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Retweet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1041) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ribbon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1042) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1043) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1044) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1045) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightToBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1046) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ring = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1047) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Road = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1048) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadBarrier = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1049) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadBridge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1050) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1051) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1052) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1053) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1054) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadSpikes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1055) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Robot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1056) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rocket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1057) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1058) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1059) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RotateRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1060) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Route = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1061) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1062) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RubleSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1063) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1064) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ruler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1065) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerCombined = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1066) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerHorizontal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1067) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1068) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupeeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1069) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupiahSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1070) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.S = "S"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1071) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SackDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1072) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SackXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1073) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sailboat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1074) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Satellite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1075) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SatelliteDish = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1076) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleBalanced = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1077) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleUnbalanced = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1078) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleUnbalancedFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1079) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.School = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1080) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1081) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1082) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1083) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1084) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1085) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Scissors = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1086) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Screwdriver = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1087) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScrewdriverWrench = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1088) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Scroll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1089) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScrollTorah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1090) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SdCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1091) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Section = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1092) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Seedling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1093) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Server = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1094) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shader = "S"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L15) | Letter mark used for authored Shader graph assets. | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shapes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1095) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Share = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1096) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShareFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1097) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShareNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1098) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SheetPlastic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1099) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShekelSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1100) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1101) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldCat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1102) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldDog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1103) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldHalved = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1104) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1105) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1106) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ship = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1107) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shirt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1108) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShoePrints = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1109) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1110) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShopLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1111) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShopSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1112) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shower = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1113) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shrimp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1114) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shuffle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1115) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShuttleSpace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1116) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Signal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1118) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Signature = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1119) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SignHanging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1117) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SignsPost = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1120) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SimCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1121) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1122) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sitemap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1123) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Skull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1124) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SkullCrossbones = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1125) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Slash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1126) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sleigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1127) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sliders = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1128) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Smog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1129) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Smoking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1130) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowflake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1131) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowman = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1132) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowplow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1133) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Soap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1134) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Socks = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1135) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SolarPanel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1136) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1137) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SortDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1138) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SortUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1139) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1140) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SpaghettiMonsterFlying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1141) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SpellCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1142) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spider = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1143) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spinner = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1144) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Splotch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1145) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spoon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1146) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SprayCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1147) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SprayCanSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1148) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Square = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1149) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareArrowUpRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1150) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareBinary = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1151) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1152) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1153) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1154) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1155) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1156) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareEnvelope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1157) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1158) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareH = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1159) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1160) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareNfi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1161) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareParking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1162) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1163) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePersonConfined = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1164) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePhone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1165) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePhoneFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1166) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1167) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePollHorizontal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1168) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePollVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1169) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareRootVariable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1170) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareRss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1171) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareShareNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1172) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareUpRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1173) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1174) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1175) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StaffSnake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1176) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1177) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stamp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1178) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stapler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1179) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Star = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1180) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarAndCrescent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1181) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1182) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarHalfStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1183) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarOfDavid = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1184) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarOfLife = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1185) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SterlingSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1186) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stethoscope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1187) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1188) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stopwatch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1189) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stopwatch20 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1190) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Store = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1191) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StoreSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1192) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StreetView = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1193) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Strikethrough = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1194) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stroopwafel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1195) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Subscript = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1196) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Suitcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1197) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SuitcaseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1198) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SuitcaseRolling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1199) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1200) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SunPlantWilt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1201) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Superscript = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1202) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Swatchbook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1203) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Synagogue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1204) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Syringe = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1205) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.T = "T"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1206) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Table = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1207) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCells = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1208) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsColumnLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1209) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsLarge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1210) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsRowLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1211) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsRowUnlock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1212) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableColumns = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1213) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1214) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tablet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1216) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TabletButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1217) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableTennisPaddleBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1215) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tablets = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1219) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TabletScreenButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1218) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TachographDigital = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1220) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1221) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tags = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1222) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tape = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1223) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tarp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1224) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TarpDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1225) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Taxi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1226) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Teeth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1227) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TeethOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1228) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1229) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1230) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1231) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1232) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1233) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1234) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureLow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1235) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureQuarter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1236) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureThreeQuarters = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1237) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TengeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1238) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1239) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1240) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1241) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowsDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1243) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowTurnLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1242) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tents = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1244) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Terminal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1245) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextHeight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1246) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1247) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextWidth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1248) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Thermometer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1249) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbsDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1250) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbsUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1251) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Thumbtack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1252) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbtackSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1253) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ticket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1254) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TicketSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1255) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Timeline = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1256) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToggleOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1257) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToggleOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1258) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Toilet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1259) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPaper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1260) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPaperSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1261) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPortable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1262) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletsPortable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1263) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Toolbox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1264) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tooth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1265) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToriiGate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1266) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tornado = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1267) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerBroadcast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1268) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerCell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1269) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerObservation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1270) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tractor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1271) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trademark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1272) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrafficLight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1273) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trailer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1274) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Train = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1275) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrainSubway = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1276) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrainTram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1277) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Transgender = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1278) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1279) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1280) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1281) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashCanArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1282) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1283) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TreeCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1284) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TriangleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1285) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trophy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1286) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trowel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1287) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrowelBricks = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1288) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Truck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1289) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1290) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1291) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1292) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckField = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1293) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFieldUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1294) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFront = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1295) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1296) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMonster = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1297) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMoving = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1298) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckPickup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1299) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckPlane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1300) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckRampBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1301) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1302) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurkishLiraSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1303) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurnDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1304) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurnUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1305) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tv = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1306) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.U = "U"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1307) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Umbrella = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1308) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UmbrellaBeach = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1309) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Underline = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1310) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UniversalAccess = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1311) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Unlock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1312) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UnlockKeyhole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1313) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1314) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpDownLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1315) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Upload = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1319) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1316) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpRightAndDownLeftFromCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1317) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpRightFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1318) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.User = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1320) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserAstronaut = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1321) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1322) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserClock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1323) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserDoctor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1324) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1325) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGraduate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1326) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1327) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserInjured = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1328) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLarge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1329) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLargeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1330) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1331) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1332) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserNinja = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1333) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserNurse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1334) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserPen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1335) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1336) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Users = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1343) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersBetweenLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1344) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserSecret = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1337) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersGear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1345) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1338) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1339) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1346) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersRays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1347) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersRectangle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1348) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1349) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersViewfinder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1350) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserTag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1340) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserTie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1341) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1342) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Utensils = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1351) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.V = "V"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1352) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VanShuttle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1353) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vault = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1354) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VectorSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1355) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Venus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1356) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VenusDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1357) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VenusMars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1358) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vest = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1359) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VestPatches = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1360) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vial = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1361) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VialCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1362) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vials = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1364) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VialVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1363) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Video = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1365) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VideoSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1366) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vihara = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1367) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Virus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1368) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusCovid = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1369) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusCovidSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1370) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Viruses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1372) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1371) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Voicemail = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1373) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Volcano = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1374) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Volleyball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1375) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1376) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeLow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1377) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1378) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1379) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VrCardboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1380) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.W = "W"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1381) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WalkieTalkie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1382) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wallet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1383) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandMagic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1384) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandMagicSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1385) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1386) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Warehouse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1387) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Water = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1388) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WaterLadder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1389) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WaveSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1390) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WebAwesome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1391) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WeightHanging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1392) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WeightScale = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1393) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheatAwn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1394) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheatAwnCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1395) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wheelchair = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1396) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheelchairMove = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1397) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WhiskeyGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1398) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wifi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1399) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wind = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1400) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowMaximize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1401) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowMinimize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1402) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowRestore = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1403) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineBottle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1404) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1405) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineGlassEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1406) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WonSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1407) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Worm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1408) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wrench = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1409) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.X = "X"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1410) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Xmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1412) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.XmarksLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1413) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.XRay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1411) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Y = "Y"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1414) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.YenSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1415) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.YinYang = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1416) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Z = "Z"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1417) | 编译期常量;值及类型见当前声明。 | — | — | -| [`public static class Inno.Adapter.Presentation.ImGui.ImGuiIcon`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L12) | — | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions.ImGuiInteractionOptions(bool commandKeyBehavior, int horizontalWheelDirection)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L22) | Captures interaction conventions without inferring them from the current operating system.
`commandKeyBehavior`:Whether ImGui should use Command-based editing and selection conventions.
`horizontalWheelDirection`:Either one or minus one, applied to the platform event's horizontal wheel value. | — | `System.ArgumentOutOfRangeException`:The wheel direction is not one or minus one. | +| [`public sealed class Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L8) | Freezes product-selected keyboard conventions and horizontal wheel interpretation. | — | — | +| [`public Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions.ImGuiInteractionOptions(bool commandKeyBehavior, int horizontalWheelDirection)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L22) | Captures interaction conventions without inferring them from the current operating system.
`commandKeyBehavior`: Whether ImGui should use Command-based editing and selection conventions.
`horizontalWheelDirection`: Either one or minus one, applied to the platform event's horizontal wheel value. | — | `System.ArgumentOutOfRangeException`: The wheel direction is not one or minus one. | | [`public bool Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions.commandKeyBehavior { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L35) | Gets the explicitly chosen editing and selection conventions. | — | — | | [`public int Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions.horizontalWheelDirection { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L40) | Gets the horizontal platform wheel multiplier. | — | — | -| [`public sealed class Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/ImGuiInteractionOptions.cs#L8) | Freezes product-selected keyboard conventions and horizontal wheel interpretation. | — | — | - -### `Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.ImGuiTextureHandle(ulong value)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L19) | Creates an opaque texture token allocated by an ImGui presentation backend.
`value`:Non-zero backend token. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.Equals(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle other)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.Equals(object obj)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.GetHashCode()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.operator ==(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle left, Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle right)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.operator !=(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle left, Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle right)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.ToString()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.isValid { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L37) | Gets whether this token identifies a registered texture. | — | — | -| [`public ulong Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.value { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L32) | Gets the opaque token consumed only by the active ImGui backend. | — | — | | [`public readonly record struct Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle : System.IEquatable`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Identifies a backend-owned texture through an opaque ImGui token instead of a native graphics handle. | — | — | - -### `Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.CreateViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L123) | Creates backend presentation state for one detached viewport.
`target`:New viewport target. | — | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.DrainViewportRetirements()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L175) | Drains requested retirements at a closed-frame owner-thread point outside native UI callbacks. | Failure or timeout retains all unfinished viewport and renderer owners. | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.PresentViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L153) | Marks one detached viewport ready for presentation.
`target`:Viewport destination. | — | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RenderMain(nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L101) | Consumes current main-window draw data before the next ImGui frame begins.
`drawData`:Opaque native ImDrawData pointer valid for the current frame. | — | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RenderViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target, nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L142) | Consumes draw data for one detached viewport.
`target`:Viewport destination.
`drawData`:Opaque native ImDrawData pointer valid for the current frame. | — | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.ResizeViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L131) | Resizes backend presentation state for one detached viewport.
`target`:Updated viewport target. | — | — | -| [`System.Threading.Tasks.Task Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RetireViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L167) | Removes the viewport from future rendering and schedules release of its borrowed native window.
返回:A task completed only when the renderer no longer uses the native window. This is not a GPU fence.
`target`:The viewport whose original owner keeps its native window alive during retirement. | Native callbacks may request retirement but must not wait or advance rendering. | — | -| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.SynchronizeMainOutput(int pixelWidth, int pixelHeight)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L112) | Synchronizes the primary drawable after a platform resize.
`pixelWidth`:Current drawable width in physical pixels.
`pixelHeight`:Current drawable height in physical pixels. | — | — | -| [`bool Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.supportsViewports { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L93) | Gets whether detached viewport windows are rendered by this backend. | — | — | -| [`public interface Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer : System.IDisposable`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L88) | Presents ImGui draw data through a replaceable renderer while platform input and windows stay shared. | — | — | - -### `Inno.Adapter.Presentation.ImGui.PlatformImGuiContext` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L130) | Releases the resources owned by this instance. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.DrawImage(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle texture, System.Numerics.Vector2 size, System.Numerics.Vector2 uv0 = default(System.Numerics.Vector2), System.Numerics.Vector2 uv1 = default(System.Numerics.Vector2))`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L120) | Renders the image presentation for the current editor frame.
`texture`:The texture consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`size`:The size consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`uv0`:The uv0 consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`uv1`:The uv1 consumed by draw image; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.LoadIniSettings(string? settings)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L72) | Loads the current editor layout text into Dear ImGui without retaining the source string.
`settings`:The validated configuration that controls this operation. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.RegisterFontStyle(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style, string filePath, float fontSizePixels = 16)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L46) | Registers a named font style and its glyph ranges in the active ImGui font atlas.
`style`:The style consumed by register font style; ownership remains with the caller unless explicitly stated otherwise.
`filePath`:The file path text validated by the register font style operation.
`fontSizePixels`:The font size pixels consumed by register font style; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public nint Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.RenderFrame(System.Action drawFrame)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L100) | Records frame rendering for the current frame.
返回:The validated int ptr that represents the completed operation.
`drawFrame`:The callback invoked by render frame within the operation's owned lifetime. | — | — | -| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.SetIniFile(string? filePath)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L61) | Updates the ini file state and applies the resulting invariants.
`filePath`:The file path text validated by the set ini file operation. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.TryCaptureIniSettings(out string settings, bool force = false)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L86) | Attempts to capture ini settings without changing state when the operation cannot complete.
返回:true when the requested condition is satisfied; otherwise, false.
`settings`:The validated configuration that controls this operation.
`force`:Whether force behavior is enabled while try capture ini settings executes. | — | — | -| [`public bool Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.TryGetWindowId(uint viewportId, out uint windowId)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L26) | Resolves the SDL window currently owned by an ImGui viewport.
返回:true when the viewport has a live SDL window.
`viewportId`:The ImGui viewport identity.
`windowId`:The SDL window identity when available. | — | `System.ObjectDisposedException`:Thrown after the presentation context has been disposed. | -| [`public sealed class Inno.Adapter.Presentation.ImGui.PlatformImGuiContext : System.IDisposable`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L9) | Manages Dear ImGui frame lifecycle and rendering integration for a platform window. | — | — | - -### `Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public int Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.height { get; internal set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L82) | Gets the current framebuffer height. | — | — | -| [`public Inno.Adapter.Platform.PlatformNativeHandles Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.nativeHandles { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L72) | Gets backend-neutral native-window integration handles. | — | — | +| [`public Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.ImGuiTextureHandle(ulong value)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L19) | Creates an opaque texture token allocated by an ImGui presentation backend.
`value`: Non-zero backend token. | — | — | +| [`public ulong Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.value { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L32) | Gets the opaque token consumed only by the active ImGui backend. | — | — | +| [`public bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.isValid { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L37) | Gets whether this token identifies a registered texture. | — | — | +| [`public override string Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.ToString()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.operator !=(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle left, Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle right)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.operator ==(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle left, Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle right)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.GetHashCode()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.Equals(object obj)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle.Equals(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle other)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L11) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L43) | Describes one detached ImGui viewport without exposing SDL or graphics-backend handles. | — | — | | [`public uint Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.viewportId { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L62) | Gets the stable Dear ImGui viewport identity. | — | — | -| [`public int Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.width { get; internal set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L77) | Gets the current framebuffer width. | — | — | | [`public uint Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.windowId { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L67) | Gets the platform window identity. | — | — | -| [`public sealed class Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L43) | Describes one detached ImGui viewport without exposing SDL or graphics-backend handles. | — | — | - -### `Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Adapter.Presentation.ImGui.PlatformImGuiContext Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions.CreateImGuiContext(Inno.Adapter.Platform.Sdl3.Sdl3PlatformApplication application, Inno.Adapter.Platform.Sdl3.Sdl3PlatformWindow window, Inno.Adapter.Presentation.ImGui.ImGuiContextFlags contextFlags, Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions interaction, Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer? renderer = null)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L98) | Creates one ImGui context and transfers the supplied renderer ownership only after successful creation.
返回:The newly created context, which owns its renderer until complete retirement and disposal.
`application`:Target platform application instance.
`window`:Target platform window.
`interaction`:Explicit immutable keyboard and wheel conventions supplied by the product.
`contextFlags`:ImGui context feature flags.
`renderer`:Optional presentation backend; ownership transfers on success and remains with the caller on failure. | — | `System.ArgumentNullException`:An application, window, or interaction configuration is null.
`System.ArgumentException`:The window does not belong to the supplied application.
`System.InvalidOperationException`:The window is closed or already owns an ImGui context. | -| [`public static void Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions.DestroyImGuiContext(Inno.Adapter.Platform.Sdl3.Sdl3PlatformApplication application, Inno.Adapter.Platform.Sdl3.Sdl3PlatformWindow window)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L141) | Retires and disposes the window's context, retaining its application registration if retirement fails.
`application`:Target platform application instance.
`window`:Target platform window. | — | `System.ArgumentNullException`:The application or window is null.
`System.InvalidOperationException`:Retirement cannot complete at the current owner-thread safe point; the context remains owned. | -| [`public static class Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L14) | Extension APIs that add ImGui integration on top of Inno.Adapter.Platform.Sdl3.Sdl3PlatformApplication. | — | — | +| [`public Inno.Adapter.Platform.PlatformNativeHandles Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.nativeHandles { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L72) | Gets backend-neutral native-window integration handles. | — | — | +| [`public int Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.width { get; internal set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L77) | Gets the current framebuffer width. | — | — | +| [`public int Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget.height { get; internal set; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L82) | Gets the current framebuffer height. | — | — | +| [`public interface Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer : System.IDisposable`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L88) | Presents ImGui draw data through a replaceable renderer while platform input and windows stay shared. | — | — | +| [`bool Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.supportsVertexOffsets { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L93) | Gets whether every draw-data consumer applies per-command vertex offsets to 16-bit indices. | — | — | +| [`bool Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.supportsViewports { get; }`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L98) | Gets whether detached viewport windows are rendered by this backend. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RenderMain(nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L106) | Consumes current main-window draw data before the next ImGui frame begins.
`drawData`: Opaque native ImDrawData pointer valid for the current frame. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.SynchronizeMainOutput(int pixelWidth, int pixelHeight)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L117) | Synchronizes the primary drawable after a platform resize.
`pixelWidth`: Current drawable width in physical pixels.
`pixelHeight`: Current drawable height in physical pixels. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.CreateViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L128) | Creates backend presentation state for one detached viewport.
`target`: New viewport target. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.ResizeViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L136) | Resizes backend presentation state for one detached viewport.
`target`: Updated viewport target. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RenderViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target, nint drawData)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L147) | Consumes draw data for one detached viewport.
`target`: Viewport destination.
`drawData`: Opaque native ImDrawData pointer valid for the current frame. | — | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.PresentViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L158) | Marks one detached viewport ready for presentation.
`target`: Viewport destination. | — | — | +| [`System.Threading.Tasks.Task Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.RetireViewport(Inno.Adapter.Presentation.ImGui.PlatformImGuiViewportTarget target)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L172) | Removes the viewport from future rendering and schedules release of its borrowed native window.
`target`: The viewport whose original owner keeps its native window alive during retirement. | A task completed only when the renderer no longer uses the native window. This is not a GPU fence. | — | +| [`void Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer.DrainViewportRetirements()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/IPlatformImGuiRenderer.Api.cs#L180) | Drains requested retirements at a closed-frame owner-thread point outside native UI callbacks. | — | — | +| [`public sealed class Inno.Adapter.Presentation.ImGui.PlatformImGuiContext : System.IDisposable`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L9) | Manages Dear ImGui frame lifecycle and rendering integration for a platform window. | — | — | +| [`public bool Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.TryGetWindowId(uint viewportId, out uint windowId)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L26) | Resolves the SDL window currently owned by an ImGui viewport.
`viewportId`: The ImGui viewport identity.
`windowId`: The SDL window identity when available. | when the viewport has a live SDL window. | `System.ObjectDisposedException`: Thrown after the presentation context has been disposed. | +| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.RegisterFontStyle(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style, string filePath, float fontSizePixels = 16)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L46) | Registers a named font style and its glyph ranges in the active ImGui font atlas.
`style`: The style consumed by register font style; ownership remains with the caller unless explicitly stated otherwise.
`filePath`: The file path text validated by the register font style operation.
`fontSizePixels`: The font size pixels consumed by register font style; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.SetIniFile(string? filePath)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L61) | Updates the ini file state and applies the resulting invariants.
`filePath`: The file path text validated by the set ini file operation. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.LoadIniSettings(string? settings)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L72) | Loads the current editor layout text into Dear ImGui without retaining the source string.
`settings`: The validated configuration that controls this operation. | — | — | +| [`public bool Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.TryCaptureIniSettings(out string settings, bool force = false)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L86) | Attempts to capture ini settings without changing state when the operation cannot complete.
`settings`: The validated configuration that controls this operation.
`force`: Whether force behavior is enabled while try capture ini settings executes. | when the requested condition is satisfied; otherwise, . | — | +| [`public nint Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.RenderFrame(System.Action drawFrame, System.Action? prepareFrame = null)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L112) | Records frame rendering for the current frame.
`drawFrame`: The callback invoked by render frame within the operation's owned lifetime.
`prepareFrame`: Optional callback invoked with the owned context before native frame creation. | Borrowed native draw data for the completed frame, valid until the next frame or retirement. | — | +| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.DrawImage(Inno.Adapter.Presentation.ImGui.ImGuiTextureHandle texture, System.Numerics.Vector2 size, System.Numerics.Vector2 uv0 = default(System.Numerics.Vector2), System.Numerics.Vector2 uv1 = default(System.Numerics.Vector2))`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L135) | Renders the image presentation for the current editor frame.
`texture`: The texture consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`size`: The size consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`uv0`: The uv0 consumed by draw image; ownership remains with the caller unless explicitly stated otherwise.
`uv1`: The uv1 consumed by draw image; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.PlatformImGuiContext.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/PlatformImGuiContext.Api.cs#L148) | Retires the context and its owned resources after the current frame operation has completed. | — | `System.InvalidOperationException`: A frame operation still owns the context; the caller must retry after that operation completes. | +| [`public static class Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L14) | Extension APIs that add ImGui integration on top of . | — | — | +| [`public static Inno.Adapter.Presentation.ImGui.PlatformImGuiContext Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions.CreateImGuiContext(Inno.Adapter.Platform.Sdl3.Sdl3PlatformApplication application, Inno.Adapter.Platform.Sdl3.Sdl3PlatformWindow window, Inno.Adapter.Presentation.ImGui.ImGuiContextFlags contextFlags, Inno.Adapter.Presentation.ImGui.ImGuiInteractionOptions interaction, Inno.Adapter.Presentation.ImGui.IPlatformImGuiRenderer? renderer = null)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L98) | Creates one ImGui context and transfers the supplied renderer ownership only after successful creation.
`application`: Target platform application instance.
`window`: Target platform window.
`interaction`: Explicit immutable keyboard and wheel conventions supplied by the product.
`contextFlags`: ImGui context feature flags.
`renderer`: Optional presentation backend; ownership transfers on success and remains with the caller on failure. | The newly created context, which owns its renderer until complete retirement and disposal. | `System.ArgumentNullException`: An application, window, or interaction configuration is null.
`System.ArgumentException`: The window does not belong to the supplied application.
`System.InvalidOperationException`: The window is closed or already owns an ImGui context. | +| [`public static void Inno.Adapter.Presentation.ImGui.Sdl3PlatformApplicationImGuiExtensions.DestroyImGuiContext(Inno.Adapter.Platform.Sdl3.Sdl3PlatformApplication application, Inno.Adapter.Platform.Sdl3.Sdl3PlatformWindow window)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/Api/Sdl3PlatformApplicationImGuiExtensions.cs#L141) | Retires and disposes the window's context, retaining its application registration if retirement fails.
`application`: Target platform application instance.
`window`: Target platform window. | — | `System.ArgumentNullException`: The application or window is null.
`System.InvalidOperationException`: Retirement cannot complete at the current owner-thread safe point; the context remains owned. | +| [`public enum Inno.Adapter.Presentation.ImGui.ImGuiContextFlags : int`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L9) | Identifies the supported im gui context flags values for this contract. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.None = 0`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L14) | The none key. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableViewports = 1`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L18) | The enable viewports key. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableDocking = 2`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L22) | The enable docking key. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiContextFlags.EnableSmoothResize = 4`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiContextFlags.cs#L26) | The enable smooth resize key. | — | — | +| [`public static class Inno.Adapter.Presentation.ImGui.ImGuiFont`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L12) | Provides scoped access to font faces registered for the current ImGui context. | — | — | +| [`public static bool Inno.Adapter.Presentation.ImGui.ImGuiFont.IsAvailable(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L26) | Gets whether an exact font style is available in the current ImGui context.
`style`: The composable style to query. | when the exact face is registered. | — | +| [`public static Inno.Adapter.Presentation.ImGui.ImGuiFontScope Inno.Adapter.Presentation.ImGui.ImGuiFont.PushStyle(Inno.Adapter.Presentation.ImGui.ImGuiFontStyle style)`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L49) | Pushes a font style for the current ImGui context and returns a scope that restores it.
`style`: The composable style to use. | A disposable scope. Missing faces gracefully fall back to the nearest registered face. | — | +| [`public ref struct Inno.Adapter.Presentation.ImGui.ImGuiFontScope`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L157) | Restores the previous ImGui font when a scoped style ends. | — | — | +| [`public void Inno.Adapter.Presentation.ImGui.ImGuiFontScope.Dispose()`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFont.cs#L173) | Restores the font that was active before this scope. | — | — | +| [`public enum Inno.Adapter.Presentation.ImGui.ImGuiFontStyle : int`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L9) | Describes composable visual variants of an ImGui font family. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Regular = 0`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L14) | Uses the regular font face. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Bold = 1`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L19) | Uses a bold font face. | — | — | +| [`Inno.Adapter.Presentation.ImGui.ImGuiFontStyle.Italic = 2`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiFontStyle.cs#L24) | Uses an italic font face. | — | — | +| [`public static class Inno.Adapter.Presentation.ImGui.ImGuiIcon`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shader = "S"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L15) | Letter mark used for authored Shader graph assets. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num0 = "0"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num1 = "1"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num2 = "2"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num3 = "3"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L19) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num4 = "4"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L20) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num5 = "5"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L21) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num6 = "6"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L22) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num7 = "7"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L23) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num8 = "8"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L24) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Num9 = "9"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L25) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.A = "A"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L26) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AddressBook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L27) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AddressCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L28) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L29) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignJustify = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L30) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L31) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AlignRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L32) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Anchor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L33) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L34) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L35) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L36) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnchorLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L37) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L38) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L39) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L40) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L41) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L42) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L43) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L44) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AnglesUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L45) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ankh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L46) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AppleWhole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L47) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Archway = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L48) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L49) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown19 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L50) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDown91 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L51) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownAZ = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L52) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L53) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownShortWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L54) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownUpAcrossLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L55) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownUpLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L56) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownWideShort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L57) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowDownZA = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L58) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L59) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowLeftLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L60) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowPointer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L61) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L62) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L63) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L64) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L65) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightToBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L66) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRightToCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L67) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L68) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRotateRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L69) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTrendDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L70) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTrendUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L71) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTurnDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L72) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowTurnUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L73) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L74) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp19 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L75) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUp91 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L76) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpAZ = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L77) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L78) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromGroundWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L79) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpFromWaterPump = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L80) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L81) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpRightDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L82) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpRightFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L83) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpShortWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L84) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpWideShort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L85) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowUpZA = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L86) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L87) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsDownToPeople = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L88) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L89) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsLeftRightToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L90) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L91) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsSpin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L92) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsSplitUpAndLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L93) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToCircle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L94) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L95) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsToEye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L96) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsTurnRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L97) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsTurnToDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L98) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L99) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpDownLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L100) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L101) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Asterisk = "*"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L102) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.At = "@"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L103) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Atom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L104) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AudioDescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L105) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.AustralSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L106) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Award = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L107) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.B = "B"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L108) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Baby = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L109) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BabyCarriage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L110) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Backward = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L111) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BackwardFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L112) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BackwardStep = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L113) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L114) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacteria = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L115) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bacterium = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L116) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BagShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L117) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bahai = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L118) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BahtSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L119) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ban = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L120) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BanSmoking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L121) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bandage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L122) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BangladeshiTakaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L123) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Barcode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L124) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L125) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BarsProgress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L126) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BarsStaggered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L127) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Baseball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L128) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BaseballBatBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L129) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BasketShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L130) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Basketball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L131) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bath = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L132) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L133) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L134) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L135) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryQuarter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L136) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BatteryThreeQuarters = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L137) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L138) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BedPulse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L139) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BeerMugEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L140) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L141) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BellConcierge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L142) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BellSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L143) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BezierCurve = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L144) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bicycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L145) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Binoculars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L146) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Biohazard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L147) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BitcoinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L148) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Blender = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L149) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BlenderPhone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L150) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Blog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L151) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bold = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L152) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L153) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoltLightning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L154) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bomb = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L155) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L156) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L157) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Book = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L158) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookAtlas = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L159) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookBible = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L160) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookBookmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L161) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookJournalWhills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L162) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L163) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L164) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookOpenReader = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L165) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookQuran = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L166) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookSkull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L167) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BookTanakh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L168) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bookmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L169) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderAll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L170) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderNone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L171) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BorderTopLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L172) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoreHole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L173) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BottleDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L174) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BottleWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L175) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlFood = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L176) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlRice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L177) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BowlingBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L178) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Box = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L179) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxArchive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L180) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L181) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxTissue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L182) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxesPacking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L183) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BoxesStacked = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L184) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Braille = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L185) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Brain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L186) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BrazilianRealSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L187) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BreadSlice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L188) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bridge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L189) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L190) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L191) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L192) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L193) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BridgeWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L194) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Briefcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L195) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BriefcaseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L196) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Broom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L197) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BroomBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L198) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Brush = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L199) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bucket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L200) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L201) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BugSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L202) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bugs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L203) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Building = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L204) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L205) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L206) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L207) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L208) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingColumns = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L209) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L210) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L211) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingNgo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L212) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L213) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L214) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L215) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BuildingWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L216) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bullhorn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L217) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bullseye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L218) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Burger = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L219) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Burst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L220) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L221) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BusSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L222) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.BusinessTime = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L223) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.C = "C"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L224) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CableCar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L225) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CakeCandles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L226) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Calculator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L227) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Calendar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L228) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L229) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarDay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L230) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarDays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L231) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L232) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L233) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarWeek = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L234) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CalendarXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L235) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Camera = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L236) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CameraRetro = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L237) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CameraRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L238) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Campground = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L239) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CandyCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L240) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cannabis = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L241) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Capsules = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L242) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Car = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L243) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarBattery = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L244) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L245) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L246) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarRear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L247) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarSide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L248) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CarTunnel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L249) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Caravan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L250) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L251) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L252) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L253) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CaretUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L254) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Carrot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L255) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L256) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartFlatbed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L257) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartFlatbedSuitcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L258) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L259) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CartShopping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L260) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CashRegister = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L261) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L262) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CediSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L263) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CentSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L264) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Certificate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L265) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chair = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L266) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chalkboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L267) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChalkboardUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L268) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChampagneGlasses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L269) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChargingStation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L270) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartArea = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L271) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartBar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L272) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartColumn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L273) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartDiagram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L274) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartGantt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L275) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L276) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartPie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L277) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChartSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L278) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Check = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L279) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CheckDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L280) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CheckToSlot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L281) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cheese = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L282) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Chess = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L283) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessBishop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L284) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessBoard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L285) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessKing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L286) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessKnight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L287) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessPawn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L288) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessQueen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L289) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChessRook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L290) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L291) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L292) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L293) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChevronUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L294) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Child = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L295) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildCombatant = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L296) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L297) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ChildReaching = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L298) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Children = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L299) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Church = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L300) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Circle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L301) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L302) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L303) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L304) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L305) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L306) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L307) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L308) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L309) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleChevronUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L310) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDollarToSlot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L311) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L312) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L313) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L314) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleH = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L315) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleHalfStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L316) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleInfo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L317) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L318) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L319) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L320) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleNotch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L321) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePause = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L322) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePlay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L323) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L324) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L325) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleRadiation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L326) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L327) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleStop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L328) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L329) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L330) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L331) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.City = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L332) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clapperboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L333) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clipboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L334) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L335) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L336) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L337) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClipboardUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L338) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L339) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClockRotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L340) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L341) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ClosedCaptioning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L342) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cloud = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L343) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L344) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L345) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L346) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMeatball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L347) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMoon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L348) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudMoonRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L349) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L350) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudShowersHeavy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L351) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudShowersWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L352) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudSun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L353) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CloudSunRain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L354) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Clover = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L355) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Code = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L356) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeBranch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L357) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeCommit = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L358) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeCompare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L359) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeFork = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L360) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodeMerge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L361) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CodePullRequest = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L362) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Coins = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L363) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ColonSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L364) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Comment = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L365) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L366) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L367) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L368) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L369) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L370) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentSms = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L371) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Comments = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L372) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CommentsDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L373) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CompactDisc = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L374) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Compass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L375) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CompassDrafting = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L376) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Compress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L377) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Computer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L378) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ComputerMouse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L379) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cookie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L380) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CookieBite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L381) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Copy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L382) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Copyright = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L383) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Couch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L384) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L385) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CreditCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L386) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L387) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CropSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L388) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cross = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L389) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crosshairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L390) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L391) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L392) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Crutch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L393) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CruzeiroSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L394) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cube = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L395) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cubes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L396) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.CubesStacked = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L397) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.D = "D"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L398) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Database = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L399) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DeleteLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L400) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Democrat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L401) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Desktop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L402) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dharmachakra = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L403) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramNext = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L404) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramPredecessor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L405) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramProject = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L406) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiagramSuccessor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L407) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Diamond = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L408) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiamondTurnRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L409) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L410) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceD20 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L411) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceD6 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L412) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceFive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L413) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceFour = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L414) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceOne = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L415) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceSix = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L416) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceThree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L417) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceTwo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L418) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Disease = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L419) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Display = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L420) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Divide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L421) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dna = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L422) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L423) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DollarSign = "$"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L424) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dolly = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L425) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DongSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L426) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DoorClosed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L427) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DoorOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L428) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dove = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L429) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DownLeftAndUpRightToCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L430) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DownLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L431) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Download = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L432) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dragon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L433) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrawPolygon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L434) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Droplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L435) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DropletSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L436) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Drum = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L437) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrumSteelpan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L438) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DrumstickBite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L439) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dumbbell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L440) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dumpster = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L441) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.DumpsterFire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L442) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Dungeon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L443) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.E = "E"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L444) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarDeaf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L445) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarListen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L446) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAfrica = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L447) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAmericas = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L448) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthAsia = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L449) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthEurope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L450) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EarthOceania = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L451) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Egg = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L452) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eject = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L453) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Elevator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L454) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ellipsis = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L455) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EllipsisVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L456) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Envelope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L457) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L458) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L459) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopeOpenText = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L460) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EnvelopesBulk = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L461) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Equals = "="`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L462) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eraser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L463) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ethernet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L464) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EuroSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L465) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Exclamation = "!"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L466) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Expand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L467) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Explosion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L468) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eye = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L469) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeDropper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L470) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeLowVision = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L471) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L472) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.F = "F"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L473) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceAngry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L474) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceDizzy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L475) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFlushed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L476) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFrown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L477) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceFrownOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L478) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrimace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L479) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L480) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L481) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinBeamSweat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L482) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinHearts = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L483) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L484) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinSquintTears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L485) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinStars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L486) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L487) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L488) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongueSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L489) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinTongueWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L490) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinWide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L491) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceGrinWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L492) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKiss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L493) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKissBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L494) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceKissWinkHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L495) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaugh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L496) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L497) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughSquint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L498) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceLaughWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L499) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceMeh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L500) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceMehBlank = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L501) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceRollingEyes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L502) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSadCry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L503) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSadTear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L504) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L505) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmileBeam = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L506) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSmileWink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L507) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceSurprise = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L508) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaceTired = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L509) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L510) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Faucet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L511) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FaucetDrip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L512) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fax = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L513) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Feather = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L514) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FeatherPointed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L515) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ferry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L516) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.File = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L517) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L518) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L519) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileAudio = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L520) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L521) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L522) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L523) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L524) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L525) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L526) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L527) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileContract = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L528) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCsv = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L529) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileExcel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L530) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileExport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L531) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileFragment = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L532) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileHalfDashed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L533) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileImage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L534) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileImport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L535) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileInvoice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L536) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileInvoiceDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L537) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L538) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L539) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePdf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L540) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L541) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePowerpoint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L542) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilePrescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L543) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L544) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileSignature = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L545) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileVideo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L546) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileWaveform = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L547) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileWord = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L548) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileZipper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L549) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fill = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L550) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FillDrip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L551) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Film = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L552) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Filter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L553) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilterCircleDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L554) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FilterCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L555) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fingerprint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L556) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L557) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireBurner = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L558) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireExtinguisher = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L559) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireFlameCurved = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L560) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FireFlameSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L561) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Fish = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L562) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FishFins = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L563) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Flag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L564) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlagCheckered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L565) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlagUsa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L566) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Flask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L567) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlaskVial = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L568) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FloppyDisk = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L569) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FlorinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L570) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Folder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L571) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderClosed = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L572) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L573) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L574) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L575) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FolderTree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L576) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Font = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L577) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FontAwesome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L578) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Football = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L579) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Forward = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L580) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ForwardFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L581) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ForwardStep = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L582) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.FrancSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L583) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Frog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L584) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Futbol = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L585) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.G = "G"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L586) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gamepad = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L587) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GasPump = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L588) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gauge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L589) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L590) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L591) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GaugeSimpleHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L592) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gavel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L593) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L594) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gears = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L595) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gem = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L596) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Genderless = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L597) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ghost = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L598) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gift = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L599) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gifts = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L600) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GlassWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L601) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GlassWaterDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L602) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Glasses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L603) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Globe = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L604) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GolfBallTee = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L605) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gopuram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L606) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GraduationCap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L607) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GreaterThan = ">"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L608) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GreaterThanEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L609) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Grip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L610) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L611) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripLinesVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L612) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L613) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GroupArrowsRotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L614) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.GuaraniSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L615) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Guitar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L616) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L617) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.H = "H"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L618) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hammer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L619) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hamsa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L620) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L621) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandBackFist = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L622) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandDots = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L623) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandFist = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L624) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHolding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L625) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L626) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L627) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingHand = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L628) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L629) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandHoldingMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L630) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandLizard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L631) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandMiddleFinger = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L632) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPeace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L633) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L634) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L635) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L636) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L637) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandPointer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L638) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandScissors = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L639) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L640) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandSpock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L641) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Handcuffs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L642) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hands = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L643) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsAslInterpreting = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L644) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsBound = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L645) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsBubbles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L646) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsClapping = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L647) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHolding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L648) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHoldingChild = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L649) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsHoldingCircle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L650) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandsPraying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L651) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Handshake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L652) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeAngle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L653) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L654) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSimpleSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L655) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HandshakeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L656) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hanukiah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L657) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HardDrive = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L658) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hashtag = "#"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L659) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatCowboy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L660) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatCowboySide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L661) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HatWizard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L662) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideCough = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L663) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideCoughSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L664) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideMask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L665) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadSideVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L666) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Heading = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L667) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Headphones = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L668) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeadphonesSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L669) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Headset = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L670) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Heart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L671) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L672) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L673) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L674) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L675) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L676) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L677) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L678) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HeartPulse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L679) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Helicopter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L680) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelicopterSymbol = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L681) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelmetSafety = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L682) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HelmetUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L683) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HexagonNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L684) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HexagonNodesBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L685) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Highlighter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L686) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HillAvalanche = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L687) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HillRockslide = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L688) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hippo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L689) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HockeyPuck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L690) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HollyBerry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L691) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Horse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L692) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HorseHead = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L693) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hospital = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L694) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HospitalUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L695) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HotTubPerson = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L696) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hotdog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L697) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hotel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L698) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hourglass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L699) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassEnd = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L700) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L701) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HourglassStart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L702) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.House = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L703) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimney = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L704) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L705) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L706) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L707) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseChimneyWindow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L708) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L709) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L710) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L711) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseCrack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L712) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFire = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L713) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L714) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFloodWater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L715) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseFloodWaterCircleArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L716) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseLaptop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L717) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L718) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L719) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L720) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L721) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L722) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseMedicalFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L723) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseSignal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L724) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseTsunami = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L725) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HouseUser = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L726) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.HryvniaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L727) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Hurricane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L728) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.I = "I"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L729) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ICursor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L730) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IceCream = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L731) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Icicles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L732) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Icons = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L733) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdBadge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L734) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L735) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IdCardClip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L736) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Igloo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L737) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Image = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L738) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ImagePortrait = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L739) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Images = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L740) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Inbox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L741) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Indent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L742) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.IndianRupeeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L743) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Industry = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L744) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Infinity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L745) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Info = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L746) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Italic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L747) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.J = "J"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L748) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Jar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L749) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JarWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L750) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Jedi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L751) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JetFighter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L752) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JetFighterUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L753) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Joint = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L754) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.JugDetergent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L755) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.K = "K"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L756) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Kaaba = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L757) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Key = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L758) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Keyboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L759) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Khanda = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L760) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KipSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L761) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KitMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L762) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KitchenSet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L763) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.KiwiBird = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L764) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.L = "L"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L765) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandMineOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L766) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Landmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L767) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandmarkDome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L768) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LandmarkFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L769) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Language = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L770) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Laptop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L771) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopCode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L772) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopFile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L773) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LaptopMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L774) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LariSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L775) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LayerGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L776) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Leaf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L777) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LeftLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L778) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L779) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lemon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L780) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LessThan = "<"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L781) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LessThanEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L782) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LifeRing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L783) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lightbulb = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L784) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LinesLeaning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L785) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Link = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L786) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LinkSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L787) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LiraSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L788) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.List = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L789) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L790) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListOl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L791) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ListUl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L792) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LitecoinSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L793) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationArrow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L794) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationCrosshairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L795) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L796) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationPin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L797) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LocationPinLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L798) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L799) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LockOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L800) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Locust = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L801) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lungs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L802) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.LungsVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L803) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.M = "M"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L804) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Magnet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L805) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L806) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L807) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassChart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L808) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L809) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassLocation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L810) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L811) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlassPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L812) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ManatSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L813) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Map = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L814) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapLocation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L815) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapLocationDot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L816) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MapPin = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L817) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Marker = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L818) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L819) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsAndVenus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L820) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsAndVenusBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L821) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L822) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L823) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStrokeRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L824) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MarsStrokeUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L825) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L826) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlassCitrus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L827) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MartiniGlassEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L828) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mask = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L829) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MaskFace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L830) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MaskVentilator = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L831) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MasksTheater = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L832) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MattressPillow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L833) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Maximize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L834) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Medal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L835) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Memory = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L836) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Menorah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L837) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mercury = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L838) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Message = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L839) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Meteor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L840) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microchip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L841) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microphone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L842) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L843) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneLinesSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L844) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MicrophoneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L845) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Microscope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L846) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MillSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L847) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Minimize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L848) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Minus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L849) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mitten = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L850) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mobile = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L851) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L852) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileRetro = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L853) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileScreen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L854) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MobileScreenButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L855) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L856) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill1 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L857) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBill1Wave = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L858) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillTransfer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L859) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillTrendUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L860) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillWave = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L861) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBillWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L862) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyBills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L863) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L864) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MoneyCheckDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L865) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Monument = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L866) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Moon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L867) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MortarPestle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L868) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mosque = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L869) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mosquito = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L870) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MosquitoNet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L871) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Motorcycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L872) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mound = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L873) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Mountain = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L874) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MountainCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L875) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MountainSun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L876) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MugHot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L877) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.MugSaucer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L878) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Music = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L879) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.N = "N"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L880) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NairaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L881) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NetworkWired = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L882) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Neuter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L883) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Newspaper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L884) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NotEqual = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L885) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Notdef = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L886) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NoteSticky = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L887) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.NotesMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L888) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.O = "O"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L889) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ObjectGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L890) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ObjectUngroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L891) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.OilCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L892) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.OilWell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L893) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Om = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L894) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Otter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L895) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Outdent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L896) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.P = "P"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L897) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pager = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L898) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PaintRoller = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L899) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paintbrush = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L900) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Palette = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L901) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pallet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L902) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Panorama = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L903) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PaperPlane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L904) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paperclip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L905) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ParachuteBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L906) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paragraph = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L907) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Passport = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L908) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paste = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L909) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pause = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L910) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Paw = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L911) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Peace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L912) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L913) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenClip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L914) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenFancy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L915) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenNib = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L916) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenRuler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L917) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PenToSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L918) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pencil = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L919) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleArrows = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L920) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleCarryBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L921) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L922) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L923) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeoplePulling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L924) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleRobbery = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L925) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PeopleRoof = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L926) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PepperHot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L927) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Percent = "%"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L928) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Person = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L929) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonArrowDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L930) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonArrowUpFromLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L931) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBiking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L932) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBooth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L933) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBreastfeeding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L934) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L935) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L936) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonChalkboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L937) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L938) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L939) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L940) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L941) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleQuestion = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L942) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L943) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDigging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L944) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDotsFromLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L945) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L946) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDressBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L947) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonDrowning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L948) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonFalling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L949) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonFallingBurst = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L950) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHalfDress = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L951) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHarassing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L952) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonHiking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L953) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryPointing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L954) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryRifle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L955) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonMilitaryToPerson = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L956) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonPraying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L957) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonPregnant = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L958) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L959) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRifle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L960) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonRunning = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L961) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonShelter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L962) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkating = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L963) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkiing = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L964) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSkiingNordic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L965) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSnowboarding = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L966) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonSwimming = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L967) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonThroughWindow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L968) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L969) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingArrowLoopLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L970) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L971) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingDashedLineArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L972) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingLuggage = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L973) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PersonWalkingWithCane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L974) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PesetaSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L975) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PesoSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L976) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Phone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L977) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L978) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L979) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhoneVolume = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L980) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PhotoFilm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L981) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PiggyBank = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L982) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pills = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L983) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PizzaSlice = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L984) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaceOfWorship = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L985) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L986) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneArrival = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L987) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L988) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L989) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L990) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneDeparture = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L991) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L992) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L993) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlaneUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L994) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlantWilt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L995) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlateWheat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L996) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Play = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L997) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L998) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleBolt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L999) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1000) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1001) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1002) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCirclePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1003) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlugCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1004) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plus = "+"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1005) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PlusMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1006) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Podcast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1007) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Poo = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1008) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PooStorm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1009) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Poop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1010) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PowerOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1011) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Prescription = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1012) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PrescriptionBottle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1013) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PrescriptionBottleMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1014) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Print = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1015) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PumpMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1016) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PumpSoap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1017) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.PuzzlePiece = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1018) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Q = "Q"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1019) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Qrcode = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1020) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Question = "?"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1021) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.QuoteLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1022) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.QuoteRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1023) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.R = "R"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1024) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Radiation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1025) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Radio = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1026) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rainbow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1027) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RankingStar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1028) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Receipt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1029) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RecordVinyl = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1030) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleAd = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1031) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1032) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RectangleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1033) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Recycle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1034) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Registered = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1035) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Repeat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1036) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Reply = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1037) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ReplyAll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1038) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Republican = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1039) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Restroom = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1040) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Retweet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1041) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ribbon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1042) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightFromBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1043) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1044) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1045) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RightToBracket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1046) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ring = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1047) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Road = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1048) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadBarrier = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1049) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadBridge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1050) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1051) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1052) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1053) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1054) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RoadSpikes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1055) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Robot = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1056) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rocket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1057) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rotate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1058) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RotateLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1059) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RotateRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1060) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Route = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1061) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1062) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RubleSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1063) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rug = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1064) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ruler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1065) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerCombined = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1066) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerHorizontal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1067) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RulerVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1068) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupeeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1069) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupiahSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1070) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.S = "S"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1071) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SackDollar = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1072) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SackXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1073) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sailboat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1074) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Satellite = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1075) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SatelliteDish = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1076) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleBalanced = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1077) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleUnbalanced = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1078) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScaleUnbalancedFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1079) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.School = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1080) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1081) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1082) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolCircleXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1083) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolFlag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1084) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SchoolLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1085) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Scissors = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1086) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Screwdriver = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1087) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScrewdriverWrench = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1088) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Scroll = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1089) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ScrollTorah = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1090) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SdCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1091) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Section = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1092) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Seedling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1093) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Server = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1094) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shapes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1095) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Share = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1096) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShareFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1097) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShareNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1098) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SheetPlastic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1099) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShekelSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1100) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1101) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldCat = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1102) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldDog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1103) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldHalved = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1104) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldHeart = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1105) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShieldVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1106) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ship = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1107) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shirt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1108) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShoePrints = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1109) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1110) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShopLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1111) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShopSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1112) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shower = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1113) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shrimp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1114) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shuffle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1115) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ShuttleSpace = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1116) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SignHanging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1117) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Signal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1118) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Signature = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1119) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SignsPost = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1120) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SimCard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1121) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sink = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1122) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sitemap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1123) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Skull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1124) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SkullCrossbones = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1125) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Slash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1126) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sleigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1127) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sliders = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1128) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Smog = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1129) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Smoking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1130) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowflake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1131) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowman = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1132) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Snowplow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1133) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Soap = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1134) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Socks = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1135) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SolarPanel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1136) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sort = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1137) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SortDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1138) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SortUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1139) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spa = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1140) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SpaghettiMonsterFlying = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1141) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SpellCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1142) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spider = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1143) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spinner = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1144) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Splotch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1145) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Spoon = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1146) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SprayCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1147) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SprayCanSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1148) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Square = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1149) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareArrowUpRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1150) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareBinary = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1151) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1152) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1153) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1154) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCaretUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1155) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1156) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareEnvelope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1157) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1158) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareH = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1159) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1160) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareNfi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1161) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareParking = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1162) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1163) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePersonConfined = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1164) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePhone = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1165) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePhoneFlip = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1166) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1167) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePollHorizontal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1168) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquarePollVertical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1169) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareRootVariable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1170) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareRss = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1171) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareShareNodes = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1172) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareUpRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1173) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1174) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SquareXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1175) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StaffSnake = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1176) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stairs = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1177) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stamp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1178) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stapler = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1179) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Star = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1180) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarAndCrescent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1181) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1182) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarHalfStroke = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1183) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarOfDavid = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1184) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StarOfLife = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1185) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SterlingSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1186) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stethoscope = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1187) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stop = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1188) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stopwatch = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1189) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stopwatch20 = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1190) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Store = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1191) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StoreSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1192) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.StreetView = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1193) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Strikethrough = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1194) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stroopwafel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1195) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Subscript = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1196) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Suitcase = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1197) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SuitcaseMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1198) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SuitcaseRolling = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1199) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Sun = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1200) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.SunPlantWilt = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1201) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Superscript = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1202) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Swatchbook = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1203) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Synagogue = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1204) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Syringe = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1205) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.T = "T"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1206) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Table = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1207) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCells = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1208) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsColumnLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1209) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsLarge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1210) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsRowLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1211) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableCellsRowUnlock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1212) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableColumns = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1213) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableList = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1214) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TableTennisPaddleBall = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1215) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tablet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1216) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TabletButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1217) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TabletScreenButton = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1218) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tablets = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1219) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TachographDigital = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1220) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1221) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tags = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1222) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tape = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1223) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tarp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1224) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TarpDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1225) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Taxi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1226) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Teeth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1227) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TeethOpen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1228) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureArrowDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1229) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1230) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1231) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureFull = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1232) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureHalf = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1233) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1234) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureLow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1235) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureQuarter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1236) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TemperatureThreeQuarters = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1237) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TengeSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1238) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tent = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1239) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowDownToLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1240) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1241) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowTurnLeft = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1242) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TentArrowsDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1243) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tents = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1244) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Terminal = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1245) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextHeight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1246) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1247) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TextWidth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1248) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Thermometer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1249) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbsDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1250) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbsUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1251) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Thumbtack = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1252) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ThumbtackSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1253) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Ticket = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1254) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TicketSimple = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1255) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Timeline = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1256) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToggleOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1257) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToggleOn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1258) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Toilet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1259) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPaper = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1260) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPaperSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1261) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletPortable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1262) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToiletsPortable = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1263) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Toolbox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1264) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tooth = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1265) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.ToriiGate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1266) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tornado = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1267) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerBroadcast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1268) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerCell = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1269) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TowerObservation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1270) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tractor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1271) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trademark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1272) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrafficLight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1273) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trailer = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1274) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Train = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1275) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrainSubway = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1276) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrainTram = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1277) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Transgender = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1278) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1279) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1280) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashCan = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1281) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrashCanArrowUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1282) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tree = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1283) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TreeCity = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1284) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TriangleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1285) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trophy = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1286) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Trowel = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1287) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TrowelBricks = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1288) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Truck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1289) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckArrowRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1290) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckDroplet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1291) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFast = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1292) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckField = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1293) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFieldUn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1294) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckFront = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1295) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMedical = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1296) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMonster = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1297) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckMoving = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1298) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckPickup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1299) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckPlane = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1300) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TruckRampBox = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1301) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1302) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurkishLiraSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1303) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurnDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1304) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.TurnUp = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1305) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Tv = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1306) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.U = "U"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1307) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Umbrella = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1308) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UmbrellaBeach = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1309) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Underline = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1310) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UniversalAccess = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1311) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Unlock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1312) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UnlockKeyhole = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1313) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpDown = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1314) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpDownLeftRight = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1315) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpLong = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1316) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpRightAndDownLeftFromCenter = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1317) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpRightFromSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1318) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Upload = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1319) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.User = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1320) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserAstronaut = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1321) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1322) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserClock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1323) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserDoctor = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1324) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1325) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGraduate = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1326) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserGroup = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1327) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserInjured = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1328) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLarge = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1329) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLargeSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1330) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserLock = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1331) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserMinus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1332) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserNinja = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1333) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserNurse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1334) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserPen = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1335) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserPlus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1336) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserSecret = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1337) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserShield = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1338) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1339) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserTag = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1340) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserTie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1341) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UserXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1342) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Users = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1343) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersBetweenLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1344) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersGear = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1345) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersLine = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1346) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersRays = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1347) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersRectangle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1348) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1349) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.UsersViewfinder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1350) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Utensils = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1351) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.V = "V"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1352) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VanShuttle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1353) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vault = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1354) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VectorSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1355) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Venus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1356) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VenusDouble = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1357) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VenusMars = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1358) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vest = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1359) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VestPatches = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1360) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vial = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1361) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VialCircleCheck = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1362) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VialVirus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1363) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vials = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1364) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Video = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1365) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VideoSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1366) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Vihara = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1367) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Virus = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1368) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusCovid = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1369) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusCovidSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1370) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VirusSlash = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1371) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Viruses = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1372) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Voicemail = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1373) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Volcano = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1374) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Volleyball = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1375) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeHigh = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1376) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeLow = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1377) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeOff = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1378) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VolumeXmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1379) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.VrCardboard = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1380) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.W = "W"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1381) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WalkieTalkie = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1382) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wallet = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1383) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandMagic = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1384) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandMagicSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1385) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WandSparkles = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1386) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Warehouse = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1387) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Water = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1388) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WaterLadder = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1389) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WaveSquare = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1390) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WebAwesome = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1391) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WeightHanging = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1392) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WeightScale = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1393) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheatAwn = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1394) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheatAwnCircleExclamation = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1395) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wheelchair = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1396) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WheelchairMove = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1397) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WhiskeyGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1398) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wifi = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1399) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wind = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1400) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowMaximize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1401) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowMinimize = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1402) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WindowRestore = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1403) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineBottle = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1404) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineGlass = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1405) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WineGlassEmpty = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1406) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.WonSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1407) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Worm = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1408) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Wrench = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1409) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.X = "X"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1410) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.XRay = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1411) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Xmark = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1412) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.XmarksLines = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1413) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Y = "Y"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1414) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.YenSign = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1415) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.YinYang = ""`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1416) | Inherited or compiler-provided contract. | — | — | +| [`public const string Inno.Adapter.Presentation.ImGui.ImGuiIcon.Z = "Z"`](../../../backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ImGuiIcon.cs#L1417) | Inherited or compiler-provided contract. | — | — | ## 项目依赖 -- [Inno.Core.Execution](../../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.Sdl3](../Sdl3/Inno.Native.Sdl3.md):公开引用边界由实际签名核对。 -- [Inno.Native.ImGui](Inno.Native.ImGui.md):公开引用边界由实际签名核对。 -- `BGCS.Runtime`:通过固定版本 NuGet 包提供互操作运行时,不引用生成器。 -- [Inno.Adapter.Platform](../../platform/Inno.Adapter.Platform.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Presentation](../../platform/Inno.Adapter.Presentation.md):公开引用边界由实际签名核对。 -- [Inno.Platform](../../platform/Inno.Platform.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Platform.Sdl3](../Sdl3/Inno.Adapter.Platform.Sdl3.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Native.Sdl3` | 公开签名依赖 | +| `Inno.Native.ImGui` | 公开签名依赖 | +| `Inno.Adapter.Platform` | 公开签名依赖 | +| `Inno.Adapter.Presentation` | 公开签名依赖 | +| `Inno.Platform` | 公开签名依赖 | +| `Inno.Adapter.Platform.Sdl3` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/build/Inno.Build.Cli.md b/docs/build/Inno.Build.Cli.md index c887df676..5f86dd3d5 100644 --- a/docs/build/Inno.Build.Cli.md +++ b/docs/build/Inno.Build.Cli.md @@ -29,6 +29,8 @@ dotnet run --project entry/InnoEngine.Build/InnoEngine.Build.csproj -- verify . SDK 生成的 `Inno.Build.Cli.deps.json` 随开发入口复制。该清单属于 CLI 自己的显式创作闭包,使库宿主和开发入口发现同一组脚本契约;不在编译器中维护领域类型名单。 +Assets、Shaders 与通用 Graph 创作 API 直接引用其 feature 或可复用 presentation 项目,不引用具体 Panel 来间接携带脚本契约。命令行编译无需实例化这些 UI;实际绘制仍由 Editor 的 session 和 Panel 负责。 + CLI 与平台 Editor 组合库显式启用共同的 `InnoExposeRuntimeClosure` SDK 构建规则。清单与实际运行依赖随引用复制,不改变项目的 `PrivateAssets="compile"` 边界,也不扫描输出目录来猜测扩展。 | 命令 | 行为 | @@ -88,14 +90,60 @@ Support Pack 准备前缓存、缓存预算回收和完整性能矩阵尚未验 ## 当前源码公开 API 清单 -当前声明由源码语义清单核对,包含 public 和可供外部派生实现使用的 protected 成员。构建契约不作为游戏脚本导出;线程、失败与所有权要求见前文及每项 XML。 - -### `Inno.Build.Cli.BuildApplication` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -| 声明 | 行为、参数与结果 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public static class Inno.Build.Cli.BuildApplication`](../../build/cli/Inno.Build.Cli/BuildApplication.cs#L11) | Executes explicit engine and project build commands for a caller-owned development lifetime. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Cli.BuildApplication.Run(string[] arguments, System.Threading.CancellationToken cancellationToken)`](../../build/cli/Inno.Build.Cli/BuildApplication.cs#L28) | Runs one build command without taking ownership of the caller's cancellation source.
`arguments`: The command and its documented options; help prints the supported command names.
`cancellationToken`: Cancels owned work and preserves completed publications according to their pipeline contracts. | Zero on success, two on cancellation, or one on a command or execution failure. | `System.ArgumentNullException`: The argument array is absent. | + +## 项目依赖 + +| 工程 | 边界 | | --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Cli.BuildApplication.Run(string[] arguments, System.Threading.CancellationToken cancellationToken)`](../../build/cli/Inno.Build.Cli/BuildApplication.cs#L28) | Runs one build command without taking ownership of the caller's cancellation source.
`arguments`:The command and its documented options; help prints the supported command names.
`cancellationToken`:Cancels owned work and preserves completed publications according to their pipeline contracts.
返回:Zero on success, two on cancellation, or one on a command or execution failure.
T:System.ArgumentNullException:The argument array is absent. | -| [`public static class Inno.Build.Cli.BuildApplication`](../../build/cli/Inno.Build.Cli/BuildApplication.cs#L11) | Executes explicit engine and project build commands for a caller-owned development lifetime. | +| `Inno.Build.Bindings` | 实现依赖;不传递 compile surface | +| `Inno.Build.Composition` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains` | 实现依赖;不传递 compile surface | +| `Inno.Build` | 实现依赖;不传递 compile surface | +| `Inno.Build.Managed` | 实现依赖;不传递 compile surface | +| `Inno.Build.Distribution.Standard` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains.Bgfx` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.UI.RmlUi.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains.Bgfx.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains.Bgfx.Tools` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Modules.DotNet` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Serialization.DotNet` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Annotations` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Inspection` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Graph` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Shaders.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Graph.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Panel.Hierarchy` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Panel.Inspector` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Plugins.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Animation.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Audio.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Text.Assets` | 实现依赖;不传递 compile surface | +| `Inno.UI.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 实现依赖;不传递 compile surface | +| `Inno.Tooling.Architecture` | 实现依赖;不传递 compile surface | diff --git a/docs/build/Inno.Build.Distribution.Standard.md b/docs/build/Inno.Build.Distribution.Standard.md index 1d9b3c4d5..65e0bb4df 100644 --- a/docs/build/Inno.Build.Distribution.Standard.md +++ b/docs/build/Inno.Build.Distribution.Standard.md @@ -2,6 +2,10 @@ [分类索引](README.md) · [Wiki 首页](../README.md) · [平台归属与扩展](../architecture/PLATFORM_EXTENSION_GUIDE.md) +## Shader 编译工作区 + +`StandardBuildCaches.CreateShaderWorkspaceAsync` 在明确登记的 `artifacts/build-tools/shaders/requests` 创建 Core 进程目录。Task 与 CLI 持有同一契约至导入、编译和校验全部完成;结束释放 payload,claim 和叶锁由既有缓存维护的独占 owner 协议退休。编译库只消费借用工作区,不选择全局临时目录,也不另外实现清理器。 + ## 职责与边界 唯一标准发行注册入口,将已实现的平台贡献、共享 DotNet 托管部署和产品 Native 闭包组合起来。它是具体组合,不是中立领域。 @@ -57,69 +61,51 @@ Windows Editor 导出 Browser 游戏时,离线工具仍需能在 Windows 执 ## 当前源码公开 API 清单 -当前声明由源码语义清单核对,包含 public 和可供外部派生实现使用的 protected 成员。构建契约不作为游戏脚本导出;线程、失败与所有权要求见前文及每项 XML。 - -### `Inno.Build.Distribution.Standard.StandardBuildCaches` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Collections.Generic.IReadOnlyList Inno.Build.Distribution.Standard.StandardBuildCaches.CreateOwners(string engineRoot, System.Collections.Generic.IReadOnlyList components)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs#L30) | Registers exact Native, binding and Support Pack ownership recorded by their publishers. Unrecorded directories remain outside the cleanup authority.
`engineRoot`:The absolute engine checkout containing the artifact roots.
`components`:The actual registered component owners; shared toolchain roots are registered once.
返回:Immutable owner registrations with current-generation and transitive dependency protection.
T:System.ArgumentException:The root is blank or relative. | -| [`public static class Inno.Build.Distribution.Standard.StandardBuildCaches`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs#L12) | Registers the standard distribution's actual immutable artifact publishers for coordinated cleanup. | - -### `Inno.Build.Distribution.Standard.StandardBuildDistribution` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static Inno.Build.Distribution.Standard.StandardBuildDistribution Inno.Build.Distribution.Standard.StandardBuildDistribution.Create(Inno.Build.Composition.BuildCompositionContext context, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L52) | Composes one explicit binding per implemented platform without resolving tools.
`context`:The explicitly selected SDK and execution host.
`tools`:Borrowed frozen content-compilation tools, or null to consume this application's explicit deployment.
返回:A frozen standard distribution with backend configuration bound to the same platform identities. | -| [`public Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile Inno.Build.Distribution.Standard.StandardBuildDistribution.ResolveShaderTarget(Inno.Build.BuildTargetId target)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L91) | Resolves explicitly registered shader configuration independently of tool execution target.
`target`:The artifact's actual publication target.
返回:The borrowed immutable configuration; unavailable targets fail before native preparation.
T:System.NotSupportedException:The distribution has no shader configuration for this target. | -| [`public Inno.Build.Composition.BuildDistribution Inno.Build.Distribution.Standard.StandardBuildDistribution.build { get; }`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L38) | Gets the neutral distribution borrowed by product hosts and shared pipeline mechanisms. | -| [`public sealed class Inno.Build.Distribution.Standard.StandardBuildDistribution`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L23) | Binds platform publication and backend configuration once at the standard distribution boundary. | - -### `Inno.Build.Distribution.Standard.StandardBuildEnvironment` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static Inno.Build.Composition.BuildCompositionContext Inno.Build.Distribution.Standard.StandardBuildEnvironment.Capture(string applicationDirectory, Inno.Build.BuildTargetId toolsTarget, string? dotnetHost = null)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs#L34) | Resolves the selected managed host and captures execution capabilities at the composition boundary.
`applicationDirectory`:The product or tool's installed location used for source provisioning.
`toolsTarget`:The native target explicitly selected for executable build tools, independently of exported games.
`dotnetHost`:An explicit managed SDK executable, or null to use the current tool process's selected host.
返回:A frozen execution context; publication targets must be selected independently.
T:System.PlatformNotSupportedException:The distribution has no build execution integration for this system or processor.
T:System.IO.FileNotFoundException:The selected managed SDK host cannot be located. | -| [`public static class Inno.Build.Distribution.Standard.StandardBuildEnvironment`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs#L11) | Captures the actual machine running distribution tools without choosing an output target. | - -### `Inno.Build.Distribution.Standard.StandardBuildHosts` - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static Inno.Build.Toolchains.BuildHostDescriptor Inno.Build.Distribution.Standard.StandardBuildHosts.Capture()`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildHosts.cs#L22) | Captures the current execution machine from registered platform providers.
返回:The selected immutable host and execution capabilities.
T:System.PlatformNotSupportedException:No registered provider supports the current machine. | -| [`public static class Inno.Build.Distribution.Standard.StandardBuildHosts`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildHosts.cs#L11) | Registers supported build hosts without duplicating their execution facts. | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Distribution.Standard.StandardBuildCaches.CreateShaderWorkspaceAsync(string engineRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs#L39) | Creates a shader compilation workspace in the registered process-owned build cache.
`engineRoot`: The checkout owning cache maintenance and the source-independent compiler request.
`cancellationToken`: Cancels registration or ownership acquisition before compilation begins. | A fresh directory lease; the caller must retain it until compilation finishes and dispose it afterward. Remaining claim metadata is retired by the same cache maintenance service. | `System.IO.IOException`: The cache boundary or operation ownership cannot be established.
`System.OperationCanceledException`: The request was canceled before workspace ownership was returned.
`System.ArgumentException`: The checkout root is blank or relative. | +| [`public static class Inno.Build.Distribution.Standard.StandardBuildCaches`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs#L12) | Registers the standard distribution's actual immutable artifact publishers for coordinated cleanup. | — | — | +| [`public static System.Collections.Generic.IReadOnlyList Inno.Build.Distribution.Standard.StandardBuildCaches.CreateOwners(string engineRoot, System.Collections.Generic.IReadOnlyList components)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildCaches.cs#L30) | Registers exact Native, binding and Support Pack ownership recorded by their publishers. Unrecorded directories remain outside the cleanup authority.
`engineRoot`: The absolute engine checkout containing the artifact roots.
`components`: The actual registered component owners; shared toolchain roots are registered once. | Immutable owner registrations with current-generation and transitive dependency protection. | `System.ArgumentException`: The root is blank or relative. | +| [`public sealed class Inno.Build.Distribution.Standard.StandardBuildDistribution`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L23) | Binds platform publication and backend configuration once at the standard distribution boundary. | — | — | +| [`public Inno.Build.Composition.BuildDistribution Inno.Build.Distribution.Standard.StandardBuildDistribution.build { get; }`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L38) | Gets the neutral distribution borrowed by product hosts and shared pipeline mechanisms. | — | — | +| [`public static Inno.Build.Distribution.Standard.StandardBuildDistribution Inno.Build.Distribution.Standard.StandardBuildDistribution.Create(Inno.Build.Composition.BuildCompositionContext context, Inno.Build.Toolchains.Bgfx.Tools.ToolRunner? tools = null)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L52) | Composes one explicit binding per implemented platform without resolving tools.
`context`: The explicitly selected SDK and execution host.
`tools`: Borrowed frozen content-compilation tools, or null to consume this application's explicit deployment. | A frozen standard distribution with backend configuration bound to the same platform identities. | — | +| [`public Inno.Build.Toolchains.Bgfx.Tools.BgfxShaderTargetProfile Inno.Build.Distribution.Standard.StandardBuildDistribution.ResolveShaderTarget(Inno.Build.BuildTargetId target)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildDistribution.cs#L91) | Resolves explicitly registered shader configuration independently of tool execution target.
`target`: The artifact's actual publication target. | The borrowed immutable configuration; unavailable targets fail before native preparation. | `System.NotSupportedException`: The distribution has no shader configuration for this target. | +| [`public static class Inno.Build.Distribution.Standard.StandardBuildEnvironment`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs#L12) | Captures the actual machine running distribution tools without choosing an output target. | — | — | +| [`public static Inno.Build.Composition.BuildCompositionContext Inno.Build.Distribution.Standard.StandardBuildEnvironment.Capture(string applicationDirectory, Inno.Build.BuildTargetId toolsTarget, string? dotnetHost = null)`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildEnvironment.cs#L36) | Resolves the selected managed host and captures execution capabilities at the composition boundary.
`applicationDirectory`: The product or tool's installed location used for source provisioning.
`toolsTarget`: The native target explicitly selected for executable build tools, independently of exported games.
`dotnetHost`: An explicit SDK executable, or null to use DOTNET_HOST_PATH, the loaded shared runtime's muxer, then normal executable discovery when this process has no shared runtime installation. | A frozen execution context; publication targets must be selected independently. | `System.PlatformNotSupportedException`: The distribution has no build execution integration for this system or processor.
`System.IO.FileNotFoundException`: The selected managed SDK host cannot be located. | +| [`public static class Inno.Build.Distribution.Standard.StandardBuildHosts`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildHosts.cs#L11) | Registers supported build hosts without duplicating their execution facts. | — | — | +| [`public static Inno.Build.Toolchains.BuildHostDescriptor Inno.Build.Distribution.Standard.StandardBuildHosts.Capture()`](../../build/distributions/Inno.Build.Distribution.Standard/StandardBuildHosts.cs#L22) | Captures the current execution machine from registered platform providers. | The selected immutable host and execution capabilities. | `System.PlatformNotSupportedException`: No registered provider supports the current machine. | ## 项目依赖 -- [Inno.Build.Bindings](Inno.Build.Bindings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Integration.WindowsX64.Bgfx](../platform/WindowsX64/Inno.Integration.WindowsX64.Bgfx.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Integration.MacOSArm64.Bgfx](../platform/MacOSArm64/Inno.Integration.MacOSArm64.Bgfx.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Integration.BrowserWasm32.Bgfx](../platform/BrowserWasm32/Inno.Integration.BrowserWasm32.Bgfx.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.Bgfx.Tools](../backends/Bgfx/Inno.Build.Toolchains.Bgfx.Tools.md):公开引用边界由实际签名核对。 -- [Inno.Build.Composition](Inno.Build.Composition.md):公开引用边界由实际签名核对。 -- [Inno.Build.Managed.DotNet](../backends/DotNet/Inno.Build.Managed.DotNet.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.WindowsX64](../platform/WindowsX64/Inno.Build.WindowsX64.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.MacOSArm64](../platform/MacOSArm64/Inno.Build.MacOSArm64.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.BrowserWasm32](../platform/BrowserWasm32/Inno.Build.BrowserWasm32.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.Bgfx](../backends/Bgfx/Inno.Build.Toolchains.Bgfx.md):公开引用边界由实际签名核对。 -- [Inno.Build.Toolchains.ImGui](../backends/ImGui/Inno.Build.Toolchains.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.ImGuizmo](../backends/ImGui/Inno.Build.Toolchains.ImGuizmo.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.MiniAudio](../backends/MiniAudio/Inno.Build.Toolchains.MiniAudio.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.Sdl3](../backends/Sdl3/Inno.Build.Toolchains.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.Text](../backends/Text/Inno.Build.Toolchains.Text.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains.UI](../backends/RmlUi/Inno.Build.Toolchains.UI.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Toolchains](Inno.Build.Toolchains.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Native.LibraryLoading` | 实现依赖;不传递 compile surface | +| `Inno.Build.Windows.Host` | 实现依赖;不传递 compile surface | +| `Inno.Build.Bindings` | 实现依赖;不传递 compile surface | +| `Inno.Integration.WindowsX64.Bgfx` | 实现依赖;不传递 compile surface | +| `Inno.Integration.MacOSArm64.Bgfx` | 实现依赖;不传递 compile surface | +| `Inno.Integration.BrowserWasm32.Bgfx` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains.Bgfx.Tools` | 公开签名依赖 | +| `Inno.Build.Composition` | 公开签名依赖 | +| `Inno.Build.Managed.DotNet` | 实现依赖;不传递 compile surface | +| `Inno.Integration.WindowsX64.Build` | 实现依赖;不传递 compile surface | +| `Inno.Integration.MacOSArm64.Build` | 实现依赖;不传递 compile surface | +| `Inno.Integration.BrowserWasm32.Build` | 实现依赖;不传递 compile surface | +| `Inno.Build.Toolchains.Bgfx` | 公开签名依赖 | +| `Inno.Build.Toolchains` | 公开签名依赖 | + +本轮新增公开工作区返回值直接使用 [Inno.Core.IO](../core/Inno.Core.IO.md) 的目录所有权契约;对应工程直接声明该公开依赖。 diff --git a/docs/build/Inno.Build.TaskHosting.md b/docs/build/Inno.Build.TaskHosting.md index 65c2e34b7..0ee536c70 100644 --- a/docs/build/Inno.Build.TaskHosting.md +++ b/docs/build/Inno.Build.TaskHosting.md @@ -10,6 +10,8 @@ owned Restore 使用 NuGet 标准属性 `ExcludeRestorePackageImports=true`;Ta 工程使用标准 PackageReference 和 SDK Restore。输入快照读取实际求值的 assets 路径及包内容,不回查另一配置的默认 obj;独立 ArtifactsPath 隔离所属还原与编译输出。工具保留其真实 SDK 输入,规则与更新方式见[依赖还原](DEPENDENCY_SUBMISSION.md)。 +最早的 Core.IO 引导采用独立、中立的 SDK 编译 scope。有调用方 `ArtifactsPath` 时,其位置为该 owner 下的 `.inno-task-foundation`;普通输出使用 Task artifacts 下固定的 `foundation`。它是按配置复用的 SDK 中间输出,不是每次操作发布一个新 generation。引导请求继续剥离产品 TFM、RID、Browser、AOT 和发布参数;不会以不同全局属性写入调用方同一个可变 Core.IO 输出。随后仍由原有 Core.IO 协议声明 publisher owner,并进行私有加载、冻结、校验和原子发布。 + ## 职责与边界 这是 MSBuild 引导边界的库,负责协调 Task 运行闭包的编译、发布及退休,并在读取保护期间复制生成绑定。它依赖 Core IO、中立 Toolchains 与 MSBuild 契约, @@ -21,6 +23,8 @@ Solution 命令构建通过 `Directory.Solution.targets` 为同一次构建传 owner 表示本次执行的所有权,其随机值本身不进入完整 Task runtime 的输入指纹。请求求值仍保留该属性;若导入文件、源码选择或 `DefineConstants` 等实际编译输入由它改变,这些求值结果仍参与指纹,不能误用上次结果。Design-time Build 不准备 publisher。 IDE 可以把已经求值的 `ProjectInstance` 交给另一个 MSBuild worker。worker 重建 `UsingTask` 注册时可能再次求值,因此不能假设其随机加载身份仍等于传入的普通属性。共同 targets 使用 SDK inline task 捕获任务注册阶段的加载身份,再令 publisher 复制、runtime 发布与后续输入使用该身份。加载路径仍按实际注册隔离,不依赖 IDE 设置、固定机器路径或预先存在的缓存。 最小 owner 引导消费 SDK Build 实际返回的 Foundation 程序集路径,并保留调用方明确选择的 `ArtifactsPath`,不拼接目标框架或强制写入普通 bin。它从已构建文件读取字节执行公开协议,不让长寿命 IDE 构建宿主锁住 SDK 输出;下一次编译可以正常更新该文件。此阶段只交换 BCL 字符串与 Task,不把 Foundation 类型或委托保存到共享注册表。 + +生成器通过 SDK `AdditionalTargetFrameworkInfoProperty` 声明自身的 `IsRoslynComponent` 角色。同一工具无论经 Analyzer、脚本编译器的私有运行引用,还是 SDK 追加的传递引用到达,都使用同一中立工具求值;产品参数、父级 `BuildProjectReferences` 和 Task 操作 owner 不属于工具编译输入。`ArtifactsPath` 和 Configuration 仍保留,正式源码与私有 Task 输出继续隔离。直接对生成器显式传入的 RID 仍由 SDK 正常求值,此规则只规范消费工具的项目引用,不改写用户的直接请求。 `BuildTaskRuntimeTask` 使用同一 `artifacts/build-tools/tasks/workspaces/shared` 根的 `FileLease` 协调完整 Task 项目编译;等待时通过 MSBuild `Yield`/`Reacquire` 释放节点,失败、取消或异常都释放写 lease。 私有 Task 编译明确禁用共享 C# compiler server;可复用源码 workspace 的 SDK 子进程同样使用 `--disable-build-servers`。编译完成后不会让另一个长寿命编译宿主持有 workspace 中的生成器 DLL。普通 IDE 的编译服务器配置不由此改变。 SDK 与项目构建仍由当前 MSBuild engine 执行,保留明确属性和移除产品属性的机制,不创建第二个 CLI 或通过 PATH 重新选择 SDK。完整 Task 项目返回实际运行文件的逻辑路径及 SHA-256。 diff --git a/docs/build/Inno.Build.Toolchains.md b/docs/build/Inno.Build.Toolchains.md index f737a7e26..344e0bcdf 100644 --- a/docs/build/Inno.Build.Toolchains.md +++ b/docs/build/Inno.Build.Toolchains.md @@ -158,24 +158,191 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 缓存清理的 entry 路由仅准备 Toolchains 及 Foundation 闭包,不加载完整 CLI、BGCS 生成器、Editor 或第三方后端。产品清理需要产品预检,仍属于完整 Build 命令。 -## 当前源码公开 API 清单 -当前声明由源码语义清单核对,包含 public 和可供外部派生实现使用的 protected 成员。构建契约不作为游戏脚本导出;线程、失败与所有权要求见前文及每项 XML。 -### `Inno.Build.Toolchains.BuildArtifactManifest` +## 缓存管理公开契约 - - - -| 声明 | 行为、参数与结果 | +| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| --- | --- | --- | --- | +| [`public static Inno.Build.Toolchains.BuildCacheCleanupPlan Inno.Build.Toolchains.BuildCacheCleanupPlan.Create(string workspace, System.Collections.Generic.IReadOnlyList owners, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L84) | 明确注册 owner 的物理边界、协调路径和借用的清单/保护根 callbacks。 | Callbacks 在本次清理期间有效;发布及根更新使用同一协调 lease。 | 不得通过目录名猜 owner;拒绝重复、越界或重叠注册。 | + +### 进程加载保护 + + +| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| --- | --- | --- | --- | + + +## 进程与构建所有权 API + +| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| --- | --- | --- | --- | +| [`public Inno.Build.Toolchains.BuildCacheArtifactDescriptor.BuildCacheArtifactDescriptor(string directory, string leasePath, string? targetId, long bytes, System.DateTime lastUseUtc, System.Collections.Generic.IReadOnlyList dependencies, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L48) | 冻结目录、目标、角色、协调路径、字节、UTC 使用时间与依赖;大小只服务预算。 | 描述不接管 producer 或 reader lease。 | 绝对路径、非负大小、UTC 时间和有效角色必须成立。 | +| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCacheArtifactDescriptor.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L104) | Gets the declared lifetime used for retirement and preferred-workspace accounting. | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`public enum Inno.Build.Toolchains.BuildCacheArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L6) | 明确区分不可变产物、可复用 workspace 与进程临时目录。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ImmutableArtifact = 0`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L11) | 完整产物由当前产品/Pack/活动读取保护。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ReusableWorkspace = 1`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L16) | 编译活动时保护;无活动时可按预算退休,即使最近使用。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ProcessWorkingDirectory = 2`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L21) | 进程活动或 Unknown 时保护,退出后可退休。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`public Inno.Build.Toolchains.BuildCachePublisher.BuildCachePublisher(string directory, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L34) | 为明确 cache 根选择生命周期,之后通过 Describe 提供登记与保护关系。 | 构造不取得 lease;发布/读取交接由调用方持有 activity 与产物保护。 | 根必须绝对且不越界;不认领未登记目录。 | +| [`public void Inno.Build.Toolchains.BuildCachePublisher.BeginWorkingDirectory(string directory, string leasePath, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L168) | 调用者取得 activity 与 producer 独占 lease 后,先登记 reusable workspace,再允许 payload 写入。 | lease 仍由调用方持有;不永久固定闲置 workspace。 | 已有无登记目录拒绝认领;目标归属按显式参数记录。 | +| [`public Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.WithDependency(Inno.Build.Toolchains.NativeBuildInput input, string fingerprint)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L190) | 把实际依赖产物和指纹冻结进后端 recipe,保留选定工具;依赖产品仅在 recipe 构造期间借用。 | 不转移 NativeBuildProduct 的读取 leases。 | 没有声明转换的依赖步骤走正常冷路径,不能猜测链接参数。 | +| [`public Inno.Build.Toolchains.ProductNativeBuildStep.ProductNativeBuildStep(string id, Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options, System.Collections.Generic.IReadOnlyList dependencies, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? build, System.Func? createRecipe, System.Func deploymentPath, string? configuration = null, System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? bindDependencies = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L52) | Freezes a component step without embedding any backend list in the shared executor. | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | +| [`public System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? Inno.Build.Toolchains.ProductNativeBuildStep.bindDependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L134) | 把实际依赖产物和指纹冻结进后端 recipe,保留选定工具;依赖产品仅在 recipe 构造期间借用。 | 不转移 NativeBuildProduct 的读取 leases。 | 没有声明转换的依赖步骤走正常冷路径,不能猜测链接参数。 | + + + + + + + + + + + + + +有效 MSBuild 图的节点按项目路径与完整 global properties 排序,避免同路径多配置节点的无序枚举造成错误失效。参数、include 与编译项顺序继续参与身份;验证失败会报告文件或声明变化。内部 capture 只服务诊断,不构成新的公开缓存权威。 + +工具 publication 在固定输入前,使用同一 SDK 还原入口项目及 InnoBuildInputProject 声明的辅助输入项目。辅助项目来自有效图,清理的 global properties 保持一致;Capture/Verify 本身仍只读取,不通过缺 assets 的 fallback 掩盖错误。 + +### 输入路径别名与创作归属 + +同一校验阶段按最终物理路径去重哈希,但保留每次读取的原路径和 checkout 相对逻辑身份。macOS 的 `/var` 别名、目录链接与 Windows junction 不会把所属源码误记成外部 SDK 输入;固定编译副本仍按所属源码布局准备。独立锁等待、编译及提交后重新校验,不跨阶段复用内容结论。 + +## 原生生成编译契约 API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +| 当前签名 | 语义 | | --- | --- | -| [`public static bool Inno.Build.Toolchains.BuildArtifactManifest.IsComplete(string directory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, Inno.Build.Toolchains.NativeBuildContext? context = null)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L88) | Checks the identity, exact output file set and bytes before reusing a cached artifact.
`directory`:The published artifact directory.
`fingerprint`:The requested input identity.
`outputDirectories`:The same relative output subdirectories supplied when recording the artifact.
`context`:The optional operation collecting actual output integrity reads.
返回:True only for complete, unmodified outputs; false for a missing, malformed or mismatched manifest.
T:System.ArgumentException:An output path escapes the artifact root.
T:System.IO.IOException:An existing output cannot be read. | -| [`public static void Inno.Build.Toolchains.BuildArtifactManifest.Write(string directory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, Inno.Build.Toolchains.NativeBuildContext? context = null)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L40) | Records a completed staging tree before its owner publishes the directory.
`directory`:The complete staging root owned exclusively by the caller.
`fingerprint`:The immutable identity of every input used to produce these outputs.
`outputDirectories`:Relative output subdirectories included in this artifact; each must stay within the root.
`context`:The optional operation collecting independent output-read statistics.
T:System.ArgumentException:An identity or output path is invalid.
T:System.IO.IOException:An output or manifest cannot be read or written. | -| [`public static class Inno.Build.Toolchains.BuildArtifactManifest`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L15) | Validates complete build outputs against their input identity and recorded file hashes. | +| [`public sealed class Inno.Build.Toolchains.NativeBindingCompilation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | Describes a selected component's native requirements independently of its source generator. | +| [`public required Inno.Build.Toolchains.NativeBindingSourceRoot[] Inno.Build.Toolchains.NativeBindingCompilation.roots { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L27) | Gets root bindings whose identities are local to this component. | +| [`public required Inno.Build.Toolchains.NativeBindingSourceFile[] Inno.Build.Toolchains.NativeBindingCompilation.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L32) | Gets all exact input files; only files marked compile are translation units. | +| [`public required Inno.Build.Toolchains.NativeBindingIncludeDirectory[] Inno.Build.Toolchains.NativeBindingCompilation.includeDirectories { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L37) | Gets include requirements in their original search order. | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.defines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L42) | Gets ordered definitions for manifest translation units; upstream target configuration remains component-owned. | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.compilerDefines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L47) | Gets parser target macro facts; these must not override the consuming compiler's builtins. | +| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cxxStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L52) | Gets the minimum C++ standard, or zero when no C++ translation unit is required. | +| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L57) | Gets the minimum C standard, or zero when no C translation unit is required. | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.requiredExports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L62) | Gets the exact native symbols that the component must provide. | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportSymbols { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L67) | Gets static table entry points, or an empty array for dynamic-only bindings. | +| [`public required string Inno.Build.Toolchains.NativeBindingCompilation.staticExportAbi { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L72) | Gets the static table ABI digest, or an empty value when no static table exists. | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportNames { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L77) | Gets static table entries in address-resolution order. | +| [`public static Inno.Build.Toolchains.NativeBindingCompilation Inno.Build.Toolchains.NativeBindingCompilation.Load(string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L97) | Reads the current engine-owned compilation description from a selected native output. | +| [`public string Inno.Build.Toolchains.NativeBindingCompilation.ResolveRoot(string engineRoot, string nativeDirectory, string rootId)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L124) | Resolves a component-local root using the current checkout and selected output. | +| [`public void Inno.Build.Toolchains.NativeBindingCompilation.VerifyInputs(System.Func resolveRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L152) | Verifies the exact bytes at original or remapped roots before their use. | +| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingCompilation.GetInputs(string engineRoot, string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L179) | Enumerates exact file declarations for standalone or aggregate recipe fingerprinting. | +| [`public Inno.Build.Toolchains.NativeBindingCompilation.NativeBindingCompilation()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public sealed record Inno.Build.Toolchains.NativeBindingIncludeDirectory : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement. | +| [`public Inno.Build.Toolchains.NativeBindingIncludeDirectory.NativeBindingIncludeDirectory(string rootId, string relativePath, bool system, bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement. | +| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L19) | The component-local input root. | +| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L20) | The root-relative directory, or empty for the root itself. | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.system { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L21) | Whether this directory contains system headers. | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.framework { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L22) | Whether the compiler must use framework search semantics. | +| [`public override string Inno.Build.Toolchains.NativeBindingIncludeDirectory.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Returns a string that represents the current object. | +| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator !=(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator ==(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public override int Inno.Build.Toolchains.NativeBindingIncludeDirectory.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Serves as the default hash function. | +| [`public override bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Determines whether the specified object is equal to the current object. | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(Inno.Build.Toolchains.NativeBindingIncludeDirectory? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Indicates whether the current object is equal to another object of the same type. | +| [`public void Inno.Build.Toolchains.NativeBindingIncludeDirectory.Deconstruct(out string rootId, out string relativePath, out bool system, out bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceFile : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation. | +| [`public Inno.Build.Toolchains.NativeBindingSourceFile.NativeBindingSourceFile(string rootId, string relativePath, long length, string sha256, bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation. | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L22) | The component-local input root. | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L23) | The portable root-relative filename. | +| [`public long Inno.Build.Toolchains.NativeBindingSourceFile.length { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L24) | The expected byte length. | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.sha256 { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L25) | The lowercase SHA-256 content identity. | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.compile { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L26) | Whether this file is a translation unit rather than an include dependency. | +| [`public override string Inno.Build.Toolchains.NativeBindingSourceFile.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Returns a string that represents the current object. | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator !=(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator ==(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public override int Inno.Build.Toolchains.NativeBindingSourceFile.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Serves as the default hash function. | +| [`public override bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Determines whether the specified object is equal to the current object. | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(Inno.Build.Toolchains.NativeBindingSourceFile? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Indicates whether the current object is equal to another object of the same type. | +| [`public void Inno.Build.Toolchains.NativeBindingSourceFile.Deconstruct(out string rootId, out string relativePath, out long length, out string sha256, out bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceRoot : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract. | +| [`public Inno.Build.Toolchains.NativeBindingSourceRoot.NativeBindingSourceRoot(string id, string directory, bool generated, bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract. | +| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.id { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L19) | An ordinal identity scoped to this component's selected generation. | +| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.directory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L20) | The checkout-relative or absolute external location; empty for generated output. | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.generated { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L21) | Whether this root denotes the selected native output directory. | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.frozen { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L22) | Whether source files must be copied before compilation. | +| [`public override string Inno.Build.Toolchains.NativeBindingSourceRoot.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Returns a string that represents the current object. | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator !=(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator ==(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | +| [`public override int Inno.Build.Toolchains.NativeBindingSourceRoot.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Serves as the default hash function. | +| [`public override bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Determines whether the specified object is equal to the current object. | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(Inno.Build.Toolchains.NativeBindingSourceRoot? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Indicates whether the current object is equal to another object of the same type. | +| [`public void Inno.Build.Toolchains.NativeBindingSourceRoot.Deconstruct(out string id, out string directory, out bool generated, out bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | + + -### `Inno.Build.Toolchains.BuildArtifactOptions` +| 声明 | 行为与约束 | +| --- | --- | +| [`public required bool Inno.Build.Toolchains.NativeBindingGenerationDescriptor.usesRuntimeImports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L99) | Gets whether the generated context uses runtime-resolved imports instead of a module provider. This initialization choice is independent of static or shared native linkage. | +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + @@ -190,60 +357,14 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildArtifactOptions.BuildArtifactOptions(string buildDirName, System.Collections.Generic.IReadOnlyCollection libraryTokens, System.Collections.Generic.IReadOnlyCollection extensions, System.Collections.Generic.IReadOnlyCollection? requiredPathTokens, System.Func normalizeOutputName)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Defines the deterministic filter and naming policy used to collect one native product.
`buildDirName`:The dependency-relative directory searched for native build outputs.
`libraryTokens`:File-name tokens that identify artifacts belonging to the product.
`extensions`:The accepted native artifact extensions.
`requiredPathTokens`:Optional normalized path tokens used to reject unrelated intermediate files.
`normalizeOutputName`:The deterministic output naming policy. | -| [`public void Inno.Build.Toolchains.BuildArtifactOptions.Deconstruct(out string buildDirName, out System.Collections.Generic.IReadOnlyCollection libraryTokens, out System.Collections.Generic.IReadOnlyCollection extensions, out System.Collections.Generic.IReadOnlyCollection? requiredPathTokens, out System.Func normalizeOutputName)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.BuildArtifactOptions.Equals(Inno.Build.Toolchains.BuildArtifactOptions? other)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.BuildArtifactOptions.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.BuildArtifactOptions.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.BuildArtifactOptions.ToString()`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.BuildArtifactOptions.operator ==(Inno.Build.Toolchains.BuildArtifactOptions? left, Inno.Build.Toolchains.BuildArtifactOptions? right)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.BuildArtifactOptions.operator !=(Inno.Build.Toolchains.BuildArtifactOptions? left, Inno.Build.Toolchains.BuildArtifactOptions? right)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.Toolchains.BuildArtifactOptions.buildDirName { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L27) | The dependency-relative directory searched for native build outputs. | -| [`public System.Collections.Generic.IReadOnlyCollection Inno.Build.Toolchains.BuildArtifactOptions.extensions { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L29) | The accepted native artifact extensions. | -| [`public System.Collections.Generic.IReadOnlyCollection Inno.Build.Toolchains.BuildArtifactOptions.libraryTokens { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L28) | File-name tokens that identify artifacts belonging to the product. | -| [`public System.Func Inno.Build.Toolchains.BuildArtifactOptions.normalizeOutputName { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L31) | The deterministic output naming policy. | -| [`public System.Collections.Generic.IReadOnlyCollection? Inno.Build.Toolchains.BuildArtifactOptions.requiredPathTokens { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L30) | Optional normalized path tokens used to reject unrelated intermediate files. | -| [`public sealed record Inno.Build.Toolchains.BuildArtifactOptions : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Defines the deterministic filter and naming policy used to collect one native product.
`buildDirName`:The dependency-relative directory searched for native build outputs.
`libraryTokens`:File-name tokens that identify artifacts belonging to the product.
`extensions`:The accepted native artifact extensions.
`requiredPathTokens`:Optional normalized path tokens used to reject unrelated intermediate files.
`normalizeOutputName`:The deterministic output naming policy. | - -### `Inno.Build.Toolchains.BuildArtifactCopier` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static void Inno.Build.Toolchains.BuildArtifactCopier.CopyArtifacts(string buildRoot, string outputDir, string config, Inno.Build.Toolchains.BuildArtifactOptions options)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L56) | Copies all artifacts accepted by one product policy into its output directory.
`buildRoot`:The native dependency root containing the configured build directory.
`outputDir`:The destination directory receiving normalized artifacts.
`config`:The normalized native build configuration.
`options`:The immutable filtering and naming policy. | -| [`public static class Inno.Build.Toolchains.BuildArtifactCopier`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L37) | Copies filtered native outputs into the engine's rebuildable dependency store. | - -### `Inno.Build.Toolchains.BuildArtifactReadLease` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildArtifactReadLease.AcquireAsync(string directory, string ownerDirectory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Build.Toolchains.NativeBuildContext? statistics = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L55) | Acquires reading ownership and verifies the complete artifact before exposing it to a consumer.
`ownerDirectory`:The exact publisher root retaining the persistent owner.lock gate.
`directory`:The absolute artifact directory owned by its publisher; its persistent lease is stored alongside it.
`fingerprint`:The exact input identity required by the consumer.
`outputDirectories`:The complete relative output roots declared by the publisher.
`cancellationToken`:Cancels ownership acquisition without releasing another reader's protection.
`statistics`:Optional borrowed operation receiving output-integrity read statistics; its input cache is not used.
返回:Reading ownership which the caller must retain until all artifact access completes.
T:System.ArgumentException:The directory is not absolute or a required identity is blank.
T:System.IO.InvalidDataException:The complete artifact does not match the requested identity and output inventory.
T:System.TimeoutException:Repair or retirement retains exclusive ownership for more than thirty seconds.
T:System.OperationCanceledException:Waiting was canceled before ownership could be returned. | -| [`public void Inno.Build.Toolchains.BuildArtifactReadLease.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L68) | Ends artifact access and allows repair or retirement; repeated disposal has no effect. | -| [`public sealed class Inno.Build.Toolchains.BuildArtifactReadLease : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L13) | Pins a verified immutable build artifact against cooperating repair and retirement operations. | - -### `Inno.Build.Toolchains.BuildArtifactWriteLease` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildArtifactWriteLease.AcquireAsync(string directory, string ownerDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L42) | Waits for all readers before allowing mutation of one immutable artifact directory.
`ownerDirectory`:The exact publisher root retaining the persistent owner.lock gate.
`directory`:The absolute artifact location controlled by the publisher.
`cancellationToken`:Cancels this waiter without disturbing existing readers.
返回:Exclusive protection; retain it through replacement and release it before acquiring a reading lease.
T:System.ArgumentException:The directory is empty or relative.
T:System.TimeoutException:Other owners retain the artifact for more than thirty seconds.
T:System.OperationCanceledException:Waiting was canceled. | -| [`public void Inno.Build.Toolchains.BuildArtifactWriteLease.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L58) | Ends exclusive access without removing the persistent coordination file. | -| [`public sealed class Inno.Build.Toolchains.BuildArtifactWriteLease : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L12) | Coordinates artifact replacement or retirement with every cooperating reader and writer. | - -### `Inno.Build.Toolchains.BuildCacheArtifactDescriptor` - @@ -253,115 +374,35 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildCacheArtifactDescriptor.BuildCacheArtifactDescriptor(string directory, string leasePath, string? targetId, long bytes, System.DateTime lastUseUtc, System.Collections.Generic.IReadOnlyList dependencies, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L48) | Freezes ownership metadata supplied by the artifact's actual publisher.
`directory`:The absolute published artifact or producer-owned workspace directory.
`leasePath`:The absolute persistent file used by this entry's actual producer and readers for Core.IO coordination.
`bytes`:The publisher-recorded sum of owned file lengths, captured under writing ownership for cache budgeting. It does not measure allocated disk blocks or count shared hardlinks as recovered volume space.
`targetId`:The explicit product target, or null for a shared host artifact that target-scoped cleanup must preserve.
`lastUseUtc`:The publisher's last recorded use, used only to order retirement.
`dependencies`:Absolute artifact directories required by this generation.
`role`:The producer-declared lifetime; workspace preference alone does not pin an immutable product.
T:System.ArgumentException:A path is relative or the timestamp is not UTC.
T:System.ArgumentOutOfRangeException:The measured size is negative.
T:System.ArgumentNullException:The dependency collection is absent. | -| [`public long Inno.Build.Toolchains.BuildCacheArtifactDescriptor.bytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L99) | Gets the logical file size used for budgeting, without weakening content validation. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheArtifactDescriptor.dependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L114) | Gets the frozen dependency directories protected whenever this artifact is a live root. | -| [`public string Inno.Build.Toolchains.BuildCacheArtifactDescriptor.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L84) | Gets the absolute cache entry controlled by the declaring producer. | -| [`public System.DateTime Inno.Build.Toolchains.BuildCacheArtifactDescriptor.lastUseUtc { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L109) | Gets the UTC use timestamp, which is never used to validate cached content. | -| [`public string Inno.Build.Toolchains.BuildCacheArtifactDescriptor.leasePath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L89) | Gets the exact existing producer/reader coordination path; retirement does not infer another lock protocol. | -| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCacheArtifactDescriptor.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L104) | Gets the declared lifetime used for retirement and preferred-workspace accounting. | -| [`public string? Inno.Build.Toolchains.BuildCacheArtifactDescriptor.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L94) | Gets the producer-declared target, or null when this artifact is shared by host operations. | -| [`public sealed class Inno.Build.Toolchains.BuildCacheArtifactDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L12) | Freezes one publisher-owned cache entry and the generations it requires. | - -### `Inno.Build.Toolchains.BuildCacheArtifactRole` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ImmutableArtifact = 0`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L11) | A complete immutable generation remains protected by selected products and active readers. | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ProcessWorkingDirectory = 2`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L21) | An operation directory remains protected until its exact process owners have exited. | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ReusableWorkspace = 1`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L16) | A mutable compiler workspace may retire when no producer or consumer is active. | -| [`public enum Inno.Build.Toolchains.BuildCacheArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L6) | Declares the producer's file lifetime independently of target identity and directory naming. | - -### `Inno.Build.Toolchains.BuildCacheCleanupPlan` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static Inno.Build.Toolchains.BuildCacheCleanupPlan Inno.Build.Toolchains.BuildCacheCleanupPlan.Create(string workspace, System.Collections.Generic.IReadOnlyList owners, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll = false, string? targetId = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L95) | Plans retirement by budget pressure or an explicit request to retire every unreferenced artifact.
`workspace`:The absolute checkout boundary; no owner may manage it or its parent.
`owners`:Explicit registrations; callbacks remain borrowed until execution completes.
`policy`:The immutable size, free-space and duration budgets.
`retireAll`:Whether to propose all unreferenced generations regardless of size pressure.
`cancellationToken`:Cancels planning before any directory is removed.
`targetId`:An exact producer-declared target filter, or null to manage all registered scopes. Outside-scope generations and their dependencies remain protected.
返回:A fixed proposal; execution rechecks ownership, roots and active readers before deletion.
T:System.ArgumentException:Owners overlap, identities repeat, or dependency metadata forms a cycle.
T:System.IO.IOException:An owner, artifact or lease escapes its boundary or traverses a link.
T:System.IO.InvalidDataException:An inventory has ambiguous ownership or undeclared dependencies.
T:System.OperationCanceledException:Planning was canceled.
T:System.TimeoutException:Inventory exceeded the complete operation budget; no artifact was removed. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildCacheCleanupPlan.ExecuteAsync(bool dryRun = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L184) | Executes the original proposal under fresh owner and artifact leases without waiting for active readers.
`dryRun`:Whether to report the proposal without acquiring mutation leases or deleting files.
`cancellationToken`:Cancels remaining work; successful earlier retirements are not rolled back.
返回:Actual retirement, activity skips and IO failures; protected generations are never forced out.
T:System.IO.InvalidDataException:Fresh ownership metadata violates the registered boundary.
T:System.OperationCanceledException:The caller canceled; no later candidate is processed. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupPlan.candidates { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L43) | Gets the original eligible candidates shared by preview and execution. | -| [`public long Inno.Build.Toolchains.BuildCacheCleanupPlan.protectedBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L53) | Gets the size protected by current roots and their transitive dependencies when planning. | -| [`public long Inno.Build.Toolchains.BuildCacheCleanupPlan.totalBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L48) | Gets the measured total of explicitly inventoried artifact files. | -| [`public sealed class Inno.Build.Toolchains.BuildCacheCleanupPlan`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L15) | Freezes a reviewable retirement proposal for explicitly registered cache owners in one checkout. | - -### `Inno.Build.Toolchains.BuildCacheCleanupResult` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.BuildCacheCleanupResult.failures { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L40) | Gets failed paths and their observable IO or permission diagnostics. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupResult.retired { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L30) | Gets directories actually retired; a preview always returns an empty collection. | -| [`public long Inno.Build.Toolchains.BuildCacheCleanupResult.retiredBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L46) | Gets the declared file bytes of successfully retired artifacts, excluding persistent leases. This is a logical size; hardlinks, compression and other writers can affect actual volume recovery. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupResult.skipped { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L35) | Gets candidates preserved because ownership, protection or dependencies changed. | -| [`public bool Inno.Build.Toolchains.BuildCacheCleanupResult.timeLimitReached { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L51) | Gets whether the bounded attempt ended before processing all eligible candidates. | -| [`public sealed class Inno.Build.Toolchains.BuildCacheCleanupResult`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L11) | Reports completed retirement independently from protected, active and failed artifacts. | - -### `Inno.Build.Toolchains.BuildCacheOwnerCatalog` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheOwnerCatalog.ReadOwners(string engineRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L90) | Reads explicitly registered roots using each publisher's existing inventory and protection rules.
`engineRoot`:The absolute checkout containing registrations.
`cancellationToken`:Cancels reading before a cleanup plan exists.
返回:Frozen owner descriptors; an unprepared checkout returns an empty list.
T:System.IO.InvalidDataException:A registration is malformed or has an ambiguous identity.
T:System.IO.IOException:A registered path crosses a link or leaves the checkout. | -| [`public static void Inno.Build.Toolchains.BuildCacheOwnerCatalog.Register(string engineRoot, string directory, string id, Inno.Build.Toolchains.BuildCacheArtifactRole role, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L45) | Registers a publisher root; this never adopts unrecorded artifacts inside that root.
`engineRoot`:The absolute checkout owning the registration.
`directory`:The exact publisher root beneath the checkout.
`id`:The distribution's unique owner identity.
`role`:The publisher's actual artifact lifetime.
`cancellationToken`:Cancels before registration commits.
T:System.IO.IOException:A registration crosses a link or ownership boundary.
T:System.IO.InvalidDataException:An existing root has a different registration.
T:System.OperationCanceledException:Cancellation precedes commit. | -| [`public static class Inno.Build.Toolchains.BuildCacheOwnerCatalog`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L16) | Persists explicit publisher registrations for maintenance without loading the production distribution. | - -### `Inno.Build.Toolchains.BuildCacheOwnerDescriptor` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildCacheOwnerDescriptor.BuildCacheOwnerDescriptor(string id, string directory, string coordinationPath, System.Func> readArtifacts, System.Func> readProtectedArtifacts, System.Action? retireMetadata = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L47) | Borrows publisher callbacks without discovering artifacts from directory names or file extensions.
`id`:The unique registration identity within this cleanup operation.
`directory`:The absolute cache root exclusively managed by this publisher.
`coordinationPath`:The persistent owner lease also acquired by publication and protected-root updates. It must remain within the cache root and is never deleted by cleanup.
`readArtifacts`:Returns only generations proven to belong to this owner, with their dependency metadata. The callback must observe cancellation and must not create, repair or publish artifacts.
`readProtectedArtifacts`:Returns current product and Pack roots; retirement follows their complete dependency closure. Root updates must hold coordinationPath and retain each referenced artifact's reading lease.
`retireMetadata`:Optional owner-specific metadata retirement, called only under exclusive owner coordination.
T:System.ArgumentException:An identity is blank or a path is relative.
T:System.ArgumentNullException:An inventory callback is absent. | -| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.coordinationPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L83) | Gets the persistent lease shared with publication and protected-root changes. | -| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L78) | Gets the exact registered cache root; its parent directories are never managed implicitly. | -| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L73) | Gets the unique publisher identity registered for this cleanup operation. | -| [`public sealed class Inno.Build.Toolchains.BuildCacheOwnerDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L11) | Registers a publisher's exact cache boundary and its current artifact ownership inventory. | - -### `Inno.Build.Toolchains.BuildCachePolicy` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildCachePolicy.BuildCachePolicy(long maximumBytes = 68719476736, long minimumFreeBytes = 21474836480, System.TimeSpan? timeBudget = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L25) | Creates an immutable cache budget and automatic retirement time limit.
`maximumBytes`:The managed cache budget; defaults to 64 GiB.
`minimumFreeBytes`:The free-space threshold on the checkout volume; defaults to 20 GiB.
`timeBudget`:The retirement duration, or null for the five-second default.
T:System.ArgumentOutOfRangeException:A budget is negative or the time limit is not positive. | -| [`public long Inno.Build.Toolchains.BuildCachePolicy.maximumBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L45) | Gets the maximum size of explicitly registered build caches. | -| [`public long Inno.Build.Toolchains.BuildCachePolicy.minimumFreeBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L50) | Gets the free-space threshold below which additional retirement is requested. | -| [`public System.TimeSpan Inno.Build.Toolchains.BuildCachePolicy.timeBudget { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L56) | Gets the duration after which an attempt starts no further retirement work. An already executing filesystem operation finishes before ownership is released. | -| [`public sealed class Inno.Build.Toolchains.BuildCachePolicy`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L8) | Defines bounded retirement pressure without allowing active or current generations to be removed. | - -### `Inno.Build.Toolchains.BuildCachePublisher` - @@ -371,153 +412,43 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildCachePublisher.BuildCachePublisher(string directory, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L34) | Creates a publisher for one absolute, non-overlapping cache boundary.
`directory`:The cache root exclusively controlled by this publisher.
`role`:The lifetime declared for this publisher's completed entries.
T:System.ArgumentException:The directory is blank or relative. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildCachePublisher.AcquireUseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L64) | Pins publisher activity until publication, reading handoff and current-root updates finish.
`cancellationToken`:Cancels this wait without disturbing other owners.
返回:Caller-owned shared activity; dispose after the final artifact reading lease is acquired.
T:System.OperationCanceledException:Acquisition was canceled. | -| [`public void Inno.Build.Toolchains.BuildCachePublisher.BeginWorkingDirectory(string directory, string leasePath, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L168) | Records a reusable workspace before its first payload write while the caller holds activity and producer leases. Existing unregistered directories are rejected, rather than adopted by cleanup authority.
`directory`:The new or previously registered workspace within this publisher root.
`leasePath`:The caller's persistent exclusive producer lease outside the workspace.
`targetId`:The explicit product target, or null for shared host work.
`cancellationToken`:Cancels registration before payload writes.
T:System.InvalidOperationException:The publisher does not manage reusable workspaces or an existing directory has no current registration.
T:System.IO.IOException:The workspace escapes its boundary or ownership cannot be written. | -| [`public Inno.Build.Toolchains.BuildCacheOwnerDescriptor Inno.Build.Toolchains.BuildCachePublisher.Describe(string id)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L143) | Registers only explicitly recorded ownership, without discovering artifacts by extension or directory age.
`id`:The unique publisher registration identity.
返回:Borrowed inventory callbacks for this publisher's lifetime. | -| [`public void Inno.Build.Toolchains.BuildCachePublisher.Record(string directory, string leasePath, System.Collections.Generic.IReadOnlyList dependencies, string? currentKey, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L105) | Records a completed generation and optionally selects it as a protected current root. The caller must hold this publisher's activity lease and the artifact's producer or reading lease.
`directory`:An existing artifact within this publisher's exact boundary.
`leasePath`:The persistent lease used by its producer and readers.
`dependencies`:Exact immutable generations required while this generation is protected.
`currentKey`:A product/target/configuration root identity, or null for an unselected generation.
`cancellationToken`:Cancels before metadata selection.
`targetId`:The producer's product target, or null for a shared host generation.
T:System.IO.IOException:An owned path escapes the boundary, crosses a directory link, or contains an invalid file link.
T:System.IO.DirectoryNotFoundException:The completed generation is absent.
约束:Reusable workspaces may contain compiler-created file aliases only when their complete link chain resolves to a regular file within the same workspace. Aliases are not counted as duplicate payloads. Immutable artifacts and process working directories reject all links. | -| [`public void Inno.Build.Toolchains.BuildCachePublisher.RecordIndex(string path, System.Collections.Generic.IReadOnlyList dependencies)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L257) | Registers an exact cache index and the publication directories required to keep it useful.
`path`:The producer-owned index file within this publisher root.
`dependencies`:Exact publication roots; missing dependencies make the index retireable.
约束:The caller must hold this publisher's shared activity gate through the index write and registration. | -| [`public void Inno.Build.Toolchains.BuildCachePublisher.RecordProcessUse(string directory, string leasePath, System.Collections.Generic.IReadOnlyList dependencies, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L212) | Registers a process-owned loader directory and its dependencies before returning it to a loader. Retirement preserves the directory until every recorded process has exited.
`directory`:The completed directory within this publisher's boundary.
`leasePath`:The persistent producer and retirement lease outside the directory.
`dependencies`:Immutable generations required by this loader directory.
`cancellationToken`:Cancels ownership acquisition before the process claim is recorded.
T:System.IO.IOException:The directory escapes ownership or its registration cannot be written.
T:System.OperationCanceledException:Ownership acquisition was canceled. | -| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCachePublisher.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L50) | Gets the declared lifecycle used by this publisher's inventory and maintenance registration. | -| [`public sealed class Inno.Build.Toolchains.BuildCachePublisher`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L17) | Records exact publisher-owned generations and coordinates them with bounded cache retirement. | - -### `Inno.Build.Toolchains.BuildHostServices` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildHostServices.BuildHostServices(Inno.Build.Toolchains.IToolchainWorkspaceProvider workspaces, System.StringComparer? environmentComparer = null, System.Collections.Generic.IEnumerable? inputPaths = null, string executableExtension = "")`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L27) | Captures execution policy and its implementation inputs.
`workspaces`:The host's scoped execution path provider.
`environmentComparer`:The host's environment variable name semantics.
`inputPaths`:Checkout-relative implementation files included in native recipe identity.
`executableExtension`:The host executable suffix, or an empty string for suffix-free executable names. | -| [`public System.StringComparer Inno.Build.Toolchains.BuildHostServices.environmentComparer { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L47) | Gets environment variable name semantics. | -| [`public string Inno.Build.Toolchains.BuildHostServices.executableExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L55) | Gets the host-owned executable suffix. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildHostServices.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L51) | Gets checkout-relative execution implementation inputs. | -| [`public Inno.Build.Toolchains.IToolchainWorkspaceProvider Inno.Build.Toolchains.BuildHostServices.workspaces { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L43) | Gets the host's scoped execution path provider. | -| [`public sealed class Inno.Build.Toolchains.BuildHostServices`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L10) | Freezes host execution capabilities independently of a product target SDK. | - -### `Inno.Build.Toolchains.IBuildHostProvider` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.Toolchains.BuildHostDescriptor? Inno.Build.Toolchains.IBuildHostProvider.TryCapture()`](../../build/toolchains/Inno.Build.Toolchains/Host/IBuildHostProvider.cs#L14) | Captures the current execution machine when this provider supports it.
返回:The immutable host, or null when the current machine is outside this provider's capabilities. | -| [`public interface Inno.Build.Toolchains.IBuildHostProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/IBuildHostProvider.cs#L6) | Discovers one explicitly registered host implementation. | - -### `Inno.Build.Toolchains.IToolchainWorkspaceProvider` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.IToolchainWorkspaceProvider.OpenAsync(string physicalPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L29) | Acquires an execution path for an absolute build-owned directory.
`physicalPath`:The retained physical directory.
`cancellationToken`:Cancellation while waiting for exclusive alias ownership.
返回:A scoped path whose processes must finish before disposal. | -| [`string Inno.Build.Toolchains.IToolchainWorkspaceProvider.identity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L15) | Gets the stable execution namespace included in compiler workspace identity. | -| [`public interface Inno.Build.Toolchains.IToolchainWorkspaceProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L10) | Owns the relationship between physical build storage and host tool execution paths. | - -### `Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.PhysicalToolchainWorkspaceProvider()`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L11) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.OpenAsync(string physicalPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L17) | Acquires an execution path for an absolute build-owned directory.
`physicalPath`:The retained physical directory.
`cancellationToken`:Cancellation while waiting for exclusive alias ownership.
返回:A scoped path whose processes must finish before disposal. | -| [`public string Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.identity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L14) | Gets the stable execution namespace included in compiler workspace identity. | -| [`public sealed class Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider : Inno.Build.Toolchains.IToolchainWorkspaceProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L11) | Executes tools directly in their physical owner directory. | - -### `Inno.Build.Toolchains.BuildMaintenanceApplication` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.BuildMaintenanceApplication.CleanAsync(string engineRoot, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll, bool dryRun, string? targetId, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L117) | Uses the same owner inventory, dependency protection and retirement plan for automatic and explicit cleanup.
`engineRoot`:The absolute checkout containing registered publishers.
`policy`:The bounded size and duration policy.
`retireAll`:Whether to select every unreferenced generation instead of pressure candidates.
`dryRun`:Whether to report the plan without retiring it.
`targetId`:An optional exact producer target filter; shared dependencies remain protected.
`cancellationToken`:Cancels inventory and retirement.
返回:A completed, bounded cleanup operation with protected owners retained.
T:System.InvalidOperationException:Explicit retirement encountered failures.
T:System.TimeoutException:Owner discovery or planning exhausts the operation budget before retirement can begin.
T:System.IO.IOException:Ownership metadata or protected input cannot be validated.
T:System.OperationCanceledException:The caller canceled before completion. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.BuildMaintenanceApplication.Run(string[] arguments, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L28) | Executes the cache-clean command using the checkout's explicit publisher catalog.
`arguments`:The clean command and its validated named options.
`cancellationToken`:Cancels planning or retirement without deleting protected outputs.
返回:Zero on success, two on cancellation, or one with a diagnostic on failure. | -| [`public static class Inno.Build.Toolchains.BuildMaintenanceApplication`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L14) | Runs publisher-owned cache maintenance without backend, generator or Editor dependencies. | - -### `Inno.Build.Toolchains.DotNetSdkDescriptor` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.cliPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L32) | Gets the selected SDK's managed CLI entry assembly, invoked with the recorded host without resolving another SDK from a temporary execution directory. | -| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.hostPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L21) | Gets the executable used for project-scoped SDK and workload resolution. | -| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.sdkIdentity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L26) | Gets the exact SDK identity selected by the project's global.json resolution rules. | -| [`public sealed class Inno.Build.Toolchains.DotNetSdkDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L6) | Freezes the host, SDK identity and managed CLI entry selected by one managed entry project. | - -### `Inno.Build.Toolchains.DotNetSdkEnvironment` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.DotNetSdkEnvironment.Create(string hostPath, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkEnvironment.cs#L40) | Freezes child-only overrides so the selected executable resolves the project's own SDK. Null SDK overrides remove inherited variables rather than borrowing a parent's loaded MSBuild.
`hostPath`:The explicitly selected managed executable, resolved to an absolute path before invocation.
`environment`:Additional selected toolchain variables to copy, or null when no native SDK environment is required.
返回:A read-only child environment that preserves selected toolchain settings and never changes the parent.
T:System.ArgumentException:The managed executable is blank.
T:System.IO.FileNotFoundException:The selected managed executable cannot be resolved. | -| [`public static class Inno.Build.Toolchains.DotNetSdkEnvironment`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkEnvironment.cs#L11) | Isolates a managed tool invocation from the invoking IDE's SDK and MSBuild process identity. | - -### `Inno.Build.Toolchains.DotNetSdkResolver` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.DotNetSdkResolver.ResolveAsync(string hostPath, string projectPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs#L35) | Asks the selected host to resolve its SDK from the project location without guessing installed versions.
`hostPath`:The .NET executable selected by the build composition root.
`projectPath`:The prepared entry project whose directory controls SDK selection.
`cancellationToken`:Cancels SDK discovery and drains its child process.
返回:The exact project-selected SDK, managed entry assembly and host; missing SDKs fail explicitly.
T:System.IO.FileNotFoundException:The selected host, prepared entry project or selected SDK entry assembly does not exist.
T:System.InvalidOperationException:The selected host cannot resolve a valid SDK identity and absolute base path. | -| [`public static class Inno.Build.Toolchains.DotNetSdkResolver`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs#L12) | Resolves the managed SDK using the prepared entry project's directory and normal global.json rules. | - -### `Inno.Build.Toolchains.ManagedBuildExecution` - + - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public void Inno.Build.Toolchains.ManagedBuildExecution.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L93) | Releases exclusive workspace ownership after every compiler and source consumer has finished. Repeated disposal has no effect; reusable owned files remain available for the next operation.
T:System.IO.IOException:Completed workspace ownership metadata cannot be recorded; the execution lease is still released. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildExecution.VerifyAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L73) | Checks that owned source copies still match the bytes accepted before compilation.
`cancellationToken`:Cancels complete source verification before the consumer publishes its output.
返回:Completion only when every owned input remains unchanged.
T:System.InvalidOperationException:A tool changed a fixed input during execution.
T:System.ObjectDisposedException:This execution no longer owns the workspace. | -| [`public string Inno.Build.Toolchains.ManagedBuildExecution.engineRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L51) | Gets the fixed source root used by compilation and source extraction until disposal. SDKs and restored packages retain their explicitly selected external locations. | -| [`public string Inno.Build.Toolchains.ManagedBuildExecution.project { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L56) | Gets the entry project within this execution's copied source root. | -| [`public sealed class Inno.Build.Toolchains.ManagedBuildExecution : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L17) | Owns a reusable, exclusively leased source workspace containing verified copies of an operation's inputs. | - -### `Inno.Build.Toolchains.ManagedBuildInputSnapshot` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.CaptureAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.DotNetSdkDescriptor sdk, string project, System.Collections.Generic.IReadOnlyDictionary properties, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L102) | Evaluates an explicit managed graph using its frozen SDK and hashes its declared compilation inputs.
`context`:The borrowed operation that owns phase-local content reads and tool statistics.
`sdk`:The SDK already selected for this project; evaluation must use its exact CLI entry.
`project`:The existing entry project whose references define the managed source closure.
`properties`:The immutable compiler and product properties also passed to the actual managed build.
`cancellationToken`:Cancels SDK evaluation, drains its process and removes this operation's request directory.
返回:A snapshot that can be freshly verified after waiting or executing; no Player compilation is performed.
约束:Restore and evaluation use an owned SDK output layout, independently of ordinary IDE outputs. An explicit ArtifactsPath remains the caller's responsibility, including its concurrency protection.
T:System.ArgumentNullException:A required operation, SDK or property collection is null.
T:System.IO.FileNotFoundException:A required source, compiler, restored package input or SDK declaration is absent.
T:System.IO.IOException:The source entry escapes the physical checkout or a filesystem alias cannot be resolved.
T:System.InvalidOperationException:The selected SDK cannot evaluate the declared graph.
T:System.OperationCanceledException:Evaluation or hashing was canceled. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.CreateExecutionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L59) | Materializes the operation's declared repository inputs into an exclusively owned source workspace. Compilers and source extraction must use the returned project and root, never the mutable checkout.
`cancellationToken`:Cancels ownership acquisition or verified copying before execution ownership is returned.
返回:Caller-owned fixed execution inputs. Dispose only after all compiler and source consumers finish.
T:System.InvalidOperationException:An input differs from its planned bytes while being copied.
T:System.ArgumentException:The project does not belong to the operation's source root. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.VerifyAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L140) | Re-evaluates the graph and re-reads its current bytes in a fresh verification phase.
`cancellationToken`:Cancels evaluation and verification without changing any published output.
返回:Completion when the complete current input identity still matches this snapshot.
T:System.InvalidOperationException:The graph, SDK, source, resource, analyzer or reference inputs changed.
T:System.OperationCanceledException:Verification was canceled. | -| [`public string Inno.Build.Toolchains.ManagedBuildInputSnapshot.fingerprint { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L41) | Gets the content identity of the evaluated graph, compiler inputs and selected SDK. | -| [`public sealed class Inno.Build.Toolchains.ManagedBuildInputSnapshot`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L15) | Freezes the selected SDK's evaluated managed source graph without compiling a product. | - -### `Inno.Build.Toolchains.NativeBuildContext` - @@ -539,33 +470,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBuildContext.NativeBuildContext(string engineRoot, string configuration)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L44) | Creates a build context without consulting the tool assembly's checkout.
`engineRoot`:The checkout containing InnoEngine.sln and the native source tree; aliases resolve to one physical owner.
`configuration`:The normalized debug or release configuration.
T:System.ArgumentException:The root is empty or the configuration is unsupported.
T:System.IO.DirectoryNotFoundException:The selected checkout has no repository marker.
T:System.IO.IOException:A checkout alias cannot be resolved to a physical owner. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.BeginInputVerification(string phase)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L401) | Starts a fresh verification phase whose reads can be shared only within this derived context.
`phase`:The operation phase recorded in statistics, such as binding-locks or binding-generation.
返回:A context with a new input inventory and hash cache; existing scopes are unchanged.
约束:Create another phase after each independent ownership wait, generation or compilation. | -| [`public string Inno.Build.Toolchains.NativeBuildContext.GetNativeBuildRoot(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L180) | Resolves native intermediates under the owning toolchain project in this checkout.
`component`:The explicit component descriptor whose recipe project owns native intermediates.
返回:The absolute project-local obj/native path, without creating the directory. During publication, the path also contains the operation's target and compatible workspace identity. A Windows producer receives a temporary junction to the same physical directory so SDK tools can use bounded paths.
T:System.ArgumentNullException:The component owner is null.
T:System.InvalidOperationException:The selected checkout does not contain the owning toolchain project. | -| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordBindingBatch()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L209) | Records one complete binding batch executed by the borrowed provider. | -| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordBindingGeneration()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L214) | Records one actual bridge or managed generator invocation. | -| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordManagedProcess()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L227) | Records a managed SDK process owned by generator extension preparation. | -| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordOutputRead(long bytes)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L222) | Records one full artifact output read without treating it as an input snapshot hash.
`bytes`:The actual number of bytes consumed by output integrity verification. | -| [`public Inno.Build.Toolchains.INativeBindingGenerator Inno.Build.Toolchains.NativeBuildContext.RequireBindingGenerator()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L386) | Requires generation to be configured explicitly before creating staging or launching tools.
返回:The borrowed provider without discovering or constructing an implementation.
T:System.InvalidOperationException:Composition has not supplied a binding generator. | -| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBuildContext.RequireBindings(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L356) | Requires the target binding generation prepared for an explicitly declared Native owner.
`component`:The component whose facade and managed binding share one generation.
返回:The immutable completed generation descriptor.
T:System.InvalidOperationException:The owner has no prepared generation in this operation.
T:System.ObjectDisposedException:The caller released the borrowed binding result before this operation finished. | -| [`public Inno.Build.Toolchains.NativeComponentBuildOptions Inno.Build.Toolchains.NativeBuildContext.RequireComponentOptions(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L316) | Requires product configuration for the exact component before its recipe or tools execute.
`component`:The component requesting its scoped configuration.
返回:The immutable configuration; absent or foreign ownership fails explicitly.
T:System.InvalidOperationException:No options are assigned to this component owner. | -| [`public Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeBuildContext.RequireToolchain()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L277) | Requires an explicit selection before a component can build or start a native tool.
返回:The already frozen tool selection without performing discovery.
T:System.InvalidOperationException:Composition has not resolved a toolchain. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithBindingGenerator(Inno.Build.Toolchains.INativeBindingGenerator generator)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L369) | Borrows a generator while preserving this operation's input and tool ownership.
`generator`:The shared generation implementation selected by composition.
返回:A derived context; neither context owns or disposes the provider. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithBindings(Inno.Build.Toolchains.NativeBindingGenerationResult bindings)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L333) | Creates a scoped operation with an immutable, explicitly prepared target binding closure.
`bindings`:Borrowed completed generations indexed by their Native owners. Retain this result until all derived contexts finish.
返回:A context sharing the existing tool selection, inputs and statistics. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithComponentOptions(Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L295) | Scopes one component's explicit product configuration without changing the frozen SDK.
`component`:The sole source and intermediate owner receiving the configuration.
`options`:The immutable linkage, arguments and configuration input closure.
返回:A context sharing inputs, bindings and statistics with the current operation.
T:System.ArgumentNullException:An owner or configuration is null. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithConfiguration(string configuration)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L265) | Selects an explicit component configuration while borrowing this operation's frozen SDK and input state.
`configuration`:The debug or release configuration owned by the product step.
返回:A derived context with no inherited workspace or artifact identity from another configuration.
T:System.ArgumentException:The requested configuration is unsupported. | -| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithToolchain(Inno.Build.Toolchains.NativeToolchainSelection toolchain)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L247) | Attaches a provider's immutable tool selection while preserving this operation's input ownership.
`toolchain`:The complete selection resolved for the requested target.
返回:A scoped context sharing input snapshots and statistics with this operation.
T:System.ArgumentNullException:The selection is null. | -| [`public string Inno.Build.Toolchains.NativeBuildContext.configuration { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L155) | Gets the normalized debug or release configuration prepared by this operation. | -| [`public string Inno.Build.Toolchains.NativeBuildContext.engineRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L150) | Gets the frozen physical checkout used for every source, intermediate and output path. | -| [`public Inno.Build.Toolchains.NativeBuildStatistics Inno.Build.Toolchains.NativeBuildContext.statistics { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L198) | Gets actual hashing and native execution work accumulated by this operation and its scoped contexts. | -| [`public Inno.Build.Toolchains.NativeToolchainSelection? Inno.Build.Toolchains.NativeBuildContext.toolchain { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L161) | Gets the explicit frozen target compiler and SDK selection, or null before composition resolves it. Browser toolchains resolve their own workload-specific SDK independently. | -| [`public sealed class Inno.Build.Toolchains.NativeBuildContext`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L13) | Identifies the checkout, configuration and frozen initial inputs owned by one native build operation. Create a new context for each operation; changes during its lifetime fail stability verification. | - -### `Inno.Build.Toolchains.BuildHostDescriptor` - @@ -577,83 +481,21 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.BuildHostDescriptor.BuildHostDescriptor(string system, string architecture, Inno.Build.Toolchains.BuildHostServices? services = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L25) | Captures host facts supplied by a product or command composition boundary.
`system`:The execution operating system identity.
`architecture`:The execution processor architecture.
`services`:Explicit host execution services, or null for direct physical execution.
T:System.ArgumentException:A host fact is unassigned. | -| [`public bool Inno.Build.Toolchains.BuildHostDescriptor.Equals(Inno.Build.Toolchains.BuildHostDescriptor? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.BuildHostDescriptor.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.BuildHostDescriptor.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.BuildHostDescriptor.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.BuildHostDescriptor.operator ==(Inno.Build.Toolchains.BuildHostDescriptor? left, Inno.Build.Toolchains.BuildHostDescriptor? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.BuildHostDescriptor.operator !=(Inno.Build.Toolchains.BuildHostDescriptor? left, Inno.Build.Toolchains.BuildHostDescriptor? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.Toolchains.BuildHostDescriptor.architecture { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L45) | Gets the processor architecture on which tools execute. | -| [`public Inno.Build.Toolchains.BuildHostServices Inno.Build.Toolchains.BuildHostDescriptor.services { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L50) | Gets frozen host execution services. | -| [`public string Inno.Build.Toolchains.BuildHostDescriptor.system { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L40) | Gets the operating system on which tools execute. | -| [`public sealed record Inno.Build.Toolchains.BuildHostDescriptor : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Identifies the tool execution machine without selecting any product target. | - -### `Inno.Build.Toolchains.ComponentNativeProductExecutor` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.ComponentNativeProductExecutor.ComponentNativeProductExecutor()`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L13) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public System.Threading.Tasks.Task Inno.Build.Toolchains.ComponentNativeProductExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.ProductNativeBuildPlan plan, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L16) | Builds or reuses the complete product with the operation's frozen tools.
`context`:The frozen operation.
`plan`:The complete dependency-ordered component plan.
`cancellationToken`:Cancels waits and unpublished work.
返回:The owned complete output and binding closure. | -| [`public sealed class Inno.Build.Toolchains.ComponentNativeProductExecutor : Inno.Build.Toolchains.INativeProductExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L13) | Executes portable component producers in their declared dependency order. | - -### `Inno.Build.Toolchains.INativeBindingGenerator` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeBindingGenerator.ComputeInputFingerprintAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBindingGenerationRequest request, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L34) | Computes the selected generator source input identity without producing bindings or compiling a product.
`context`:The borrowed operation with frozen target tools and phase-local input reads.
`request`:The exact component, configuration target and output mode whose source closure is planned.
`cancellationToken`:Cancels configuration loading, dependency evaluation and content hashing.
返回:The complete source preparation identity, distinct from any generated artifact fingerprint. It remains computable when reconstructible generation outputs have been retired.
T:System.InvalidOperationException:The selected tools, configuration, generator or extension inputs cannot be resolved.
T:System.OperationCanceledException:Input planning was canceled. | -| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeBindingGenerator.GenerateAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBindingGenerationRequest request, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L67) | Generates or verifies the requested closure under publication ownership.
`context`:The borrowed operation owning frozen inputs, tool selection and statistics. Configuration path variables must resolve from its selected toolchain without changing process state.
`request`:The immutable target, component closure and publication policy.
`cancellationToken`:Cancels preparation; active work must drain before returning.
返回:An owned complete immutable map indexed by Native owner project, never a partial closure. Dispose the result only after all generated-source and native-bridge consumers finish.
T:System.InvalidOperationException:Inputs change during preparation, a required frozen SDK value is absent, or required generator capabilities are unavailable.
T:System.IO.InvalidDataException:A definition or generated output fails its declared contract.
T:System.OperationCanceledException:The request was canceled and its active work has completed cleanup. | -| [`public interface Inno.Build.Toolchains.INativeBindingGenerator`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L10) | Prepares an entire binding closure without coupling native recipes to a generator or build host. | - -### `Inno.Build.Toolchains.INativeProductExecutor` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.Task Inno.Build.Toolchains.INativeProductExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.ProductNativeBuildPlan plan, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeProductExecutor.cs#L26) | Builds or reuses the complete product with the operation's frozen tools.
`context`:The frozen operation.
`plan`:The complete dependency-ordered component plan.
`cancellationToken`:Cancels waits and unpublished work.
返回:The owned complete output and binding closure. | -| [`public interface Inno.Build.Toolchains.INativeProductExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeProductExecutor.cs#L9) | Executes an explicitly selected native product graph without target discovery. | - -### `Inno.Build.Toolchains.INativeToolchainProvider` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeToolchainProvider.ResolveAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.BuildHostDescriptor host, string targetId, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeToolchainProvider.cs#L29) | Freezes compiler tools, SDK inputs and child environment before a native operation starts.
`context`:The checkout and configuration owned by the requesting operation.
`host`:The explicitly declared execution host, independent of the target.
`targetId`:The exact requested target; unsupported combinations must fail.
`cancellationToken`:Cancels SDK discovery before any product staging.
返回:A complete immutable tool selection for the requested host and target. | -| [`public interface Inno.Build.Toolchains.INativeToolchainProvider`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeToolchainProvider.cs#L9) | Resolves one platform's SDK for an explicit target on a declared tool execution host. | - -### `Inno.Build.Toolchains.NativeArtifactRole` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.Toolchains.NativeArtifactRole.ImportLibrary = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L22) | A separate linker input referring to a shared library. | -| [`Inno.Build.Toolchains.NativeArtifactRole.SharedLibrary = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L18) | A library loaded by the runtime. | -| [`Inno.Build.Toolchains.NativeArtifactRole.StaticArchive = 2`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L26) | An archive linked into the final executable. | -| [`Inno.Build.Toolchains.NativeArtifactRole.SupportFile = 3`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L30) | A tool, header or other supporting payload. | -| [`public enum Inno.Build.Toolchains.NativeArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L13) | Describes the link or deployment role explicitly assigned by a native producer. | - -### `Inno.Build.Toolchains.NativeArtifact` - @@ -665,33 +507,8 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeArtifact.NativeArtifact(string logicalId, string file, Inno.Build.Toolchains.NativeArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L53) | Captures an output independently of platform suffix conventions.
`logicalId`:The unique producer-owned output identity.
`file`:The absolute output path retained by the product's reading ownership.
`role`:The explicitly declared link or deployment role.
T:System.ArgumentException:The identity, path or role is invalid. | -| [`public bool Inno.Build.Toolchains.NativeArtifact.Equals(Inno.Build.Toolchains.NativeArtifact? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeArtifact.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeArtifact.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeArtifact.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeArtifact.operator ==(Inno.Build.Toolchains.NativeArtifact? left, Inno.Build.Toolchains.NativeArtifact? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeArtifact.operator !=(Inno.Build.Toolchains.NativeArtifact? left, Inno.Build.Toolchains.NativeArtifact? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.Toolchains.NativeArtifact.file { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L73) | Gets the exact absolute output path. | -| [`public string Inno.Build.Toolchains.NativeArtifact.logicalId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L69) | Gets the producer-owned output identity. | -| [`public Inno.Build.Toolchains.NativeArtifactRole Inno.Build.Toolchains.NativeArtifact.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L77) | Gets the explicit link or deployment role. | -| [`public sealed record Inno.Build.Toolchains.NativeArtifact : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Identifies one exact output and its producer-declared role. | - -### `Inno.Build.Toolchains.NativeArtifactPublisher` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeArtifactPublisher.PublishAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBuildRecipe recipe, System.Func build, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), System.Func>? describeOutputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifactPublisher.cs#L51) | Reuses a validated product or builds an isolated candidate and publishes it after input stability checks.
`context`:Checkout and configuration selected by build composition.
`recipe`:The frozen component, target, input inventory and ordered compiler recipe.
`build`:Producer receiving an identity-scoped context, private output directory and cancellation token. It must await all work and validate required outputs before returning.
`cancellationToken`:Cancels ownership acquisition or candidate preparation before publication.
`describeOutputs`:The producer's exact role inventory, or null for generic support payloads.
返回:A complete native product with an exact, read-only output closure.
T:System.ArgumentException:A component or target identifier is not a single path segment.
T:System.IO.IOException:An input, candidate or artifact cannot be read or published.
T:System.InvalidOperationException:Inputs change during preparation or the producer returns no complete output.
T:System.OperationCanceledException:Ownership acquisition or preparation is canceled; previous products remain installed. | -| [`public static class Inno.Build.Toolchains.NativeArtifactPublisher`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifactPublisher.cs#L15) | Publishes complete native products by source and toolchain identity under process-shared ownership. | - -### `Inno.Build.Toolchains.NativeBindingCompilation` - @@ -709,33 +526,11 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingCompilation.NativeBindingCompilation()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingCompilation.GetInputs(string engineRoot, string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L179) | Enumerates exact file declarations for standalone or aggregate recipe fingerprinting.
`engineRoot`:The current checkout's absolute root.
`nativeDirectory`:The selected native artifact directory.
返回:All generated, user and SDK inputs with their current physical locations. | -| [`public static Inno.Build.Toolchains.NativeBindingCompilation Inno.Build.Toolchains.NativeBindingCompilation.Load(string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L97) | Reads the current engine-owned compilation description from a selected native output.
`nativeDirectory`:The complete selected generation's native directory.
返回:The validated description; missing or malformed descriptions fail explicitly.
T:System.IO.IOException:The description cannot be read.
T:System.Text.Json.JsonException:The description does not match the current contract.
T:System.IO.InvalidDataException:A root, path or content identity is invalid. | -| [`public string Inno.Build.Toolchains.NativeBindingCompilation.ResolveRoot(string engineRoot, string nativeDirectory, string rootId)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L124) | Resolves a component-local root using the current checkout and selected output.
`engineRoot`:The current checkout's absolute root.
`nativeDirectory`:The selected generation's absolute native output.
`rootId`:An ordinal root identity from this description.
返回:The physical root; no other component's root table participates.
T:System.IO.InvalidDataException:The root identity is unknown. | -| [`public void Inno.Build.Toolchains.NativeBindingCompilation.VerifyInputs(System.Func resolveRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L152) | Verifies the exact bytes at original or remapped roots before their use.
`resolveRoot`:Resolves a root identity to the selected physical or copied directory.
`cancellationToken`:Cancels verification without publishing a result.
T:System.IO.InvalidDataException:A required file has a different length or digest.
T:System.IO.IOException:A required input is missing or cannot be read.
T:System.OperationCanceledException:Verification was canceled. | -| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L57) | Gets the minimum C standard, or zero when no C translation unit is required. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.compilerDefines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L47) | Gets parser target macro facts; these must not override the consuming compiler's builtins. | -| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cxxStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L52) | Gets the minimum C++ standard, or zero when no C++ translation unit is required. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.defines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L42) | Gets ordered definitions for manifest translation units; upstream target configuration remains component-owned. | -| [`public required Inno.Build.Toolchains.NativeBindingSourceFile[] Inno.Build.Toolchains.NativeBindingCompilation.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L32) | Gets all exact input files; only files marked compile are translation units. | -| [`public required Inno.Build.Toolchains.NativeBindingIncludeDirectory[] Inno.Build.Toolchains.NativeBindingCompilation.includeDirectories { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L37) | Gets include requirements in their original search order. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.requiredExports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L62) | Gets the exact native symbols that the component must provide. | -| [`public required Inno.Build.Toolchains.NativeBindingSourceRoot[] Inno.Build.Toolchains.NativeBindingCompilation.roots { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L27) | Gets root bindings whose identities are local to this component. | -| [`public required string Inno.Build.Toolchains.NativeBindingCompilation.staticExportAbi { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L72) | Gets the static table ABI digest, or an empty value when no static table exists. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportNames { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L77) | Gets static table entries in address-resolution order. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportSymbols { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L67) | Gets static table entry points, or an empty array for dynamic-only bindings. | -| [`public sealed class Inno.Build.Toolchains.NativeBindingCompilation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | Describes a selected component's native requirements independently of its source generator. | - -### `Inno.Build.Toolchains.NativeBindingGenerationDescriptor` - + @@ -743,35 +538,9 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor.NativeBindingGenerationDescriptor()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L14) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBindingGenerationDescriptor.Load(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L140) | Reads and validates the result of a completed component generation request.
`path`:The request-owned descriptor file written after successful generation.
返回:The selected generation whose managed source and optional bridge are present.
T:System.IO.InvalidDataException:The descriptor is missing required output identities or contains unavailable outputs.
T:System.Text.Json.JsonException:The descriptor cannot be read as the current contract.
T:System.IO.IOException:An immutable artifact escapes its declared publisher or traverses a linked directory. | -| [`public static void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.ValidateSelection(string path, System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IReadOnlyDictionary linkage)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L81) | Rejects publication when the managed selection differs from the complete native generations to deploy.
`path`:The operation-owned selection consumed during managed compilation.
`generations`:The complete current native project closure and its selected generations.
`linkage`:The exact component linkage used to build the native closure.
T:System.IO.InvalidDataException:The selection differs in project closure, fingerprint, source location, linkage, or structure.
T:System.ArgumentException:The current generation closure has an unsafe or repeated project name.
T:System.ArgumentNullException:The current generation closure is null.
T:System.IO.FileNotFoundException:The managed selection is no longer available.
T:System.Xml.XmlException:The selection is not a valid XML document. | -| [`public void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.Write(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L166) | Atomically writes this completed generation to a request-owned descriptor file.
`path`:The descriptor destination; a unique path keeps concurrent requests independent. | -| [`public static void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.WriteSelection(string path, System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IReadOnlyDictionary linkage)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L38) | Writes the exact native binding identities for a subsequent managed build without reselecting generations. Identical content preserves the existing import timestamp; changed content is installed atomically.
`path`:The MSBuild selection document in an intermediate directory exclusively owned by the caller.
`generations`:Explicit checkout-relative native project paths and their complete selected generations.
`linkage`:Explicit linkage of each selected component, used by its managed native initialization boundary.
T:System.ArgumentException:A project name is unsafe or repeated, or component linkage is invalid or incomplete.
T:System.ArgumentNullException:The generation or linkage closure is null.
T:System.IO.IOException:The selection cannot be read or its complete replacement cannot be installed. | -| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.artifactDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L114) | Gets the immutable artifact root protected by reading leases, or an empty value for mutable host sources. | -| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.artifactOwnerDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L120) | Gets the publisher root coordinating immutable artifact readers and retirement, or empty for host sources. This owner remains unchanged when component sources are copied into a frozen build workspace. | -| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.bindingsPath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L104) | Gets the absolute managed source path selected by the component project. | -| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.fingerprint { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L93) | Gets the immutable input identity assigned to this target generation. | -| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.nativeDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L109) | Gets the complete native artifact and compilation-requirement directory, or empty when none is required. | -| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L14) | Describes the complete binding generation selected by one native build request. | - -### `Inno.Build.Toolchains.NativeBindingOutputMode` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.Toolchains.NativeBindingOutputMode.HostSource = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L16) | Replaces the component's authored host source and bridge outputs together. | -| [`Inno.Build.Toolchains.NativeBindingOutputMode.TargetArtifacts = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L20) | Publishes immutable target outputs under the component's generation fingerprint. | -| [`public enum Inno.Build.Toolchains.NativeBindingOutputMode : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L11) | Selects the publication owner of generated sources. | - -### `Inno.Build.Toolchains.NativeBindingGenerationRequest` - @@ -780,20 +549,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingGenerationRequest.NativeBindingGenerationRequest(System.Collections.Generic.IEnumerable components, string targetId, Inno.Build.Toolchains.NativeBindingOutputMode outputMode, bool checkOnly = false, System.Collections.Generic.IReadOnlyDictionary? expectedFingerprints = null, System.Collections.Generic.IReadOnlyDictionary? componentOptions = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L52) | Captures explicit owners and a target without discovering components or parsing a build project.
`components`:The component owners, each declaring one binding definition.
`targetId`:The target identity selecting a declared configuration; host output uses the host definition.
`outputMode`:Whether to write authored host sources or isolated target artifacts.
`checkOnly`:Whether to compare complete output without publishing a candidate.
`expectedFingerprints`:Optional expected identities indexed by Native project; mismatches reject the request.
`componentOptions`:Explicit component configurations keyed by native owner project, or null for component defaults.
T:System.ArgumentException:A target, owner, output mode or expected identity is invalid or duplicated. | -| [`public bool Inno.Build.Toolchains.NativeBindingGenerationRequest.checkOnly { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L98) | Gets whether publication is forbidden and current output must match fresh generation. | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeBindingGenerationRequest.componentOptions { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L107) | Gets explicit component binding and linkage choices. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBindingGenerationRequest.components { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L86) | Gets the immutable component closure owned by this request. | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeBindingGenerationRequest.expectedFingerprints { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L102) | Gets the expected identities which must match before any candidate is generated. | -| [`public Inno.Build.Toolchains.NativeBindingOutputMode Inno.Build.Toolchains.NativeBindingGenerationRequest.outputMode { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L94) | Gets the selected output ownership policy. | -| [`public string Inno.Build.Toolchains.NativeBindingGenerationRequest.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L90) | Gets the explicit configuration target, independent of the build execution host. | -| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationRequest`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L26) | Freezes the complete binding request before ownership waits or candidate creation. | - -### `Inno.Build.Toolchains.NativeBindingGenerationResult` - @@ -804,22 +559,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingGenerationResult.NativeBindingGenerationResult(System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IEnumerable ownership)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L34) | Freezes a complete generation map and accepts its already acquired reading ownership.
`generations`:The complete Native project to generation mapping.
`ownership`:Protection transferred on successful construction; retained by the caller on failure.
T:System.ArgumentNullException:Either collection is null.
T:System.ArgumentException:A generation or protection entry is null. | -| [`public bool Inno.Build.Toolchains.NativeBindingGenerationResult.ContainsKey(string key)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L93) | Determines whether the read-only dictionary contains an element that has the specified key.
`key`:The key to locate.
T:System.ArgumentNullException:is .
返回:if the read-only dictionary contains an element that has the specified key; otherwise, .
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public void Inno.Build.Toolchains.NativeBindingGenerationResult.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L122) | Releases all protection in reverse acquisition order; repeated disposal has no effect.
T:System.AggregateException:One or more owners failed to release after every owner was attempted. | -| [`public System.Collections.Generic.IEnumerator> Inno.Build.Toolchains.NativeBindingGenerationResult.GetEnumerator()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L114) | Returns an enumerator that iterates through the collection.
返回:An enumerator that can be used to iterate through the collection.
约束:Enumeration and all descriptor consumers must finish before the result's reading protection ends.
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public bool Inno.Build.Toolchains.NativeBindingGenerationResult.TryGetValue(string key, out Inno.Build.Toolchains.NativeBindingGenerationDescriptor value)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L102) | Gets the value that is associated with the specified key.
`key`:The key to locate.
`value`:When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized.
T:System.ArgumentNullException:is .
返回:if the object that implements the interface contains an element that has the specified key; otherwise, .
约束:A returned descriptor is borrowed and must not be used to access files after this result is disposed.
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public int Inno.Build.Toolchains.NativeBindingGenerationResult.Count { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L52) | Gets the number of elements in the collection.
返回:The number of elements in the collection.
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBindingGenerationResult.this[string key] { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L87) | Gets a borrowed descriptor whose outputs remain protected until this result is disposed.
`key`:The checkout-relative Native owner project.
返回:The selected generation; neither its paths nor retained descriptors extend reading ownership.
T:System.ObjectDisposedException:Reading ownership has ended.
T:System.Collections.Generic.KeyNotFoundException:The project is outside this closure. | -| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingGenerationResult.Keys { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L61) | Gets an enumerable collection that contains the keys in the read-only dictionary.
返回:An enumerable collection that contains the keys in the read-only dictionary.
约束:The returned sequence is borrowed and must be consumed before this result is disposed.
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingGenerationResult.Values { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L70) | Gets an enumerable collection that contains the values in the read-only dictionary.
返回:An enumerable collection that contains the values in the read-only dictionary.
约束:Neither the returned sequence nor its descriptors extend the lifetime of reading protection.
T:System.ObjectDisposedException:Reading ownership has ended. | -| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationResult : System.Collections.Generic.IReadOnlyDictionary, System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L14) | Owns the reading protection of a complete binding closure until its final compiler consumer finishes. | - -### `Inno.Build.Toolchains.NativeBindingIncludeDirectory` - @@ -833,35 +572,8 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingIncludeDirectory.NativeBindingIncludeDirectory(string rootId, string relativePath, bool system, bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement.
`rootId`:The component-local input root.
`relativePath`:The root-relative directory, or empty for the root itself.
`system`:Whether this directory contains system headers.
`framework`:Whether the compiler must use framework search semantics. | -| [`public void Inno.Build.Toolchains.NativeBindingIncludeDirectory.Deconstruct(out string rootId, out string relativePath, out bool system, out bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(Inno.Build.Toolchains.NativeBindingIncludeDirectory? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeBindingIncludeDirectory.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBindingIncludeDirectory.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator ==(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator !=(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.framework { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L22) | Whether the compiler must use framework search semantics. | -| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L20) | The root-relative directory, or empty for the root itself. | -| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L19) | The component-local input root. | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.system { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L21) | Whether this directory contains system headers. | -| [`public sealed record Inno.Build.Toolchains.NativeBindingIncludeDirectory : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement.
`rootId`:The component-local input root.
`relativePath`:The root-relative directory, or empty for the root itself.
`system`:Whether this directory contains system headers.
`framework`:Whether the compiler must use framework search semantics. | - -### `Inno.Build.Toolchains.NativeBindingPreparation` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeBindingPreparation.PrepareAsync(Inno.Build.Toolchains.NativeBuildContext context, System.Collections.Generic.IReadOnlyList components, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? componentOptions = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingPreparation.cs#L35) | Requests one binding batch for all generated components of a product.
`context`:The operation owning a frozen target selection and configured generator.
`components`:The declared component closure; components without bindings are excluded.
`cancellationToken`:Cancels generation and drains active work before returning.
`componentOptions`:Exact per-owner component choices, or null for component defaults.
返回:An owned generation closure, empty for a product without bindings. Retain it through final consumption.
T:System.InvalidOperationException:The tool selection or required binding generator is absent. | -| [`public static class Inno.Build.Toolchains.NativeBindingPreparation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingPreparation.cs#L12) | Prepares a coherent target closure through the borrowed generator, without invoking MSBuild. | - -### `Inno.Build.Toolchains.NativeBindingSourceFile` - @@ -876,26 +588,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingSourceFile.NativeBindingSourceFile(string rootId, string relativePath, long length, string sha256, bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation.
`rootId`:The component-local input root.
`relativePath`:The portable root-relative filename.
`length`:The expected byte length.
`sha256`:The lowercase SHA-256 content identity.
`compile`:Whether this file is a translation unit rather than an include dependency. | -| [`public void Inno.Build.Toolchains.NativeBindingSourceFile.Deconstruct(out string rootId, out string relativePath, out long length, out string sha256, out bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(Inno.Build.Toolchains.NativeBindingSourceFile? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeBindingSourceFile.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBindingSourceFile.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator ==(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator !=(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.compile { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L26) | Whether this file is a translation unit rather than an include dependency. | -| [`public long Inno.Build.Toolchains.NativeBindingSourceFile.length { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L24) | The expected byte length. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L23) | The portable root-relative filename. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L22) | The component-local input root. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.sha256 { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L25) | The lowercase SHA-256 content identity. | -| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceFile : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation.
`rootId`:The component-local input root.
`relativePath`:The portable root-relative filename.
`length`:The expected byte length.
`sha256`:The lowercase SHA-256 content identity.
`compile`:Whether this file is a translation unit rather than an include dependency. | - -### `Inno.Build.Toolchains.NativeBindingSourceRoot` - @@ -909,37 +601,9 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBindingSourceRoot.NativeBindingSourceRoot(string id, string directory, bool generated, bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract.
`id`:An ordinal identity scoped to this component's selected generation.
`directory`:The checkout-relative or absolute external location; empty for generated output.
`generated`:Whether this root denotes the selected native output directory.
`frozen`:Whether source files must be copied before compilation. | -| [`public void Inno.Build.Toolchains.NativeBindingSourceRoot.Deconstruct(out string id, out string directory, out bool generated, out bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(Inno.Build.Toolchains.NativeBindingSourceRoot? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeBindingSourceRoot.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBindingSourceRoot.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator ==(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator !=(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.directory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L20) | The checkout-relative or absolute external location; empty for generated output. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.frozen { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L22) | Whether source files must be copied before compilation. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.generated { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L21) | Whether this root denotes the selected native output directory. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.id { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L19) | An ordinal identity scoped to this component's selected generation. | -| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceRoot : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract.
`id`:An ordinal identity scoped to this component's selected generation.
`directory`:The checkout-relative or absolute external location; empty for generated output.
`generated`:Whether this root denotes the selected native output directory.
`frozen`:Whether source files must be copied before compilation. | - -### `Inno.Build.Toolchains.NativeBuildFingerprint` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static string Inno.Build.Toolchains.NativeBuildFingerprint.Create(Inno.Build.Toolchains.NativeBuildContext context, System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L68) | Hashes declarations using the current operation's initial snapshot or explicit fresh verification phase.
`context`:The owner of this phase's complete input inventory and content hashes.
`declarations`:Ordered non-file inputs; declaration order remains significant.
`inputs`:Complete inputs with stable logical identities and physical read locations.
`cancellationToken`:Cancels complete enumeration and hashing.
返回:The SHA-256 identity; repeated physical reads within this phase are reused. | -| [`public static string Inno.Build.Toolchains.NativeBuildFingerprint.Create(System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L39) | Hashes a complete declared input closure independently of checkout location and file timestamps.
`declarations`:Ordered target, configuration, SDK, compiler and linker arguments. Order is significant.
`inputs`:Files and directories with explicit logical identities; overlapping reading locations are hashed once.
`cancellationToken`:Cancels enumeration or complete content hashing before a fingerprint is returned.
返回:A lowercase SHA-256 identity that changes when a declaration, input inventory or input byte changes.
T:System.ArgumentNullException:A required input collection is null.
T:System.IO.IOException:An input cannot be read or contains a directory-link cycle.
T:System.OperationCanceledException:Content hashing was canceled. | -| [`public static class Inno.Build.Toolchains.NativeBuildFingerprint`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L13) | Derives recipe identities from ordered declarations, logical input names and complete source bytes. | - -### `Inno.Build.Toolchains.NativeBuildInput` - @@ -951,23 +615,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBuildInput.NativeBuildInput(string logicalPath, string physicalPath)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L23) | Declares one file or directory consumed by a native recipe.
`logicalPath`:A nonempty, slash-separated identity relative to a declared logical root.
`physicalPath`:The absolute file or directory used only for reading and execution.
T:System.ArgumentException:An identity is empty, contains a control character or backslash, or the physical path is relative. | -| [`public bool Inno.Build.Toolchains.NativeBuildInput.Equals(Inno.Build.Toolchains.NativeBuildInput? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBuildInput.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public static Inno.Build.Toolchains.NativeBuildInput Inno.Build.Toolchains.NativeBuildInput.FromPath(string engineRoot, string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L61) | Declares a checkout-relative input, or an external SDK location whose path affects tool selection.
`engineRoot`:The absolute checkout root assigned the logical prefix engine/.
`path`:A checkout-relative input or an absolute SDK input.
返回:A normalized declaration preserving external SDK location identity. | -| [`public override int Inno.Build.Toolchains.NativeBuildInput.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBuildInput.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBuildInput.operator ==(Inno.Build.Toolchains.NativeBuildInput? left, Inno.Build.Toolchains.NativeBuildInput? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBuildInput.operator !=(Inno.Build.Toolchains.NativeBuildInput? left, Inno.Build.Toolchains.NativeBuildInput? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.Toolchains.NativeBuildInput.logicalPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L42) | Gets the identity included in the recipe fingerprint. | -| [`public string Inno.Build.Toolchains.NativeBuildInput.physicalPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L47) | Gets the reading location, which is not implicitly included in the fingerprint. | -| [`public sealed record Inno.Build.Toolchains.NativeBuildInput : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Separates the reproducible identity of an input from its physical reading location. | - -### `Inno.Build.Toolchains.NativeBuildPhaseStatistics` - @@ -980,24 +627,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBuildPhaseStatistics.NativeBuildPhaseStatistics()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.Equals(Inno.Build.Toolchains.NativeBuildPhaseStatistics? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeBuildPhaseStatistics.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBuildPhaseStatistics.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.operator ==(Inno.Build.Toolchains.NativeBuildPhaseStatistics? left, Inno.Build.Toolchains.NativeBuildPhaseStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.operator !=(Inno.Build.Toolchains.NativeBuildPhaseStatistics? left, Inno.Build.Toolchains.NativeBuildPhaseStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.bytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L19) | Gets the number of bytes actually read for hashing. | -| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L15) | Gets the number of complete physical file reads. | -| [`public required string Inno.Build.Toolchains.NativeBuildPhaseStatistics.phase { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L11) | Gets the named operation phase which owns these read results. | -| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.reusedReads { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L23) | Gets repeated physical hash requests satisfied from this phase's cache. | -| [`public sealed record Inno.Build.Toolchains.NativeBuildPhaseStatistics : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Describes actual full reads and physical read reuse within one explicitly bounded input phase. | - -### `Inno.Build.Toolchains.NativeBuildProduct` - @@ -1008,22 +637,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public void Inno.Build.Toolchains.NativeBuildProduct.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L88) | Releases the artifact after all deployment, compilation and linking consumers finish. File paths must not be used afterwards; repeated disposal has no effect. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildProduct.artifacts { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L82) | Gets the exact producer-declared output and link roles. | -| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor? Inno.Build.Toolchains.NativeBuildProduct.bindingGeneration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L67) | Gets the exact binding generation compiled into this product, or null for a component without bindings. Managed consumers must retain this selection rather than resolve a new generation after native preparation. | -| [`public string Inno.Build.Toolchains.NativeBuildProduct.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L46) | Gets the component identity supplied by the owning toolchain. | -| [`public string Inno.Build.Toolchains.NativeBuildProduct.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L61) | Gets the immutable artifact root containing Outputs and its integrity manifest. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildProduct.files { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L77) | Gets the exact absolute output file paths validated by the publisher. | -| [`public string Inno.Build.Toolchains.NativeBuildProduct.fingerprint { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L56) | Gets the content fingerprint covering sources, tool executables and toolchain declarations. | -| [`public string? Inno.Build.Toolchains.NativeBuildProduct.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L72) | Gets the shared-library suffix declared by the selected SDK, or null for an SDK-independent producer. | -| [`public string Inno.Build.Toolchains.NativeBuildProduct.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L51) | Gets the target ABI selected for this build. | -| [`public sealed class Inno.Build.Toolchains.NativeBuildProduct : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L11) | Identifies one complete, integrity-checked native artifact and its exact published file closure. | - -### `Inno.Build.Toolchains.NativeBuildRecipe` - @@ -1033,21 +646,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBuildRecipe.NativeBuildRecipe(Inno.Build.Toolchains.NativeComponentDescriptor owner, string component, string targetId, System.Collections.Generic.IEnumerable inputs, System.Collections.Generic.IEnumerable declarations)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L38) | Creates a complete recipe without inferring implementation identity from an assembly MVID.
`owner`:The explicit component descriptor identifying source and intermediate owners.
`component`:The component's portable artifact path segment.
`targetId`:The ABI's portable artifact path segment.
`inputs`:The complete source, generator, executor and SDK input closure.
`declarations`:Ordered compiler, linker, include-search and non-file inputs; order is significant.
T:System.ArgumentException:A component or target is not a portable segment, or an input is null.
T:System.ArgumentNullException:A required owner or collection is null. | -| [`public static Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.CreateForComponent(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor owner, string component, string targetId, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable implementationPaths)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L120) | Declares a component's source, execution and SDK inputs before binding outputs need to exist. The artifact publisher attaches the exact selected generation before hashing or compiling.
`context`:The operation's checkout and previously selected host tools.
`owner`:The component project whose implementation participates in recipe identity.
`component`:The native component artifact segment.
`targetId`:The selected target ABI.
`inputPaths`:All handwritten native sources, component tools and build definitions. Selected managed bindings and generated bridges are attached by the publisher.
`declarations`:Ordered component arguments and generation identities.
`implementationPaths`:The complete explicit implementation inputs used by this component, excluding unrelated product code.
返回:The frozen component declaration, without generating bindings or reading generated output. Unrelated build and Editor implementation assemblies do not affect it.
T:System.IO.FileNotFoundException:A required executor or component project is absent from the checkout. | -| [`public Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.WithDependency(Inno.Build.Toolchains.NativeBuildInput input, string fingerprint)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L190) | Adds one explicitly selected published dependency while preserving the recipe's frozen execution tools.
`input`:The dependency file whose bytes participate in compilation.
`fingerprint`:The immutable dependency artifact identity selected by the product.
返回:A new declaration with the dependency input and identity; this recipe is unchanged.
T:System.ArgumentException:The dependency identity is empty.
T:System.ArgumentNullException:The input is null. | -| [`public string Inno.Build.Toolchains.NativeBuildRecipe.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L70) | Gets the component's artifact path segment. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildRecipe.declarations { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L85) | Gets ordered non-file inputs; callers must explicitly sort unordered metadata before construction. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildRecipe.inputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L80) | Gets the frozen declarations of physical inputs and their logical identities. | -| [`public Inno.Build.Toolchains.NativeComponentDescriptor Inno.Build.Toolchains.NativeBuildRecipe.owner { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L65) | Gets the declared intermediate owner independently of assembly location and MVID. | -| [`public string Inno.Build.Toolchains.NativeBuildRecipe.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L75) | Gets the target ABI's artifact path segment. | -| [`public sealed class Inno.Build.Toolchains.NativeBuildRecipe`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L11) | Freezes a component's input closure, ordered tool selection and intermediate owner. | - -### `Inno.Build.Toolchains.NativeBuildStatistics` - @@ -1066,58 +664,14 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeBuildStatistics.NativeBuildStatistics()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public bool Inno.Build.Toolchains.NativeBuildStatistics.Equals(Inno.Build.Toolchains.NativeBuildStatistics? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.Toolchains.NativeBuildStatistics.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Determines whether the specified object is equal to the current object.
`obj`:The object to compare with the current object.
返回:if the specified object is equal to the current object; otherwise, . | -| [`public override int Inno.Build.Toolchains.NativeBuildStatistics.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Serves as the default hash function.
返回:A hash code for the current object. | -| [`public override string Inno.Build.Toolchains.NativeBuildStatistics.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Returns a string that represents the current object.
返回:A string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBuildStatistics.operator ==(Inno.Build.Toolchains.NativeBuildStatistics? left, Inno.Build.Toolchains.NativeBuildStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.Toolchains.NativeBuildStatistics.operator !=(Inno.Build.Toolchains.NativeBuildStatistics? left, Inno.Build.Toolchains.NativeBuildStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.bindingBatches { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L34) | Gets complete binding batches requested by this operation. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.bindingGenerations { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L39) | Gets actual bridge or managed generation invocations, excluding valid artifact reuse. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.hashedBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L18) | Gets the actual bytes consumed by input hashing, excluding output integrity validation. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.hashedFiles { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L13) | Gets the number of complete file hashes read, including required stability verification. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.managedProcesses { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L59) | Gets managed SDK processes started for generator extension preparation and managed input evaluation. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.materializedBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L44) | Gets bytes copied to freeze mutable repository inputs. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.nativeProcesses { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L24) | Gets native execution processes started through the context's frozen tool selection. SDK discovery and managed task bootstrapping are recorded by their separate build logs. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.outputBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L54) | Gets bytes read from actual outputs, separately from source snapshot verification. | -| [`public long Inno.Build.Toolchains.NativeBuildStatistics.outputFiles { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L49) | Gets independent artifact output hashes performed for integrity verification and publication. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildStatistics.phases { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L29) | Gets immutable phase-specific read accounting accumulated by this operation. | -| [`public sealed record Inno.Build.Toolchains.NativeBuildStatistics : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Reports actual input reading and compiler process work accumulated by one build context. | - -### `Inno.Build.Toolchains.NativeCMakeExecutor` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeCMakeExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor component, string sourceDirectory, string? target, System.Collections.Generic.IReadOnlyList componentArguments, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L86) | Configures and builds an explicit component target without detecting the current platform.
`context`:The recipe-owned producer scope supplied by NativeArtifactPublisher, with frozen tools and inputs.
`component`:The unique component owner of target-scoped intermediate output.
`sourceDirectory`:The declared component-owned source directory, resolved to the operation's independent copy.
`target`:The declared CMake build target, or null for the complete selected project.
`componentArguments`:Semantic component options; SDK and generator arguments come from the selection.
`cancellationToken`:Cancels and drains the complete tool process tree.
返回:The owned CMake build directory after successful compilation.
T:System.InvalidOperationException:Tools are unassigned or configuration or compilation fails.
T:System.OperationCanceledException:The operation was canceled.
T:System.ArgumentException:Product definitions conflict with a semantic definition owned by the component recipe. | -| [`public static string Inno.Build.Toolchains.NativeCMakeExecutor.FindOutput(Inno.Build.Toolchains.NativeBuildContext context, string buildDirectory, string filePattern)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L171) | Selects one declared output while excluding CMake's compiler probes and other build configurations.
`context`:The selected configuration and target generator policy.
`buildDirectory`:The component-owned CMake intermediate directory.
`filePattern`:The component's exact basename or bounded filename pattern.
返回:The sole matching output, or an explicit failure for a missing or ambiguous result.
T:System.IO.FileNotFoundException:The component did not produce exactly one expected output. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeCMakeExecutor.PrepareSourceAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor component, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L39) | Copies the recipe's declared checkout inputs before a cold CMake invocation.
`context`:The producer scope supplied by NativeArtifactPublisher while holding its intermediate lease.
`component`:The sole owner of the intermediate source copy.
`cancellationToken`:Cancels copying before the source candidate is installed.
返回:The same prepared input descriptor for subsequent invocations in this producer scope.
T:System.InvalidOperationException:No recipe scope exists or an input changed during copying.
T:System.OperationCanceledException:Copying was canceled before publication.
T:System.ArgumentNullException:The context or component is null. | -| [`public static class Inno.Build.Toolchains.NativeCMakeExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L13) | Executes component-owned CMake definitions using an already frozen target SDK selection. | - -### `Inno.Build.Toolchains.NativeCMakeSource` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public string Inno.Build.Toolchains.NativeCMakeSource.ResolveDefinition(string argument)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L94) | Resolves declared input paths in one CMake definition without redirecting output or SDK locations.
`argument`:An ordered CMake argument; definition values may contain semicolon-separated paths.
返回:The definition with known input paths redirected, or the original nondefinition argument.
T:System.ArgumentNullException:The argument is null. | -| [`public string Inno.Build.Toolchains.NativeCMakeSource.ResolvePath(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L69) | Resolves a declared checkout or selected binding input to its copied counterpart.
`path`:An absolute recipe input path. External SDK paths retain their original location.
返回:The copied checkout or binding input, or the unchanged external SDK location.
T:System.ArgumentException:The path is empty or relative.
T:System.InvalidOperationException:A checkout or selected binding path was not declared by the recipe. | -| [`public string Inno.Build.Toolchains.NativeCMakeSource.root { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L52) | Gets the isolated source root; this is neither the checkout nor an output installation directory. | -| [`public sealed class Inno.Build.Toolchains.NativeCMakeSource`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L15) | Describes copied recipe inputs owned by one cold CMake producer. External SDK locations remain assigned to the frozen toolchain. | - -### `Inno.Build.Toolchains.NativeCompilerInputs` - @@ -1127,33 +681,9 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeCompilerInputs.NativeCompilerInputs(string? cCompiler = null, string? cppCompiler = null, string? sysRoot = null, System.Collections.Generic.IEnumerable? systemIncludeDirectories = null, System.Collections.Generic.IEnumerable? cppSystemIncludeDirectories = null, System.Collections.Generic.IEnumerable? defines = null, System.Collections.Generic.IEnumerable? arguments = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L40) | Captures explicit parser inputs supplied by the target SDK provider.
`cCompiler`:The selected C compiler, or null when this selection does not provide one.
`cppCompiler`:The selected C++ compiler, or null when this selection does not provide one.
`sysRoot`:The SDK root, or null when the compiler does not use a sysroot.
`systemIncludeDirectories`:Ordered system header directories.
`cppSystemIncludeDirectories`:Ordered C++ standard library directories.
`defines`:Ordered target preprocessor definitions.
`arguments`:Ordered parser arguments supplied by the SDK.
T:System.ArgumentException:An assigned physical path is not absolute. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.arguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L85) | Gets ordered target parser arguments. | -| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.cCompiler { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L61) | Gets the explicit C compiler, or null when unavailable. | -| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.cppCompiler { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L65) | Gets the explicit C++ compiler, or null when unavailable. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.cppSystemIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L77) | Gets ordered C++ standard library directories. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.defines { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L81) | Gets ordered target definitions. | -| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.sysRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L69) | Gets the target SDK root, or null when unnecessary. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.systemIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L73) | Gets ordered SDK header directories. | -| [`public sealed class Inno.Build.Toolchains.NativeCompilerInputs`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L11) | Freezes SDK-resolved native compiler and header inputs without interpreting environment conventions. | - -### `Inno.Build.Toolchains.NativeLibraryKind` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.Toolchains.NativeLibraryKind.Shared = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L21) | Produces libraries loaded by the deployed product. | -| [`Inno.Build.Toolchains.NativeLibraryKind.Static = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L16) | Produces archives for the selected final linker. | -| [`public enum Inno.Build.Toolchains.NativeLibraryKind : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L11) | Specifies the linkage required by a product's native component operation. | - -### `Inno.Build.Toolchains.NativeComponentBuildOptions` - @@ -1162,20 +692,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeComponentBuildOptions.NativeComponentBuildOptions(Inno.Build.Toolchains.NativeLibraryKind libraryKind, System.Collections.Generic.IEnumerable? cmakeArguments = null, System.Collections.Generic.IEnumerable? inputPaths = null, string? bindingLibraryName = null, System.Collections.Generic.IEnumerable? bindingIncludeDirectories = null, Inno.Build.Toolchains.NativeLinkPlan? linkPlan = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L55) | Captures component configuration without selecting an SDK or starting a build.
`libraryKind`:The explicitly requested linkage; a recipe must support it before execution starts.
`cmakeArguments`:Ordered, unique CMake definitions belonging to this component, excluding SDK and linkage selection.
`inputPaths`:Portable checkout-relative configuration files included in content hashing and stability checks.
`linkPlan`:Component-specific platform link settings, or null for the selected SDK plan.
`bindingLibraryName`:The explicit native import identity, or null to retain the component identity.
`bindingIncludeDirectories`:Additional checkout-relative binding headers owned by an integration.
T:System.ArgumentException:Linkage is invalid, definitions are ambiguous or override SDK selection, or an input escapes the checkout. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.bindingIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L116) | Gets additional checkout-relative binding header roots. | -| [`public string? Inno.Build.Toolchains.NativeComponentBuildOptions.bindingLibraryName { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L106) | Gets the explicit native import identity, or null for the component default. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L96) | Gets the immutable ordered component definitions; order contributes to artifact identity. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L101) | Gets logical configuration input paths whose actual bytes participate in the recipe. | -| [`public Inno.Build.Toolchains.NativeLibraryKind Inno.Build.Toolchains.NativeComponentBuildOptions.libraryKind { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L91) | Gets the linkage selected by the product rather than by the execution host. | -| [`public Inno.Build.Toolchains.NativeLinkPlan? Inno.Build.Toolchains.NativeComponentBuildOptions.linkPlan { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L111) | Gets component-specific platform link settings, or null for the target defaults. | -| [`public sealed class Inno.Build.Toolchains.NativeComponentBuildOptions`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L27) | Freezes product-owned linkage, ordered component definitions and their configuration inputs. | - -### `Inno.Build.Toolchains.NativeComponentDescriptor` - @@ -1185,47 +701,13 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeComponentDescriptor.NativeComponentDescriptor(string id, string nativeProject, string toolchainProject, Inno.Build.Toolchains.NativeStaticBuildDefinition? staticBuild = null, string? bindingDefinition = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L33) | Freezes portable checkout-relative owner locations without assembly-name or directory inference.
`id`:The portable component artifact identity.
`nativeProject`:The checkout-relative native project file.
`toolchainProject`:The checkout-relative build implementation project file.
`staticBuild`:The backend-owned static recipe, or null when this component has none.
`bindingDefinition`:The portable binding definition, or null when the component generates no bindings.
T:System.ArgumentException:An identity or path is empty, absolute, or escapes its checkout. | -| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.GetNativeRoot(string engineRoot)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L90) | Resolves the declared native owner within an explicitly supplied checkout.
`engineRoot`:The absolute operation-owned checkout.
返回:The absolute native project directory, without creating it. | -| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.GetToolchainRoot(string engineRoot)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L101) | Resolves the declared recipe owner within an explicitly supplied checkout.
`engineRoot`:The absolute operation-owned checkout.
返回:The absolute toolchain project directory, without creating it. | -| [`public string? Inno.Build.Toolchains.NativeComponentDescriptor.bindingDefinition { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L79) | Gets the declared binding definition, or null for a component without generated bindings. | -| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L59) | Gets the portable artifact identity. | -| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.nativeProject { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L64) | Gets the explicit checkout-relative Native owner project. | -| [`public Inno.Build.Toolchains.NativeStaticBuildDefinition? Inno.Build.Toolchains.NativeComponentDescriptor.staticBuild { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L74) | Gets the declared static build capability without requiring a platform-specific component project. | -| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.toolchainProject { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L69) | Gets the explicit checkout-relative recipe owner project. | -| [`public sealed class Inno.Build.Toolchains.NativeComponentDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L10) | Declares the unique source and build owners of a reusable native component. | - -### `Inno.Build.Toolchains.NativeDeploymentMetadataFactory` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeDeploymentMetadataFactory.NativeDeploymentMetadataFactory(object @object, nint method)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public virtual System.IAsyncResult Inno.Build.Toolchains.NativeDeploymentMetadataFactory.BeginInvoke(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files, System.AsyncCallback callback, object @object)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public virtual System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory.EndInvoke(System.IAsyncResult result)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public virtual System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory.Invoke(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Creates explicitly selected deployment metadata from a frozen Native closure.
`products`:Borrowed complete products whose leases remain held through metadata preparation.
`files`:Exact relative deployment paths mapped to verified source files.
返回:Relative metadata paths mapped to immutable bytes; collisions with payloads are rejected. | -| [`public delegate System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Creates explicitly selected deployment metadata from a frozen Native closure.
`products`:Borrowed complete products whose leases remain held through metadata preparation.
`files`:Exact relative deployment paths mapped to verified source files.
返回:Relative metadata paths mapped to immutable bytes; collisions with payloads are rejected. | - -### `Inno.Build.Toolchains.NativeInputMaterializer` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.NativeInputMaterializer.CopyAsync(Inno.Build.Toolchains.NativeBuildContext context, string source, string destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeInputMaterializer.cs#L39) | Copies a declared input atomically when its destination differs, preserving identical file timestamps.
`context`:The operation whose initial source bytes and hashing statistics are shared across components.
`source`:The absolute input file already declared by the component recipe.
`destination`:The absolute intermediate file owned exclusively by the component producer.
`cancellationToken`:Cancels copying before publication and preserves the prior intermediate file.
返回:True when bytes were copied and published; false when the existing content was identical.
T:System.InvalidOperationException:The source changes relative to the operation's frozen input snapshot.
T:System.IO.IOException:The source, destination or atomic candidate cannot be read or published. | -| [`public static class Inno.Build.Toolchains.NativeInputMaterializer`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeInputMaterializer.cs#L13) | Materializes frozen native inputs without rewriting identical intermediate files. | - -### `Inno.Build.Toolchains.NativeLinkPlan` - @@ -1234,50 +716,15 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeLinkPlan.NativeLinkPlan(string? sharedLibraryExtension = null, string? importLibraryExtension = null, string? staticLibraryExtension = null, System.Collections.Generic.IEnumerable? cmakeArguments = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L30) | Captures explicitly supported library formats and ordered linker configuration.
`sharedLibraryExtension`:The shared-library suffix, or null when shared linkage is unsupported.
`importLibraryExtension`:The separate import-library suffix, or null when the shared library is the link input.
`staticLibraryExtension`:The archive suffix, or null when static linkage is not contributed.
`cmakeArguments`:Ordered platform-owned CMake linker definitions.
T:System.ArgumentException:No format is supported, an import format lacks shared linkage, a suffix is invalid, or an argument is null. | -| [`public string Inno.Build.Toolchains.NativeLinkPlan.GetFileExtension(Inno.Build.Toolchains.NativeLibraryKind kind)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L95) | Resolves an explicitly supported linkage format before staging or generation begins.
`kind`:The component's requested linkage.
返回:The selected file extension; unsupported linkage fails immediately.
T:System.NotSupportedException:The platform does not contribute this linkage format.
T:System.ArgumentOutOfRangeException:The linkage kind is not defined. | -| [`public string Inno.Build.Toolchains.NativeLinkPlan.GetStaticFileName(string logicalName)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L78) | Formats an explicitly declared logical archive using the selected platform format.
`logicalName`:The component-owned logical archive name.
返回:The exact platform file name.
T:System.NotSupportedException:Static linkage has no registered archive format. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeLinkPlan.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L64) | Gets ordered platform linker definitions. | -| [`public string? Inno.Build.Toolchains.NativeLinkPlan.importLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L56) | Gets the separate import suffix, or null when no separate import library is used. | -| [`public string? Inno.Build.Toolchains.NativeLinkPlan.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L52) | Gets the shared-library suffix, or null when shared linkage is unsupported. | -| [`public string? Inno.Build.Toolchains.NativeLinkPlan.staticLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L60) | Gets the static archive suffix, or null when static linkage is unavailable. | -| [`public sealed class Inno.Build.Toolchains.NativeLinkPlan`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L10) | Freezes platform library formats and linker configuration independently of component recipes. | - -### `Inno.Build.Toolchains.NativeStaticBuildDefinition` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeStaticBuildDefinition.NativeStaticBuildDefinition(string cmakeFile, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable logicalOutputs, string? nativeSourceDirectory = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L33) | Freezes the component's CMake entry point, source dependencies and exact installed archive names.
`cmakeFile`:The portable checkout-relative component definition.
`inputPaths`:The portable checkout-relative source and build dependencies.
`logicalOutputs`:The exact archive file names required after installation.
`nativeSourceDirectory`:The declared checkout-relative root used by the component's native CMake fragment, or null when no such fragment is consumed.
T:System.ArgumentException:A path escapes its root or an archive name is invalid or duplicated.
T:System.ArgumentNullException:A required collection is null. | -| [`public string Inno.Build.Toolchains.NativeStaticBuildDefinition.cmakeFile { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L58) | Gets the component-owned CMake definition, which participates in artifact identity. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeStaticBuildDefinition.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L63) | Gets the immutable complete source dependency paths relative to the checkout. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeStaticBuildDefinition.logicalOutputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L73) | Gets the exact required output closure within this component's target directory. | -| [`public string? Inno.Build.Toolchains.NativeStaticBuildDefinition.nativeSourceDirectory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L68) | Gets the declared native fragment's source root, or null when the recipe only consumes its other inputs. | -| [`public sealed class Inno.Build.Toolchains.NativeStaticBuildDefinition`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L10) | Declares a backend-owned static-library recipe and its complete checkout input closure. | - -### `Inno.Build.Toolchains.NativeToolchainPreparation` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Collections.Generic.Dictionary Inno.Build.Toolchains.NativeToolchainPreparation.CreateEnvironment()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L26) | Creates a child-process environment that clears undeclared ambient compiler policy.
返回:A caller-owned dictionary to populate with explicitly selected SDK variables. | -| [`public static Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeToolchainPreparation.Freeze(string targetId, Inno.Build.Toolchains.BuildHostDescriptor host, System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IReadOnlyDictionary environment, System.Collections.Generic.IEnumerable inputs, System.Collections.Generic.IEnumerable cmakeArguments, Inno.Build.Toolchains.NativeLinkPlan linkPlan, bool multiConfiguration, Inno.Build.Toolchains.NativeCompilerInputs? compilerInputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L65) | Validates and freezes a provider's complete tool selection before product staging.
`targetId`:The explicit requested target.
`host`:The declared tool execution host.
`tools`:Selected executable paths.
`environment`:Isolated child environment.
`inputs`:Selected compiler and SDK inputs.
`cmakeArguments`:Ordered target configuration arguments.
`multiConfiguration`:Whether configuration is chosen during build.
`linkPlan`:The sole source of target library formats and linker capabilities.
`compilerInputs`:The selected SDK parser inputs, or null for a selection without parsing capability.
返回:An immutable selection with validated physical tool and SDK inputs.
T:System.IO.FileNotFoundException:A selected SDK input or executable is absent. | -| [`public static class Inno.Build.Toolchains.NativeToolchainPreparation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L11) | Supplies shared environment isolation and SDK input validation without selecting a platform. | - -### `Inno.Build.Toolchains.NativeToolchainSelection` - @@ -1293,27 +740,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.NativeToolchainSelection.NativeToolchainSelection(string targetId, Inno.Build.Toolchains.BuildHostDescriptor host, System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IReadOnlyDictionary environment, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable cmakeArguments, Inno.Build.Toolchains.NativeLinkPlan linkPlan, bool multiConfiguration, Inno.Build.Toolchains.NativeCompilerInputs? compilerInputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L55) | Captures an immutable, explicit compiler and SDK selection supplied by a platform provider.
`targetId`:The exact native target identity.
`host`:The machine on which the selected tools execute.
`tools`:Absolute executable paths indexed by explicit command names.
`environment`:Frozen SDK values applied to owned tools and in-process binding configuration without changing process state.
`inputPaths`:Compiler, SDK and tool files participating in product identity.
`cmakeArguments`:Ordered configuration arguments pinning compiler, generator and SDK.
`multiConfiguration`:Whether configuration is selected during build rather than configure.
`linkPlan`:The sole source of target library formats and linker capabilities.
`compilerInputs`:Explicit SDK parser inputs, or null when no native parser is provided.
T:System.ArgumentException:The selection is incomplete or has unassigned target facts.
T:System.ArgumentNullException:A required descriptor or collection is null.
T:System.IO.FileNotFoundException:A selected executable or SDK input is unavailable before preparation begins. | -| [`public string Inno.Build.Toolchains.NativeToolchainSelection.ResolveExecutable(string name)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L212) | Resolves an executable from the frozen selection rather than a mutable process PATH.
`name`:The explicit command registered by the platform provider.
返回:The selected executable; an absent command fails immediately.
T:System.InvalidOperationException:The provider did not register the requested command. | -| [`public bool Inno.Build.Toolchains.NativeToolchainSelection.TryResolveExecutable(string name, out string executable)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L231) | Checks an optional tool capability without discovering a command from the process environment.
`name`:The declared tool identity to check.
`executable`:The frozen absolute path when present, or an empty string when absent.
返回:True only when this selection explicitly supplies the requested tool.
T:System.ArgumentException:The tool identity is empty. | -| [`public Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeToolchainSelection.WithTools(System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IEnumerable inputs)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L183) | Adds integration-owned tools without changing the platform SDK or replacing existing commands.
`tools`:Explicit absolute executables required by the selected backend integration.
`inputs`:Integration implementation and configuration files participating in tool selection.
返回:A new immutable selection containing both the platform and integration inputs.
T:System.ArgumentException:A contribution conflicts with an already selected command.
T:System.IO.FileNotFoundException:A contributed tool or input does not exist. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L135) | Gets configuration arguments selecting the compiler, generator and SDK. | -| [`public Inno.Build.Toolchains.NativeCompilerInputs Inno.Build.Toolchains.NativeToolchainSelection.compilerInputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L158) | Gets the target provider's explicit parser inputs. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.declarations { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L130) | Gets ordered tool and environment facts included in the recipe fingerprint. | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeToolchainSelection.environment { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L140) | Gets frozen SDK values supplied to owned tools and in-process binding configuration. | -| [`public Inno.Build.Toolchains.BuildHostDescriptor Inno.Build.Toolchains.NativeToolchainSelection.host { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L120) | Gets the declared tool execution host. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L125) | Gets the immutable SDK and compiler input closure. | -| [`public Inno.Build.Toolchains.NativeLinkPlan Inno.Build.Toolchains.NativeToolchainSelection.linkPlan { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L163) | Gets the platform-owned linker capabilities. | -| [`public bool Inno.Build.Toolchains.NativeToolchainSelection.multiConfiguration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L153) | Gets whether the selected generator supports build-time configuration selection. | -| [`public string Inno.Build.Toolchains.NativeToolchainSelection.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L148) | Gets the target's shared-library suffix.
T:System.NotSupportedException:The target does not support shared libraries. | -| [`public string Inno.Build.Toolchains.NativeToolchainSelection.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L115) | Gets the target identity, without inferring it from the execution host. | -| [`public sealed class Inno.Build.Toolchains.NativeToolchainSelection`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L12) | Freezes target compiler tools and SDK inputs independently of discovery and platform policy. | - -### `Inno.Build.Toolchains.ProductNativeBuildPlan` - @@ -1321,19 +747,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.ProductNativeBuildPlan.ProductNativeBuildPlan(string productId, System.Collections.Generic.IReadOnlyList steps, Inno.Build.Toolchains.NativeDeploymentMetadataFactory? deploymentMetadata = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L36) | Validates the complete ordered component graph before tools or staging can start.
`productId`:The product identity whose native closure is selected.
`steps`:Unique, dependency-ordered component operations.
`deploymentMetadata`:Optional product-owned metadata preparation, without backend discovery.
T:System.ArgumentException:The product, component identity or dependency ordering is invalid.
T:System.ArgumentNullException:The step collection is null. | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.ProductNativeBuildPlan.CreateDeploymentFiles(System.Collections.Generic.IReadOnlyList products)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L133) | Resolves exact deployment paths from the declared layout rather than component-name conventions.
`products`:The complete result of this plan, in declaration order.
返回:Owned immutable relative-output to source-file mappings for atomic deployment.
T:System.ArgumentException:Products differ from the plan or deployment paths collide or escape their root. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildPlan.CreateRecipes(Inno.Build.Toolchains.NativeBuildContext context)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L91) | Resolves the complete selected source recipe closure before binding generation or native compilation.
`context`:The borrowed operation with a frozen target SDK; each step supplies its own component options.
返回:Immutable recipes in declaration order, including each component's selected tools and parameters. Actual generated bytes and dependency artifacts are added by the producer before publication.
T:System.ArgumentNullException:The context is null.
T:System.InvalidOperationException:Tools are unassigned or a declaration returns a foreign owner, component or target.
T:System.NotSupportedException:A static component does not support the selected target. | -| [`public Inno.Build.Toolchains.NativeDeploymentMetadataFactory? Inno.Build.Toolchains.ProductNativeBuildPlan.deploymentMetadata { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L70) | Gets the product's explicit metadata preparation, or null when no metadata is required. | -| [`public string Inno.Build.Toolchains.ProductNativeBuildPlan.productId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L60) | Gets the explicit product identity. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildPlan.steps { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L65) | Gets the frozen dependency-ordered component closure. | -| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildPlan`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L14) | Executes a frozen product closure with one target toolchain and no implicit backend selection. | - -### `Inno.Build.Toolchains.ProductNativeBuildResult` - @@ -1341,19 +754,6 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.ProductNativeBuildResult.ProductNativeBuildResult(System.Collections.Generic.IEnumerable products, Inno.Build.Toolchains.NativeBindingGenerationResult bindings, string? aggregateDirectory = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L30) | Takes ownership of a complete native and binding closure.
`products`:Owned products in dependency order.
`bindings`:The owned matching binding batch.
`aggregateDirectory`:A complete aggregate install directory, or null for individual products. | -| [`public void Inno.Build.Toolchains.ProductNativeBuildResult.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L94) | Releases products in reverse dependency order after all consumers have finished. Repeated disposal has no effect.
T:System.AggregateException:One or more reading owners failed to release after every owner was attempted. | -| [`public System.Collections.Generic.IEnumerator Inno.Build.Toolchains.ProductNativeBuildResult.GetEnumerator()`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L81) | Enumerates borrowed products in plan order without transferring their ownership.
返回:An enumerator which must finish before this result is disposed.
T:System.ObjectDisposedException:The result has already released its leases. | -| [`public int Inno.Build.Toolchains.ProductNativeBuildResult.Count { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L43) | Gets the number of products in the completed build plan. | -| [`public Inno.Build.Toolchains.NativeBuildProduct Inno.Build.Toolchains.ProductNativeBuildResult.this[int index] { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L63) | Borrows a product whose lease is owned by this result until disposal.
`index`:The zero-based position in the plan's declaration order.
返回:The borrowed product; the caller must finish access before disposing the result.
T:System.ObjectDisposedException:The result no longer owns the products. | -| [`public string? Inno.Build.Toolchains.ProductNativeBuildResult.aggregateDirectory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L48) | Gets the complete aggregate install directory, or null for individually published products. | -| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildResult : System.Collections.Generic.IReadOnlyList, System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L12) | Owns a complete ordered Native product closure and releases its reading leases together. | - -### `Inno.Build.Toolchains.ProductNativeBuildStep` - @@ -1365,35 +765,9 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.ProductNativeBuildStep.ProductNativeBuildStep(string id, Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options, System.Collections.Generic.IReadOnlyList dependencies, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? build, System.Func? createRecipe, System.Func deploymentPath, string? configuration = null, System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? bindDependencies = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L52) | Freezes a component step without embedding any backend list in the shared executor.
`id`:The expected published component identity.
`component`:The unique source and recipe owners.
`options`:Explicit product linkage, ordered component parameters and their input closure.
`dependencies`:Earlier component identities required by this operation.
`build`:The awaited standalone producer, or null when the component is executed by a static aggregate.
`createRecipe`:Declares the standalone producer's source inputs without creating generated outputs, or null for a component whose complete source definition is owned by a static aggregate.
`deploymentPath`:Maps a relative output and target to a deployment path, or null for build-only inputs.
`configuration`:An explicit component configuration, or null to use the product configuration.
`bindDependencies`:Optional backend-owned transformation adding the exact published dependency inputs to its source recipe. It must not generate outputs or execute tools.
T:System.ArgumentException:The identity or dependency set is invalid.
T:System.ArgumentNullException:A required owner, producer or path policy is null. | -| [`public System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? Inno.Build.Toolchains.ProductNativeBuildStep.bindDependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L134) | Gets the backend-owned dependency transformation, or null for an independent source recipe. The supplied products are borrowed only during recipe construction. | -| [`public System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? Inno.Build.Toolchains.ProductNativeBuildStep.build { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L112) | Gets the standalone producer, or null for a component owned by a static aggregate executor. | -| [`public Inno.Build.Toolchains.NativeComponentDescriptor Inno.Build.Toolchains.ProductNativeBuildStep.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L97) | Gets the explicit source and recipe owners. | -| [`public string? Inno.Build.Toolchains.ProductNativeBuildStep.configuration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L128) | Gets the step's explicit configuration, or null to borrow the containing product configuration. | -| [`public System.Func? Inno.Build.Toolchains.ProductNativeBuildStep.createRecipe { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L118) | Gets the producer's source declaration, or null when a static aggregate owns the component recipe. The declaration must not generate bindings, run a compiler or create staging. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildStep.dependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L107) | Gets dependencies that must precede this step. | -| [`public System.Func Inno.Build.Toolchains.ProductNativeBuildStep.deploymentPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L123) | Gets the explicit output layout policy; null results exclude build-only files. | -| [`public string Inno.Build.Toolchains.ProductNativeBuildStep.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L92) | Gets the exact expected component identity. | -| [`public Inno.Build.Toolchains.NativeComponentBuildOptions Inno.Build.Toolchains.ProductNativeBuildStep.options { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L102) | Gets product-owned configuration independently of the selected SDK. | -| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildStep`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L12) | Binds one product component to an owned build operation and explicit deployment layout. | - -### `Inno.Build.Toolchains.ProductNativeDeployment` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ProductNativeDeployment.InstallAsync(System.Collections.Generic.IReadOnlyList products, Inno.Build.Toolchains.ProductNativeBuildPlan plan, string applicationDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L46) | Validates the application's native tree and atomically replaces it when its exact contents differ.
`products`:Validated runtime and optional authoring products returned by the declared product plan. All products must use the same target; each component can appear once.
`cancellationToken`:Cancels ownership acquisition or staging before the atomic installation starts.
返回:Completion after the complete native tree has been validated under exclusive publication ownership. Identical deployments retain their files, including libraries loaded by a running application.
`plan`:The explicit component closure and deployment mapping used to produce these products.
`applicationDirectory`:The managed output directory receiving the complete native tree.
T:System.ArgumentException:The product closure is empty, contains duplicate components or mixes targets.
T:System.IO.IOException:A product cannot be validated, copied or installed; the prior native tree remains available.
T:System.OperationCanceledException:The operation was canceled before installation; the previous native tree is unchanged. | -| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ProductNativeDeployment.StageAsync(System.Collections.Generic.IReadOnlyList products, Inno.Build.Toolchains.ProductNativeBuildPlan plan, string applicationDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L87) | Writes a new native tree into an unpublished candidate exclusively owned by the caller.
`products`:The pinned, validated products to copy into the candidate.
`plan`:The explicit component layout and neutral deployment metadata.
`applicationDirectory`:An existing caller-owned candidate with no native subtree.
`cancellationToken`:Cancels copying or final input verification before the caller publishes.
返回:Completion after copying and verifying payload, without creating transaction metadata in the candidate.
约束:The caller must retain exclusive ownership and discard the entire candidate on failure or cancellation. This operation neither selects a publication nor replaces an existing native tree.
T:System.ArgumentException:The product closure or candidate path is invalid.
T:System.IO.DirectoryNotFoundException:The caller has not created its owned candidate.
T:System.IO.IOException:The native tree already exists, or an input cannot be verified or copied.
T:System.OperationCanceledException:Cancellation prevents completing the unpublished candidate. | -| [`public static class Inno.Build.Toolchains.ProductNativeDeployment`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L15) | Materializes an explicitly built host-native closure into one application's deployment directory. | - -### `Inno.Build.Toolchains.ToolchainEnvironment` - @@ -1410,233 +784,475 @@ BuildCacheOwnerCatalog 由实际发行登记 publisher 根、ID 和生命周期 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.CaptureOutputAsync(System.Diagnostics.ProcessStartInfo start, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L512) | Captures platform tool discovery through the same cancellable process lifecycle as builds.
`start`:The prepared discovery command, including any platform-specific argument quoting.
`cancellationToken`:Cancels discovery and waits for its process tree to exit.
返回:Complete standard output after successful discovery; errors and cancellation propagate. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.CaptureOutputAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L309) | Captures a tool's standard output while forwarding errors and preserving the common process lifecycle.
`fileName`:The executable to launch.
`arguments`:Structured arguments passed without shell interpretation.
`workingDirectory`:The directory used for project and SDK resolution.
`cancellationToken`:Cancels the child process tree and waits for its complete exit.
`environment`:Optional child-only environment variables. Null values remove inherited variables; empty values remain explicitly empty.
返回:The complete standard output after successful exit; failures and cancellation propagate.
T:System.InvalidOperationException:The tool fails to start or exits unsuccessfully.
T:System.OperationCanceledException:The operation was canceled.
T:System.IO.IOException:Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | -| [`public static bool Inno.Build.Toolchains.ToolchainEnvironment.ContainsAny(string value, params string[] needles)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L417) | Determines whether a value contains at least one token without regard to casing.
`value`:The value searched for candidate tokens.
`needles`:The candidate tokens tested against the value.
返回:when at least one token occurs in the value; otherwise . | -| [`public static void Inno.Build.Toolchains.ToolchainEnvironment.DeleteDirectory(string path)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L486) | Deletes one explicitly resolved toolchain output directory when it exists.
`path`:The exact directory selected by a toolchain command.
T:System.ArgumentException:Thrown when is empty or does not resolve to an absolute path. | -| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.FindRepoRoot()`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L345) | Resolves the repository containing the currently executing toolchain assembly.
返回:The absolute path of the repository root.
T:System.InvalidOperationException:Thrown when no repository marker exists above the toolchain assembly directory. | -| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.NormalizeOutputName(string fileName, string config)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L444) | Normalizes a native artifact name and appends its build configuration.
`fileName`:The source artifact file name.
`config`:The normalized configuration appended to the artifact stem.
返回:The deterministic output file name. | -| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.ResolveExecutable(string name)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L164) | Resolves one explicitly selected executable before it becomes a build input.
`name`:An executable path or a command name available on the current process PATH.
返回:The absolute executable path used for process execution and artifact identity.
T:System.ArgumentException:The name is empty.
T:System.IO.FileNotFoundException:The executable is unavailable at the supplied path or on PATH. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L93) | Runs a host-native process with the context's frozen compiler and SDK selection.
`context`:A context resolved by NativeToolchainSelection before artifact fingerprinting.
`fileName`:A selected build command or an absolute executable declared as a component input.
`arguments`:Individual arguments passed without shell interpretation.
`workingDirectory`:The source snapshot or intermediate directory owned by the operation.
`cancellationToken`:Cancels and retires the complete process tree.
返回:Completion after successful exit and complete output delivery.
T:System.InvalidOperationException:Host tools were not resolved, a command is unavailable or a process fails.
T:System.OperationCanceledException:The operation was canceled. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary environment)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L50) | Executes a declared absolute tool with an explicitly resolved SDK environment and records the operation cost.
`context`:The operation owning execution statistics and frozen recipe inputs.
`fileName`:The absolute executable declared by the recipe; no host-native SDK is inferred.
`arguments`:Individual arguments passed without shell interpretation.
`workingDirectory`:The owned source or intermediate directory.
`cancellationToken`:Cancels execution and drains the complete process tree.
`environment`:The frozen environment supplied by the selected SDK resolver.
返回:Completion after successful exit and output delivery.
T:System.ArgumentException:The executable is not an absolute declared tool path.
T:System.InvalidOperationException:The process fails to start or exits unsuccessfully.
T:System.OperationCanceledException:Execution was canceled. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, string arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L134) | Runs a quoted host command with a frozen compiler and SDK environment.
`context`:The context resolved before artifact fingerprinting.
`fileName`:A selected command or an absolute executable declared in the component inputs.
`arguments`:Arguments quoted for the selected executable's command-line parser.
`workingDirectory`:The operation's source snapshot or intermediate directory.
`cancellationToken`:Cancels and retires the complete process tree.
返回:Completion after the command exits successfully and its streams drain.
T:System.InvalidOperationException:Tools are unresolved or execution fails.
T:System.OperationCanceledException:The operation was canceled. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L216) | Runs a child build with structured arguments, cancellation and hidden windows.
`fileName`:The executable to launch.
`arguments`:The individual arguments, passed without shell interpretation.
`workingDirectory`:The repository or staging directory owned by the operation.
`cancellationToken`:Cancels the child process and its complete process tree.
`environment`:Optional process-local toolchain variables; the parent environment is unchanged. Null values remove inherited variables, while empty values remain explicitly empty.
返回:Completion after both output streams have drained and the process succeeds.
T:System.InvalidOperationException:The process fails to start or exits unsuccessfully.
T:System.OperationCanceledException:The operation was canceled.
T:System.IO.IOException:Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment, System.IO.TextWriter standardOutput, System.IO.TextWriter standardError)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L261) | Runs a child build with caller-owned output destinations and the shared process retirement protocol.
`fileName`:The executable selected by the build composition.
`arguments`:Structured arguments passed without shell interpretation.
`workingDirectory`:The project directory controlling SDK and workload resolution.
`cancellationToken`:Cancels the complete process tree and drains both output streams.
`environment`:Optional child-only toolchain variables. Null values remove inherited variables; empty values remain explicitly empty.
`standardOutput`:The caller-owned standard output destination; the runner does not dispose it.
`standardError`:The caller-owned standard error destination; the runner does not dispose it.
返回:Completion after successful exit and complete output delivery; failures propagate.
T:System.InvalidOperationException:The executable cannot start or exits unsuccessfully.
T:System.IO.IOException:An output destination fails; the owned process tree is terminated before failure returns.
T:System.OperationCanceledException:The operation was canceled after retiring its owned process tree. | -| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, string arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L390) | Runs a child build using a complete argument string, without a shell.
`fileName`:The executable resolved by the host operating system.
`arguments`:Arguments quoted for the selected executable's command-line parser.
`workingDirectory`:The directory owned by the operation.
`cancellationToken`:Cancels the complete process tree and waits for output to finish draining.
返回:Completion after successful exit; failures and cancellation propagate.
T:System.InvalidOperationException:The process fails to start or exits unsuccessfully.
T:System.OperationCanceledException:The operation was canceled.
T:System.IO.IOException:Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | -| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.TrimConfigSuffix(string baseName)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L463) | Removes a trailing native debug or release token from an artifact stem.
`baseName`:The artifact stem to normalize.
返回:The normalized artifact stem without a configuration suffix. | -| [`public static void Inno.Build.Toolchains.ToolchainEnvironment.ValidateConfiguration(string configuration)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L330) | Validates the configuration shared by all native component builds.
`configuration`:The debug or release token supplied by the build workflow.
T:System.ArgumentException:The configuration is unsupported. | -| [`public static class Inno.Build.Toolchains.ToolchainEnvironment`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L15) | Provides deterministic host-process and workspace operations shared by native dependency toolchains. | - -### `Inno.Build.Toolchains.ToolchainLayout` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_DEBUG_CONFIGURATION = "debug"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L21) | Identifies the normalized debug configuration token. | -| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_EXTERNAL_DIRECTORY_NAME = "extern"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L16) | Identifies the directory containing checked-out native dependency sources. | -| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_RELEASE_CONFIGURATION = "release"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L26) | Identifies the normalized release configuration token. | -| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_REPOSITORY_MARKER_FILE = "InnoEngine.sln"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L11) | Identifies the repository marker used while resolving a toolchain workspace. | -| [`public static class Inno.Build.Toolchains.ToolchainLayout`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L6) | Defines stable repository-relative names shared by native dependency toolchains. | - -### `Inno.Build.Toolchains.ToolchainWorkingDirectory` - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.Toolchains.ToolchainWorkingDirectory.ToolchainWorkingDirectory(string toolPath, System.IDisposable? ownership = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L26) | Captures an absolute execution path and its optional release protocol.
`toolPath`:The execution directory selected by the host provider.
`ownership`:The alias and lease owner, or null for direct physical execution.
T:System.ArgumentException:The execution path is not absolute. | -| [`public void Inno.Build.Toolchains.ToolchainWorkingDirectory.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L44) | Releases provider resources after all dependent processes have stopped. | -| [`public string Inno.Build.Toolchains.ToolchainWorkingDirectory.toolPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L39) | Gets the execution path, valid until disposal. | -| [`public sealed class Inno.Build.Toolchains.ToolchainWorkingDirectory : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L10) | Retains a provider-owned tool execution path until all dependent processes finish. | - -## 项目依赖 - -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):公开引用边界由实际签名核对。 - -## 缓存管理公开契约 - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Build.Toolchains.BuildCacheCleanupPlan Inno.Build.Toolchains.BuildCacheCleanupPlan.Create(string workspace, System.Collections.Generic.IReadOnlyList owners, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L84) | 明确注册 owner 的物理边界、协调路径和借用的清单/保护根 callbacks。 | Callbacks 在本次清理期间有效;发布及根更新使用同一协调 lease。 | 不得通过目录名猜 owner;拒绝重复、越界或重叠注册。 | - -### 进程加载保护 - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | - - -## 进程与构建所有权 API - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Build.Toolchains.BuildCacheArtifactDescriptor.BuildCacheArtifactDescriptor(string directory, string leasePath, string? targetId, long bytes, System.DateTime lastUseUtc, System.Collections.Generic.IReadOnlyList dependencies, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L48) | 冻结目录、目标、角色、协调路径、字节、UTC 使用时间与依赖;大小只服务预算。 | 描述不接管 producer 或 reader lease。 | 绝对路径、非负大小、UTC 时间和有效角色必须成立。 | -| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCacheArtifactDescriptor.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L104) | Gets the declared lifetime used for retirement and preferred-workspace accounting. | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`public enum Inno.Build.Toolchains.BuildCacheArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L6) | 明确区分不可变产物、可复用 workspace 与进程临时目录。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ImmutableArtifact = 0`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L11) | 完整产物由当前产品/Pack/活动读取保护。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ReusableWorkspace = 1`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L16) | 编译活动时保护;无活动时可按预算退休,即使最近使用。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ProcessWorkingDirectory = 2`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L21) | 进程活动或 Unknown 时保护,退出后可退休。 | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`public Inno.Build.Toolchains.BuildCachePublisher.BuildCachePublisher(string directory, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L34) | 为明确 cache 根选择生命周期,之后通过 Describe 提供登记与保护关系。 | 构造不取得 lease;发布/读取交接由调用方持有 activity 与产物保护。 | 根必须绝对且不越界;不认领未登记目录。 | -| [`public void Inno.Build.Toolchains.BuildCachePublisher.BeginWorkingDirectory(string directory, string leasePath, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L168) | 调用者取得 activity 与 producer 独占 lease 后,先登记 reusable workspace,再允许 payload 写入。 | lease 仍由调用方持有;不永久固定闲置 workspace。 | 已有无登记目录拒绝认领;目标归属按显式参数记录。 | -| [`public Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.WithDependency(Inno.Build.Toolchains.NativeBuildInput input, string fingerprint)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L190) | 把实际依赖产物和指纹冻结进后端 recipe,保留选定工具;依赖产品仅在 recipe 构造期间借用。 | 不转移 NativeBuildProduct 的读取 leases。 | 没有声明转换的依赖步骤走正常冷路径,不能猜测链接参数。 | -| [`public Inno.Build.Toolchains.ProductNativeBuildStep.ProductNativeBuildStep(string id, Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options, System.Collections.Generic.IReadOnlyList dependencies, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? build, System.Func? createRecipe, System.Func deploymentPath, string? configuration = null, System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? bindDependencies = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L52) | Freezes a component step without embedding any backend list in the shared executor. | 不可变元数据;不持有进程或文件 lease。 | 值查询没有写入副作用。 | -| [`public System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? Inno.Build.Toolchains.ProductNativeBuildStep.bindDependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L134) | 把实际依赖产物和指纹冻结进后端 recipe,保留选定工具;依赖产品仅在 recipe 构造期间借用。 | 不转移 NativeBuildProduct 的读取 leases。 | 没有声明转换的依赖步骤走正常冷路径,不能猜测链接参数。 | - - - - - - - - - - - - - -有效 MSBuild 图的节点按项目路径与完整 global properties 排序,避免同路径多配置节点的无序枚举造成错误失效。参数、include 与编译项顺序继续参与身份;验证失败会报告文件或声明变化。内部 capture 只服务诊断,不构成新的公开缓存权威。 - -工具 publication 在固定输入前,使用同一 SDK 还原入口项目及 InnoBuildInputProject 声明的辅助输入项目。辅助项目来自有效图,清理的 global properties 保持一致;Capture/Verify 本身仍只读取,不通过缺 assets 的 fallback 掩盖错误。 +| [`public static class Inno.Build.Toolchains.BuildArtifactManifest`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L15) | Validates complete build outputs against their input identity and recorded file hashes. | — | — | +| [`public static void Inno.Build.Toolchains.BuildArtifactManifest.Write(string directory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, Inno.Build.Toolchains.NativeBuildContext? context = null)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L40) | Records a completed staging tree before its owner publishes the directory.
`directory`: The complete staging root owned exclusively by the caller.
`fingerprint`: The immutable identity of every input used to produce these outputs.
`outputDirectories`: Relative output subdirectories included in this artifact; each must stay within the root.
`context`: The optional operation collecting independent output-read statistics. | — | `System.ArgumentException`: An identity or output path is invalid.
`System.IO.IOException`: An output or manifest cannot be read or written. | +| [`public static bool Inno.Build.Toolchains.BuildArtifactManifest.IsComplete(string directory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, Inno.Build.Toolchains.NativeBuildContext? context = null)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactManifest.cs#L88) | Checks the identity, exact output file set and bytes before reusing a cached artifact.
`directory`: The published artifact directory.
`fingerprint`: The requested input identity.
`outputDirectories`: The same relative output subdirectories supplied when recording the artifact.
`context`: The optional operation collecting actual output integrity reads. | True only for complete, unmodified outputs; false for a missing, malformed or mismatched manifest. | `System.ArgumentException`: An output path escapes the artifact root.
`System.IO.IOException`: An existing output cannot be read. | +| [`public sealed record Inno.Build.Toolchains.BuildArtifactOptions : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Defines the deterministic filter and naming policy used to collect one native product.
`buildDirName`: The dependency-relative directory searched for native build outputs.
`libraryTokens`: File-name tokens that identify artifacts belonging to the product.
`extensions`: The accepted native artifact extensions.
`requiredPathTokens`: Optional normalized path tokens used to reject unrelated intermediate files.
`normalizeOutputName`: The deterministic output naming policy. | — | — | +| [`public Inno.Build.Toolchains.BuildArtifactOptions.BuildArtifactOptions(string buildDirName, System.Collections.Generic.IReadOnlyCollection libraryTokens, System.Collections.Generic.IReadOnlyCollection extensions, System.Collections.Generic.IReadOnlyCollection? requiredPathTokens, System.Func normalizeOutputName)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Defines the deterministic filter and naming policy used to collect one native product.
`buildDirName`: The dependency-relative directory searched for native build outputs.
`libraryTokens`: File-name tokens that identify artifacts belonging to the product.
`extensions`: The accepted native artifact extensions.
`requiredPathTokens`: Optional normalized path tokens used to reject unrelated intermediate files.
`normalizeOutputName`: The deterministic output naming policy. | — | — | +| [`public string Inno.Build.Toolchains.BuildArtifactOptions.buildDirName { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L27) | The dependency-relative directory searched for native build outputs. | — | — | +| [`public System.Collections.Generic.IReadOnlyCollection Inno.Build.Toolchains.BuildArtifactOptions.libraryTokens { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L28) | File-name tokens that identify artifacts belonging to the product. | — | — | +| [`public System.Collections.Generic.IReadOnlyCollection Inno.Build.Toolchains.BuildArtifactOptions.extensions { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L29) | The accepted native artifact extensions. | — | — | +| [`public System.Collections.Generic.IReadOnlyCollection? Inno.Build.Toolchains.BuildArtifactOptions.requiredPathTokens { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L30) | Optional normalized path tokens used to reject unrelated intermediate files. | — | — | +| [`public System.Func Inno.Build.Toolchains.BuildArtifactOptions.normalizeOutputName { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L31) | The deterministic output naming policy. | — | — | +| [`public override string Inno.Build.Toolchains.BuildArtifactOptions.ToString()`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.BuildArtifactOptions.operator !=(Inno.Build.Toolchains.BuildArtifactOptions? left, Inno.Build.Toolchains.BuildArtifactOptions? right)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.BuildArtifactOptions.operator ==(Inno.Build.Toolchains.BuildArtifactOptions? left, Inno.Build.Toolchains.BuildArtifactOptions? right)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.BuildArtifactOptions.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.BuildArtifactOptions.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.BuildArtifactOptions.Equals(Inno.Build.Toolchains.BuildArtifactOptions? other)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Build.Toolchains.BuildArtifactOptions.Deconstruct(out string buildDirName, out System.Collections.Generic.IReadOnlyCollection libraryTokens, out System.Collections.Generic.IReadOnlyCollection extensions, out System.Collections.Generic.IReadOnlyCollection? requiredPathTokens, out System.Func normalizeOutputName)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L26) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.BuildArtifactCopier`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L37) | Copies filtered native outputs into the engine's rebuildable dependency store. | — | — | +| [`public static void Inno.Build.Toolchains.BuildArtifactCopier.CopyArtifacts(string buildRoot, string outputDir, string config, Inno.Build.Toolchains.BuildArtifactOptions options)`](../../build/toolchains/Inno.Build.Toolchains/BuildArtifactOptions.cs#L56) | Copies all artifacts accepted by one product policy into its output directory.
`buildRoot`: The native dependency root containing the configured build directory.
`outputDir`: The destination directory receiving normalized artifacts.
`config`: The normalized native build configuration.
`options`: The immutable filtering and naming policy. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildArtifactReadLease : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L13) | Pins a verified immutable build artifact against cooperating repair and retirement operations. | — | — | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildArtifactReadLease.AcquireAsync(string directory, string ownerDirectory, string fingerprint, System.Collections.Generic.IReadOnlyList outputDirectories, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Build.Toolchains.NativeBuildContext? statistics = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L55) | Acquires reading ownership and verifies the complete artifact before exposing it to a consumer.
`ownerDirectory`: The exact publisher root retaining the persistent owner.lock gate.
`directory`: The absolute artifact directory owned by its publisher; its persistent lease is stored alongside it.
`fingerprint`: The exact input identity required by the consumer.
`outputDirectories`: The complete relative output roots declared by the publisher.
`cancellationToken`: Cancels ownership acquisition without releasing another reader's protection.
`statistics`: Optional borrowed operation receiving output-integrity read statistics; its input cache is not used. | Reading ownership which the caller must retain until all artifact access completes. | `System.ArgumentException`: The directory is not absolute or a required identity is blank.
`System.IO.InvalidDataException`: The complete artifact does not match the requested identity and output inventory.
`System.TimeoutException`: Repair or retirement retains exclusive ownership for more than thirty seconds.
`System.OperationCanceledException`: Waiting was canceled before ownership could be returned. | +| [`public void Inno.Build.Toolchains.BuildArtifactReadLease.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactReadLease.cs#L68) | Ends artifact access and allows repair or retirement; repeated disposal has no effect. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildArtifactWriteLease : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L12) | Coordinates artifact replacement or retirement with every cooperating reader and writer. | — | — | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildArtifactWriteLease.AcquireAsync(string directory, string ownerDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L42) | Waits for all readers before allowing mutation of one immutable artifact directory.
`ownerDirectory`: The exact publisher root retaining the persistent owner.lock gate.
`directory`: The absolute artifact location controlled by the publisher.
`cancellationToken`: Cancels this waiter without disturbing existing readers. | Exclusive protection; retain it through replacement and release it before acquiring a reading lease. | `System.ArgumentException`: The directory is empty or relative.
`System.TimeoutException`: Other owners retain the artifact for more than thirty seconds.
`System.OperationCanceledException`: Waiting was canceled. | +| [`public void Inno.Build.Toolchains.BuildArtifactWriteLease.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildArtifactWriteLease.cs#L58) | Ends exclusive access without removing the persistent coordination file. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildCacheArtifactDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L12) | Freezes one publisher-owned cache entry and the generations it requires. | — | — | +| [`public Inno.Build.Toolchains.BuildCacheArtifactDescriptor.BuildCacheArtifactDescriptor(string directory, string leasePath, string? targetId, long bytes, System.DateTime lastUseUtc, System.Collections.Generic.IReadOnlyList dependencies, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L48) | Freezes ownership metadata supplied by the artifact's actual publisher.
`directory`: The absolute published artifact or producer-owned workspace directory.
`leasePath`: The absolute persistent file used by this entry's actual producer and readers for Core.IO coordination.
`bytes`: The publisher-recorded sum of owned file lengths, captured under writing ownership for cache budgeting. It does not measure allocated disk blocks or count shared hardlinks as recovered volume space.
`targetId`: The explicit product target, or null for a shared host artifact that target-scoped cleanup must preserve.
`lastUseUtc`: The publisher's last recorded use, used only to order retirement.
`dependencies`: Absolute artifact directories required by this generation.
`role`: The producer-declared lifetime; workspace preference alone does not pin an immutable product. | — | `System.ArgumentException`: A path is relative or the timestamp is not UTC.
`System.ArgumentOutOfRangeException`: The measured size is negative.
`System.ArgumentNullException`: The dependency collection is absent. | +| [`public string Inno.Build.Toolchains.BuildCacheArtifactDescriptor.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L84) | Gets the absolute cache entry controlled by the declaring producer. | — | — | +| [`public string Inno.Build.Toolchains.BuildCacheArtifactDescriptor.leasePath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L89) | Gets the exact existing producer/reader coordination path; retirement does not infer another lock protocol. | — | — | +| [`public string? Inno.Build.Toolchains.BuildCacheArtifactDescriptor.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L94) | Gets the producer-declared target, or null when this artifact is shared by host operations. | — | — | +| [`public long Inno.Build.Toolchains.BuildCacheArtifactDescriptor.bytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L99) | Gets the logical file size used for budgeting, without weakening content validation. | — | — | +| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCacheArtifactDescriptor.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L104) | Gets the declared lifetime used for retirement and preferred-workspace accounting. | — | — | +| [`public System.DateTime Inno.Build.Toolchains.BuildCacheArtifactDescriptor.lastUseUtc { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L109) | Gets the UTC use timestamp, which is never used to validate cached content. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheArtifactDescriptor.dependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactDescriptor.cs#L114) | Gets the frozen dependency directories protected whenever this artifact is a live root. | — | — | +| [`public enum Inno.Build.Toolchains.BuildCacheArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L6) | Declares the producer's file lifetime independently of target identity and directory naming. | — | — | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ImmutableArtifact = 0`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L11) | A complete immutable generation remains protected by selected products and active readers. | — | — | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ReusableWorkspace = 1`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L16) | A mutable compiler workspace may retire when no producer or consumer is active. | — | — | +| [`Inno.Build.Toolchains.BuildCacheArtifactRole.ProcessWorkingDirectory = 2`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheArtifactRole.cs#L21) | An operation directory remains protected until its exact process owners have exited. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildCacheCleanupPlan`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L15) | Freezes a reviewable retirement proposal for explicitly registered cache owners in one checkout. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupPlan.candidates { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L43) | Gets the original eligible candidates shared by preview and execution. | — | — | +| [`public long Inno.Build.Toolchains.BuildCacheCleanupPlan.totalBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L48) | Gets the measured total of explicitly inventoried artifact files. | — | — | +| [`public long Inno.Build.Toolchains.BuildCacheCleanupPlan.protectedBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L53) | Gets the size protected by current roots and their transitive dependencies when planning. | — | — | +| [`public static Inno.Build.Toolchains.BuildCacheCleanupPlan Inno.Build.Toolchains.BuildCacheCleanupPlan.Create(string workspace, System.Collections.Generic.IReadOnlyList owners, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll = false, string? targetId = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L95) | Plans retirement by budget pressure or an explicit request to retire every unreferenced artifact.
`workspace`: The absolute checkout boundary; no owner may manage it or its parent.
`owners`: Explicit registrations; callbacks remain borrowed until execution completes.
`policy`: The immutable size, free-space and duration budgets.
`retireAll`: Whether to propose all unreferenced generations regardless of size pressure.
`cancellationToken`: Cancels planning before any directory is removed.
`targetId`: An exact producer-declared target filter, or null to manage all registered scopes. Outside-scope generations and their dependencies remain protected. | A fixed proposal; execution rechecks ownership, roots and active readers before deletion. | `System.ArgumentException`: Owners overlap, identities repeat, or dependency metadata forms a cycle.
`System.IO.IOException`: An owner, artifact or lease escapes its boundary or traverses a link.
`System.IO.InvalidDataException`: An inventory has ambiguous ownership or undeclared dependencies.
`System.OperationCanceledException`: Planning was canceled.
`System.TimeoutException`: Inventory exceeded the complete operation budget; no artifact was removed. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildCacheCleanupPlan.ExecuteAsync(bool dryRun = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupPlan.cs#L184) | Executes the original proposal under fresh owner and artifact leases without waiting for active readers.
`dryRun`: Whether to report the proposal without acquiring mutation leases or deleting files.
`cancellationToken`: Cancels remaining work; successful earlier retirements are not rolled back. | Actual retirement, activity skips and IO failures; protected generations are never forced out. | `System.IO.InvalidDataException`: Fresh ownership metadata violates the registered boundary.
`System.OperationCanceledException`: The caller canceled; no later candidate is processed. | +| [`public sealed class Inno.Build.Toolchains.BuildCacheCleanupResult`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L11) | Reports completed retirement independently from protected, active and failed artifacts. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupResult.retired { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L30) | Gets directories actually retired; a preview always returns an empty collection. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheCleanupResult.skipped { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L35) | Gets candidates preserved because ownership, protection or dependencies changed. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.BuildCacheCleanupResult.failures { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L40) | Gets failed paths and their observable IO or permission diagnostics. | — | — | +| [`public long Inno.Build.Toolchains.BuildCacheCleanupResult.retiredBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L46) | Gets the declared file bytes of successfully retired artifacts, excluding persistent leases. This is a logical size; hardlinks, compression and other writers can affect actual volume recovery. | — | — | +| [`public bool Inno.Build.Toolchains.BuildCacheCleanupResult.timeLimitReached { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheCleanupResult.cs#L51) | Gets whether the bounded attempt ended before processing all eligible candidates. | — | — | +| [`public static class Inno.Build.Toolchains.BuildCacheOwnerCatalog`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L16) | Persists explicit publisher registrations for maintenance without loading the production distribution. | — | — | +| [`public static void Inno.Build.Toolchains.BuildCacheOwnerCatalog.Register(string engineRoot, string directory, string id, Inno.Build.Toolchains.BuildCacheArtifactRole role, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L45) | Registers a publisher root; this never adopts unrecorded artifacts inside that root.
`engineRoot`: The absolute checkout owning the registration.
`directory`: The exact publisher root beneath the checkout.
`id`: The distribution's unique owner identity.
`role`: The publisher's actual artifact lifetime.
`cancellationToken`: Cancels before registration commits. | — | `System.IO.IOException`: A registration crosses a link or ownership boundary.
`System.IO.InvalidDataException`: An existing root has a different registration.
`System.OperationCanceledException`: Cancellation precedes commit. | +| [`public static System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildCacheOwnerCatalog.ReadOwners(string engineRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerCatalog.cs#L90) | Reads explicitly registered roots using each publisher's existing inventory and protection rules.
`engineRoot`: The absolute checkout containing registrations.
`cancellationToken`: Cancels reading before a cleanup plan exists. | Frozen owner descriptors; an unprepared checkout returns an empty list. | `System.IO.InvalidDataException`: A registration is malformed or has an ambiguous identity.
`System.IO.IOException`: A registered path crosses a link or leaves the checkout. | +| [`public sealed class Inno.Build.Toolchains.BuildCacheOwnerDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L11) | Registers a publisher's exact cache boundary and its current artifact ownership inventory. | — | — | +| [`public Inno.Build.Toolchains.BuildCacheOwnerDescriptor.BuildCacheOwnerDescriptor(string id, string directory, string coordinationPath, System.Func> readArtifacts, System.Func> readProtectedArtifacts, System.Action? retireMetadata = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L47) | Borrows publisher callbacks without discovering artifacts from directory names or file extensions.
`id`: The unique registration identity within this cleanup operation.
`directory`: The absolute cache root exclusively managed by this publisher.
`coordinationPath`: The persistent owner lease also acquired by publication and protected-root updates. It must remain within the cache root and is never deleted by cleanup.
`readArtifacts`: Returns only generations proven to belong to this owner, with their dependency metadata. The callback must observe cancellation and must not create, repair or publish artifacts.
`readProtectedArtifacts`: Returns current product and Pack roots; retirement follows their complete dependency closure. Root updates must hold coordinationPath and retain each referenced artifact's reading lease.
`retireMetadata`: Optional owner-specific metadata retirement, called only under exclusive owner coordination. | — | `System.ArgumentException`: An identity is blank or a path is relative.
`System.ArgumentNullException`: An inventory callback is absent. | +| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L73) | Gets the unique publisher identity registered for this cleanup operation. | — | — | +| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L78) | Gets the exact registered cache root; its parent directories are never managed implicitly. | — | — | +| [`public string Inno.Build.Toolchains.BuildCacheOwnerDescriptor.coordinationPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCacheOwnerDescriptor.cs#L83) | Gets the persistent lease shared with publication and protected-root changes. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildCachePolicy`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L8) | Defines bounded retirement pressure without allowing active or current generations to be removed. | — | — | +| [`public Inno.Build.Toolchains.BuildCachePolicy.BuildCachePolicy(long maximumBytes = 68719476736, long minimumFreeBytes = 21474836480, System.TimeSpan? timeBudget = null)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L25) | Creates an immutable cache budget and automatic retirement time limit.
`maximumBytes`: The managed cache budget; defaults to 64 GiB.
`minimumFreeBytes`: The free-space threshold on the checkout volume; defaults to 20 GiB.
`timeBudget`: The retirement duration, or null for the five-second default. | — | `System.ArgumentOutOfRangeException`: A budget is negative or the time limit is not positive. | +| [`public long Inno.Build.Toolchains.BuildCachePolicy.maximumBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L45) | Gets the maximum size of explicitly registered build caches. | — | — | +| [`public long Inno.Build.Toolchains.BuildCachePolicy.minimumFreeBytes { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L50) | Gets the free-space threshold below which additional retirement is requested. | — | — | +| [`public System.TimeSpan Inno.Build.Toolchains.BuildCachePolicy.timeBudget { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePolicy.cs#L56) | Gets the duration after which an attempt starts no further retirement work. An already executing filesystem operation finishes before ownership is released. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildCachePublisher`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L17) | Records exact publisher-owned generations and coordinates them with bounded cache retirement. | — | — | +| [`public Inno.Build.Toolchains.BuildCachePublisher.BuildCachePublisher(string directory, Inno.Build.Toolchains.BuildCacheArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L34) | Creates a publisher for one absolute, non-overlapping cache boundary.
`directory`: The cache root exclusively controlled by this publisher.
`role`: The lifetime declared for this publisher's completed entries. | — | `System.ArgumentException`: The directory is blank or relative. | +| [`public Inno.Build.Toolchains.BuildCacheArtifactRole Inno.Build.Toolchains.BuildCachePublisher.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L50) | Gets the declared lifecycle used by this publisher's inventory and maintenance registration. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.BuildCachePublisher.AcquireUseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L64) | Pins publisher activity until publication, reading handoff and current-root updates finish.
`cancellationToken`: Cancels this wait without disturbing other owners. | Caller-owned shared activity; dispose after the final artifact reading lease is acquired. | `System.OperationCanceledException`: Acquisition was canceled. | +| [`public void Inno.Build.Toolchains.BuildCachePublisher.Record(string directory, string leasePath, System.Collections.Generic.IReadOnlyList dependencies, string? currentKey, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L105) | Records a completed generation and optionally selects it as a protected current root. The caller must hold this publisher's activity lease and the artifact's producer or reading lease.
`directory`: An existing artifact within this publisher's exact boundary.
`leasePath`: The persistent lease used by its producer and readers.
`dependencies`: Exact owned entries required while this generation is protected.
`currentKey`: A product/target/configuration root identity, or null for an unselected generation.
`cancellationToken`: Cancels before metadata selection.
`targetId`: The producer's product target, or null for a shared host generation. | — | `System.IO.IOException`: An owned path escapes the boundary, crosses a directory link, or contains an invalid file link.
`System.IO.DirectoryNotFoundException`: The completed generation is absent. | +| [`public Inno.Build.Toolchains.BuildCacheOwnerDescriptor Inno.Build.Toolchains.BuildCachePublisher.Describe(string id)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L143) | Registers only explicitly recorded ownership, without discovering artifacts by extension or directory age.
`id`: The unique publisher registration identity. | Borrowed inventory callbacks for this publisher's lifetime. | — | +| [`public void Inno.Build.Toolchains.BuildCachePublisher.BeginWorkingDirectory(string directory, string leasePath, string? targetId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L168) | Records a reusable workspace before its first payload write while the caller holds activity and producer leases. Existing unregistered directories are rejected, rather than adopted by cleanup authority.
`directory`: The new or previously registered workspace within this publisher root.
`leasePath`: The caller's persistent exclusive producer lease outside the workspace.
`targetId`: The explicit product target, or null for shared host work.
`cancellationToken`: Cancels registration before payload writes. | — | `System.InvalidOperationException`: The publisher does not manage reusable workspaces or an existing directory has no current registration.
`System.IO.IOException`: The workspace escapes its boundary or ownership cannot be written. | +| [`public void Inno.Build.Toolchains.BuildCachePublisher.RecordProcessUse(string directory, string leasePath, System.Collections.Generic.IReadOnlyList dependencies, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L212) | Registers a process-owned loader directory and its dependencies before returning it to a loader. Retirement preserves the directory until every recorded process has exited. Repeated registrations retain the complete dependency closure of that process-owned operation.
`directory`: The completed directory within this publisher's boundary.
`leasePath`: The persistent producer and retirement lease outside the directory.
`dependencies`: Owned generations and workspaces required throughout this process-owned operation.
`cancellationToken`: Cancels ownership acquisition before the process claim is recorded. | — | `System.IO.IOException`: The directory escapes ownership or its registration cannot be written.
`System.OperationCanceledException`: Ownership acquisition was canceled. | +| [`public void Inno.Build.Toolchains.BuildCachePublisher.RecordIndex(string path, System.Collections.Generic.IReadOnlyList dependencies)`](../../build/toolchains/Inno.Build.Toolchains/Cache/BuildCachePublisher.cs#L257) | Registers an exact cache index and the publication directories required to keep it useful.
`path`: The producer-owned index file within this publisher root.
`dependencies`: Exact publication roots; missing dependencies make the index retireable. | — | — | +| [`public sealed class Inno.Build.Toolchains.BuildHostServices`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L10) | Freezes host execution capabilities independently of a product target SDK. | — | — | +| [`public Inno.Build.Toolchains.BuildHostServices.BuildHostServices(Inno.Build.Toolchains.IToolchainWorkspaceProvider workspaces, System.StringComparer? environmentComparer = null, System.Collections.Generic.IEnumerable? inputPaths = null, string executableExtension = "")`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L27) | Captures execution policy and its implementation inputs.
`workspaces`: The host's scoped execution path provider.
`environmentComparer`: The host's environment variable name semantics.
`inputPaths`: Checkout-relative implementation files included in native recipe identity.
`executableExtension`: The host executable suffix, or an empty string for suffix-free executable names. | — | — | +| [`public Inno.Build.Toolchains.IToolchainWorkspaceProvider Inno.Build.Toolchains.BuildHostServices.workspaces { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L43) | Gets the host's scoped execution path provider. | — | — | +| [`public System.StringComparer Inno.Build.Toolchains.BuildHostServices.environmentComparer { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L47) | Gets environment variable name semantics. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.BuildHostServices.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L51) | Gets checkout-relative execution implementation inputs. | — | — | +| [`public string Inno.Build.Toolchains.BuildHostServices.executableExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/BuildHostServices.cs#L55) | Gets the host-owned executable suffix. | — | — | +| [`public interface Inno.Build.Toolchains.IBuildHostProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/IBuildHostProvider.cs#L6) | Discovers one explicitly registered host implementation. | — | — | +| [`Inno.Build.Toolchains.BuildHostDescriptor? Inno.Build.Toolchains.IBuildHostProvider.TryCapture()`](../../build/toolchains/Inno.Build.Toolchains/Host/IBuildHostProvider.cs#L14) | Captures the current execution machine when this provider supports it. | The immutable host, or null when the current machine is outside this provider's capabilities. | — | +| [`public interface Inno.Build.Toolchains.IToolchainWorkspaceProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L10) | Owns the relationship between physical build storage and host tool execution paths. | — | — | +| [`string Inno.Build.Toolchains.IToolchainWorkspaceProvider.identity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L15) | Gets the stable execution namespace included in compiler workspace identity. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.IToolchainWorkspaceProvider.OpenAsync(string physicalPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Host/IToolchainWorkspaceProvider.cs#L29) | Acquires an execution path for an absolute build-owned directory.
`physicalPath`: The retained physical directory.
`cancellationToken`: Cancellation while waiting for exclusive alias ownership. | A scoped path whose processes must finish before disposal. | — | +| [`public sealed class Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider : Inno.Build.Toolchains.IToolchainWorkspaceProvider`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L11) | Executes tools directly in their physical owner directory. | — | — | +| [`public string Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.identity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L14) | Gets the stable execution namespace included in compiler workspace identity. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.OpenAsync(string physicalPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L17) | Acquires an execution path for an absolute build-owned directory.
`physicalPath`: The retained physical directory.
`cancellationToken`: Cancellation while waiting for exclusive alias ownership. | A scoped path whose processes must finish before disposal. | — | +| [`public Inno.Build.Toolchains.PhysicalToolchainWorkspaceProvider.PhysicalToolchainWorkspaceProvider()`](../../build/toolchains/Inno.Build.Toolchains/Host/PhysicalToolchainWorkspaceProvider.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.BuildMaintenanceApplication`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L14) | Runs publisher-owned cache maintenance without backend, generator or Editor dependencies. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.BuildMaintenanceApplication.Run(string[] arguments, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L28) | Executes the cache-clean command using the checkout's explicit publisher catalog.
`arguments`: The clean command and its validated named options.
`cancellationToken`: Cancels planning or retirement without deleting protected outputs. | Zero on success, two on cancellation, or one with a diagnostic on failure. | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.BuildMaintenanceApplication.CleanAsync(string engineRoot, Inno.Build.Toolchains.BuildCachePolicy policy, bool retireAll, bool dryRun, string? targetId, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Maintenance/BuildMaintenanceApplication.cs#L117) | Uses the same owner inventory, dependency protection and retirement plan for automatic and explicit cleanup.
`engineRoot`: The absolute checkout containing registered publishers.
`policy`: The bounded size and duration policy.
`retireAll`: Whether to select every unreferenced generation instead of pressure candidates.
`dryRun`: Whether to report the plan without retiring it.
`targetId`: An optional exact producer target filter; shared dependencies remain protected.
`cancellationToken`: Cancels inventory and retirement. | A completed, bounded cleanup operation with protected owners retained. | `System.InvalidOperationException`: Explicit retirement encountered failures.
`System.TimeoutException`: Owner discovery or planning exhausts the operation budget before retirement can begin.
`System.IO.IOException`: Ownership metadata or protected input cannot be validated.
`System.OperationCanceledException`: The caller canceled before completion. | +| [`public sealed class Inno.Build.Toolchains.DotNetSdkDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L6) | Freezes the host, SDK identity and managed CLI entry selected by one managed entry project. | — | — | +| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.hostPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L21) | Gets the executable used for project-scoped SDK and workload resolution. | — | — | +| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.sdkIdentity { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L26) | Gets the exact SDK identity selected by the project's global.json resolution rules. | — | — | +| [`public string Inno.Build.Toolchains.DotNetSdkDescriptor.cliPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkDescriptor.cs#L32) | Gets the selected SDK's managed CLI entry assembly, invoked with the recorded host without resolving another SDK from a temporary execution directory. | — | — | +| [`public static class Inno.Build.Toolchains.DotNetSdkEnvironment`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkEnvironment.cs#L11) | Isolates a managed tool invocation from the invoking IDE's SDK and MSBuild process identity. | — | — | +| [`public static System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.DotNetSdkEnvironment.Create(string hostPath, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkEnvironment.cs#L40) | Freezes child-only overrides so the selected executable resolves the project's own SDK. Null SDK overrides remove inherited variables rather than borrowing a parent's loaded MSBuild.
`hostPath`: The explicitly selected managed executable, resolved to an absolute path before invocation.
`environment`: Additional selected toolchain variables to copy, or null when no native SDK environment is required. | A read-only child environment that preserves selected toolchain settings and never changes the parent. | `System.ArgumentException`: The managed executable is blank.
`System.IO.FileNotFoundException`: The selected managed executable cannot be resolved. | +| [`public static class Inno.Build.Toolchains.DotNetSdkResolver`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs#L12) | Resolves the managed SDK using the prepared entry project's directory and normal global.json rules. | — | — | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.DotNetSdkResolver.ResolveAsync(string hostPath, string projectPath, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs#L35) | Asks the selected host to resolve its SDK from the project location without guessing installed versions.
`hostPath`: The .NET executable selected by the build composition root.
`projectPath`: The prepared entry project whose directory controls SDK selection.
`cancellationToken`: Cancels SDK discovery and drains its child process. | The exact project-selected SDK, managed entry assembly and host; missing SDKs fail explicitly. | `System.IO.FileNotFoundException`: The selected host, prepared entry project or selected SDK entry assembly does not exist.
`System.InvalidOperationException`: The selected host cannot resolve a valid SDK identity and absolute base path. | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.DotNetSdkResolver.ResolveDirectoryAsync(string hostPath, string ownerDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/DotNetSdkResolver.cs#L75) | Selects the SDK from an authoring owner's directory before any temporary evaluation project is created.
`hostPath`: The managed host explicitly selected by the composition root.
`ownerDirectory`: The existing source owner whose ancestor global.json rules govern the operation.
`cancellationToken`: Cancels discovery and drains the SDK process. | The selected SDK and fixed managed CLI path, without probing reference pack directories. | `System.IO.DirectoryNotFoundException`: The source owner directory does not exist.
`System.IO.FileNotFoundException`: The host or selected SDK CLI is missing.
`System.InvalidOperationException`: The host cannot resolve the source owner's requested SDK.
`System.OperationCanceledException`: The caller cancels SDK discovery. | +| [`public sealed class Inno.Build.Toolchains.ManagedBuildExecution : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L17) | Owns a reusable, exclusively leased source workspace containing verified copies of an operation's inputs. | — | — | +| [`public string Inno.Build.Toolchains.ManagedBuildExecution.engineRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L51) | Gets the fixed source root used by compilation and source extraction until disposal. SDKs and restored packages retain their explicitly selected external locations. | — | — | +| [`public string Inno.Build.Toolchains.ManagedBuildExecution.project { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L56) | Gets the entry project within this execution's copied source root. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildExecution.VerifyAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L73) | Checks that owned source copies still match the bytes accepted before compilation.
`cancellationToken`: Cancels complete source verification before the consumer publishes its output. | Completion only when every owned input remains unchanged. | `System.InvalidOperationException`: A tool changed a fixed input during execution.
`System.ObjectDisposedException`: This execution no longer owns the workspace. | +| [`public void Inno.Build.Toolchains.ManagedBuildExecution.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildExecution.cs#L93) | Releases exclusive workspace ownership after every compiler and source consumer has finished. Repeated disposal has no effect; reusable owned files remain available for the next operation. | — | `System.IO.IOException`: Completed workspace ownership metadata cannot be recorded; the execution lease is still released. | +| [`public sealed class Inno.Build.Toolchains.ManagedBuildInputSnapshot`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L15) | Freezes the selected SDK's evaluated managed source graph without compiling a product. | — | — | +| [`public string Inno.Build.Toolchains.ManagedBuildInputSnapshot.fingerprint { get; }`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L41) | Gets the content identity of the evaluated graph, compiler inputs and selected SDK. | — | — | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.CreateExecutionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L59) | Materializes the operation's declared repository inputs into an exclusively owned source workspace. Compilers and source extraction must use the returned project and root, never the mutable checkout.
`cancellationToken`: Cancels ownership acquisition or verified copying before execution ownership is returned. | Caller-owned fixed execution inputs. Dispose only after all compiler and source consumers finish. | `System.InvalidOperationException`: An input differs from its planned bytes while being copied.
`System.ArgumentException`: The project does not belong to the operation's source root. | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.CaptureAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.DotNetSdkDescriptor sdk, string project, System.Collections.Generic.IReadOnlyDictionary properties, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L102) | Evaluates an explicit managed graph using its frozen SDK and hashes its declared compilation inputs.
`context`: The borrowed operation that owns phase-local content reads and tool statistics.
`sdk`: The SDK already selected for this project; evaluation must use its exact CLI entry.
`project`: The existing entry project whose references define the managed source closure.
`properties`: The immutable compiler and product properties also passed to the actual managed build.
`cancellationToken`: Cancels SDK evaluation, drains its process and removes this operation's request directory. | A snapshot that can be freshly verified after waiting or executing; no Player compilation is performed. | `System.ArgumentNullException`: A required operation, SDK or property collection is null.
`System.IO.FileNotFoundException`: A required source, compiler, restored package input or SDK declaration is absent.
`System.IO.IOException`: The source entry escapes the physical checkout or a filesystem alias cannot be resolved.
`System.InvalidOperationException`: The selected SDK cannot evaluate the declared graph.
`System.OperationCanceledException`: Evaluation or hashing was canceled. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ManagedBuildInputSnapshot.VerifyAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Managed/ManagedBuildInputSnapshot.cs#L140) | Re-evaluates the graph and re-reads its current bytes in a fresh verification phase.
`cancellationToken`: Cancels evaluation and verification without changing any published output. | Completion when the complete current input identity still matches this snapshot. | `System.InvalidOperationException`: The graph, SDK, source, resource, analyzer or reference inputs changed.
`System.OperationCanceledException`: Verification was canceled. | +| [`public sealed class Inno.Build.Toolchains.NativeBuildContext`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L13) | Identifies the checkout, configuration and frozen initial inputs owned by one native build operation. Create a new context for each operation; changes during its lifetime fail stability verification. | — | — | +| [`public Inno.Build.Toolchains.NativeBuildContext.NativeBuildContext(string engineRoot, string configuration)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L44) | Creates a build context without consulting the tool assembly's checkout.
`engineRoot`: The checkout containing InnoEngine.sln and the native source tree; aliases resolve to one physical owner.
`configuration`: The normalized debug or release configuration. | — | `System.ArgumentException`: The root is empty or the configuration is unsupported.
`System.IO.DirectoryNotFoundException`: The selected checkout has no repository marker.
`System.IO.IOException`: A checkout alias cannot be resolved to a physical owner. | +| [`public string Inno.Build.Toolchains.NativeBuildContext.engineRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L150) | Gets the frozen physical checkout used for every source, intermediate and output path. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildContext.configuration { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L155) | Gets the normalized debug or release configuration prepared by this operation. | — | — | +| [`public Inno.Build.Toolchains.NativeToolchainSelection? Inno.Build.Toolchains.NativeBuildContext.toolchain { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L161) | Gets the explicit frozen target compiler and SDK selection, or null before composition resolves it. Browser toolchains resolve their own workload-specific SDK independently. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildContext.GetNativeBuildRoot(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L180) | Resolves native intermediates under the owning toolchain project in this checkout.
`component`: The explicit component descriptor whose recipe project owns native intermediates. | The absolute project-local obj/native path, without creating the directory. During publication, the path also contains the operation's target and compatible workspace identity. A Windows producer receives a temporary junction to the same physical directory so SDK tools can use bounded paths. | `System.ArgumentNullException`: The component owner is null.
`System.InvalidOperationException`: The selected checkout does not contain the owning toolchain project. | +| [`public Inno.Build.Toolchains.NativeBuildStatistics Inno.Build.Toolchains.NativeBuildContext.statistics { get; }`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L198) | Gets actual hashing and native execution work accumulated by this operation and its scoped contexts. | — | — | +| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordBindingBatch()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L209) | Records one complete binding batch executed by the borrowed provider. | — | — | +| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordBindingGeneration()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L214) | Records one actual bridge or managed generator invocation. | — | — | +| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordOutputRead(long bytes)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L222) | Records one full artifact output read without treating it as an input snapshot hash.
`bytes`: The actual number of bytes consumed by output integrity verification. | — | — | +| [`public void Inno.Build.Toolchains.NativeBuildContext.RecordManagedProcess()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L227) | Records a managed SDK process owned by generator extension preparation. | — | — | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithToolchain(Inno.Build.Toolchains.NativeToolchainSelection toolchain)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L247) | Attaches a provider's immutable tool selection while preserving this operation's input ownership.
`toolchain`: The complete selection resolved for the requested target. | A scoped context sharing input snapshots and statistics with this operation. | `System.ArgumentNullException`: The selection is null. | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithConfiguration(string configuration)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L265) | Selects an explicit component configuration while borrowing this operation's frozen SDK and input state.
`configuration`: The debug or release configuration owned by the product step. | A derived context with no inherited workspace or artifact identity from another configuration. | `System.ArgumentException`: The requested configuration is unsupported. | +| [`public Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeBuildContext.RequireToolchain()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L277) | Requires an explicit selection before a component can build or start a native tool. | The already frozen tool selection without performing discovery. | `System.InvalidOperationException`: Composition has not resolved a toolchain. | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithComponentOptions(Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L295) | Scopes one component's explicit product configuration without changing the frozen SDK.
`component`: The sole source and intermediate owner receiving the configuration.
`options`: The immutable linkage, arguments and configuration input closure. | A context sharing inputs, bindings and statistics with the current operation. | `System.ArgumentNullException`: An owner or configuration is null. | +| [`public Inno.Build.Toolchains.NativeComponentBuildOptions Inno.Build.Toolchains.NativeBuildContext.RequireComponentOptions(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L316) | Requires product configuration for the exact component before its recipe or tools execute.
`component`: The component requesting its scoped configuration. | The immutable configuration; absent or foreign ownership fails explicitly. | `System.InvalidOperationException`: No options are assigned to this component owner. | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithBindings(Inno.Build.Toolchains.NativeBindingGenerationResult bindings)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L333) | Creates a scoped operation with an immutable, explicitly prepared target binding closure.
`bindings`: Borrowed completed generations indexed by their Native owners. Retain this result until all derived contexts finish. | A context sharing the existing tool selection, inputs and statistics. | — | +| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBuildContext.RequireBindings(Inno.Build.Toolchains.NativeComponentDescriptor component)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L356) | Requires the target binding generation prepared for an explicitly declared Native owner.
`component`: The component whose facade and managed binding share one generation. | The immutable completed generation descriptor. | `System.InvalidOperationException`: The owner has no prepared generation in this operation.
`System.ObjectDisposedException`: The caller released the borrowed binding result before this operation finished. | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.WithBindingGenerator(Inno.Build.Toolchains.INativeBindingGenerator generator)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L369) | Borrows a generator while preserving this operation's input and tool ownership.
`generator`: The shared generation implementation selected by composition. | A derived context; neither context owns or disposes the provider. | — | +| [`public Inno.Build.Toolchains.INativeBindingGenerator Inno.Build.Toolchains.NativeBuildContext.RequireBindingGenerator()`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L386) | Requires generation to be configured explicitly before creating staging or launching tools. | The borrowed provider without discovering or constructing an implementation. | `System.InvalidOperationException`: Composition has not supplied a binding generator. | +| [`public Inno.Build.Toolchains.NativeBuildContext Inno.Build.Toolchains.NativeBuildContext.BeginInputVerification(string phase)`](../../build/toolchains/Inno.Build.Toolchains/NativeBuildContext.cs#L401) | Starts a fresh verification phase whose reads can be shared only within this derived context.
`phase`: The operation phase recorded in statistics, such as binding-locks or binding-generation. | A context with a new input inventory and hash cache; existing scopes are unchanged. | — | +| [`public sealed record Inno.Build.Toolchains.BuildHostDescriptor : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Identifies the tool execution machine without selecting any product target. | — | — | +| [`public Inno.Build.Toolchains.BuildHostDescriptor.BuildHostDescriptor(string system, string architecture, Inno.Build.Toolchains.BuildHostServices? services = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L25) | Captures host facts supplied by a product or command composition boundary.
`system`: The execution operating system identity.
`architecture`: The execution processor architecture.
`services`: Explicit host execution services, or null for direct physical execution. | — | `System.ArgumentException`: A host fact is unassigned. | +| [`public string Inno.Build.Toolchains.BuildHostDescriptor.system { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L40) | Gets the operating system on which tools execute. | — | — | +| [`public string Inno.Build.Toolchains.BuildHostDescriptor.architecture { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L45) | Gets the processor architecture on which tools execute. | — | — | +| [`public Inno.Build.Toolchains.BuildHostServices Inno.Build.Toolchains.BuildHostDescriptor.services { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L50) | Gets frozen host execution services. | — | — | +| [`public override string Inno.Build.Toolchains.BuildHostDescriptor.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.BuildHostDescriptor.operator !=(Inno.Build.Toolchains.BuildHostDescriptor? left, Inno.Build.Toolchains.BuildHostDescriptor? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.BuildHostDescriptor.operator ==(Inno.Build.Toolchains.BuildHostDescriptor? left, Inno.Build.Toolchains.BuildHostDescriptor? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.BuildHostDescriptor.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.BuildHostDescriptor.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.BuildHostDescriptor.Equals(Inno.Build.Toolchains.BuildHostDescriptor? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/BuildHostDescriptor.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Build.Toolchains.ComponentNativeProductExecutor : Inno.Build.Toolchains.INativeProductExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L13) | Executes portable component producers in their declared dependency order. | — | — | +| [`public System.Threading.Tasks.Task Inno.Build.Toolchains.ComponentNativeProductExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.ProductNativeBuildPlan plan, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L16) | Builds or reuses the complete product with the operation's frozen tools.
`context`: The frozen operation.
`plan`: The complete dependency-ordered component plan.
`cancellationToken`: Cancels waits and unpublished work. | The owned complete output and binding closure. | — | +| [`public Inno.Build.Toolchains.ComponentNativeProductExecutor.ComponentNativeProductExecutor()`](../../build/toolchains/Inno.Build.Toolchains/Native/ComponentNativeProductExecutor.cs#L13) | Inherited or compiler-provided contract. | — | — | +| [`public interface Inno.Build.Toolchains.INativeBindingGenerator`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L10) | Prepares an entire binding closure without coupling native recipes to a generator or build host. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeBindingGenerator.ComputeInputFingerprintAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBindingGenerationRequest request, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L34) | Computes the selected generator source input identity without producing bindings or compiling a product.
`context`: The borrowed operation with frozen target tools and phase-local input reads.
`request`: The exact component, configuration target and output mode whose source closure is planned.
`cancellationToken`: Cancels configuration loading, dependency evaluation and content hashing. | The complete source preparation identity, distinct from any generated artifact fingerprint. It remains computable when reconstructible generation outputs have been retired. | `System.InvalidOperationException`: The selected tools, configuration, generator or extension inputs cannot be resolved.
`System.OperationCanceledException`: Input planning was canceled. | +| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeBindingGenerator.GenerateAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBindingGenerationRequest request, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeBindingGenerator.cs#L67) | Generates or verifies the requested closure under publication ownership.
`context`: The borrowed operation owning frozen inputs, tool selection and statistics. Configuration path variables must resolve from its selected toolchain without changing process state.
`request`: The immutable target, component closure and publication policy.
`cancellationToken`: Cancels preparation; active work must drain before returning. | An owned complete immutable map indexed by Native owner project, never a partial closure. Dispose the result only after all generated-source and native-bridge consumers finish. | `System.InvalidOperationException`: Inputs change during preparation, a required frozen SDK value is absent, or required generator capabilities are unavailable.
`System.IO.InvalidDataException`: A definition or generated output fails its declared contract.
`System.OperationCanceledException`: The request was canceled and its active work has completed cleanup. | +| [`public interface Inno.Build.Toolchains.INativeProductExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeProductExecutor.cs#L9) | Executes an explicitly selected native product graph without target discovery. | — | — | +| [`System.Threading.Tasks.Task Inno.Build.Toolchains.INativeProductExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.ProductNativeBuildPlan plan, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeProductExecutor.cs#L26) | Builds or reuses the complete product with the operation's frozen tools.
`context`: The frozen operation.
`plan`: The complete dependency-ordered component plan.
`cancellationToken`: Cancels waits and unpublished work. | The owned complete output and binding closure. | — | +| [`public interface Inno.Build.Toolchains.INativeToolchainProvider`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeToolchainProvider.cs#L9) | Resolves one platform's SDK for an explicit target on a declared tool execution host. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.Toolchains.INativeToolchainProvider.ResolveAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.BuildHostDescriptor host, string targetId, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/INativeToolchainProvider.cs#L29) | Freezes compiler tools, SDK inputs and child environment before a native operation starts.
`context`: The checkout and configuration owned by the requesting operation.
`host`: The explicitly declared execution host, independent of the target.
`targetId`: The exact requested target; unsupported combinations must fail.
`cancellationToken`: Cancels SDK discovery before any product staging. | A complete immutable tool selection for the requested host and target. | — | +| [`public enum Inno.Build.Toolchains.NativeArtifactRole : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L13) | Describes the link or deployment role explicitly assigned by a native producer. | — | — | +| [`Inno.Build.Toolchains.NativeArtifactRole.SharedLibrary = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L18) | A library loaded by the runtime. | — | — | +| [`Inno.Build.Toolchains.NativeArtifactRole.ImportLibrary = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L22) | A separate linker input referring to a shared library. | — | — | +| [`Inno.Build.Toolchains.NativeArtifactRole.StaticArchive = 2`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L26) | An archive linked into the final executable. | — | — | +| [`Inno.Build.Toolchains.NativeArtifactRole.SupportFile = 3`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L30) | A tool, header or other supporting payload. | — | — | +| [`public sealed record Inno.Build.Toolchains.NativeArtifact : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Identifies one exact output and its producer-declared role. | — | — | +| [`public Inno.Build.Toolchains.NativeArtifact.NativeArtifact(string logicalId, string file, Inno.Build.Toolchains.NativeArtifactRole role)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L53) | Captures an output independently of platform suffix conventions.
`logicalId`: The unique producer-owned output identity.
`file`: The absolute output path retained by the product's reading ownership.
`role`: The explicitly declared link or deployment role. | — | `System.ArgumentException`: The identity, path or role is invalid. | +| [`public string Inno.Build.Toolchains.NativeArtifact.logicalId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L69) | Gets the producer-owned output identity. | — | — | +| [`public string Inno.Build.Toolchains.NativeArtifact.file { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L73) | Gets the exact absolute output path. | — | — | +| [`public Inno.Build.Toolchains.NativeArtifactRole Inno.Build.Toolchains.NativeArtifact.role { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L77) | Gets the explicit link or deployment role. | — | — | +| [`public override string Inno.Build.Toolchains.NativeArtifact.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeArtifact.operator !=(Inno.Build.Toolchains.NativeArtifact? left, Inno.Build.Toolchains.NativeArtifact? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeArtifact.operator ==(Inno.Build.Toolchains.NativeArtifact? left, Inno.Build.Toolchains.NativeArtifact? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeArtifact.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeArtifact.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeArtifact.Equals(Inno.Build.Toolchains.NativeArtifact? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifact.cs#L36) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public static class Inno.Build.Toolchains.NativeArtifactPublisher`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifactPublisher.cs#L15) | Publishes complete native products by source and toolchain identity under process-shared ownership. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeArtifactPublisher.PublishAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeBuildRecipe recipe, System.Func build, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), System.Func>? describeOutputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeArtifactPublisher.cs#L51) | Reuses a validated product or builds an isolated candidate and publishes it after input stability checks.
`context`: Checkout and configuration selected by build composition.
`recipe`: The frozen component, target, input inventory and ordered compiler recipe.
`build`: Producer receiving an identity-scoped context, private output directory and cancellation token. It must await all work and validate required outputs before returning.
`cancellationToken`: Cancels ownership acquisition or candidate preparation before publication.
`describeOutputs`: The producer's exact role inventory, or null for generic support payloads. | A complete native product with an exact, read-only output closure. | `System.ArgumentException`: A component or target identifier is not a single path segment.
`System.IO.IOException`: An input, candidate or artifact cannot be read or published.
`System.InvalidOperationException`: Inputs change during preparation or the producer returns no complete output.
`System.OperationCanceledException`: Ownership acquisition or preparation is canceled; previous products remain installed. | +| [`public sealed class Inno.Build.Toolchains.NativeBindingCompilation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | Describes a selected component's native requirements independently of its source generator. | — | — | +| [`public required Inno.Build.Toolchains.NativeBindingSourceRoot[] Inno.Build.Toolchains.NativeBindingCompilation.roots { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L27) | Gets root bindings whose identities are local to this component. | — | — | +| [`public required Inno.Build.Toolchains.NativeBindingSourceFile[] Inno.Build.Toolchains.NativeBindingCompilation.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L32) | Gets all exact input files; only files marked compile are translation units. | — | — | +| [`public required Inno.Build.Toolchains.NativeBindingIncludeDirectory[] Inno.Build.Toolchains.NativeBindingCompilation.includeDirectories { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L37) | Gets include requirements in their original search order. | — | — | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.defines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L42) | Gets ordered definitions for manifest translation units; upstream target configuration remains component-owned. | — | — | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.compilerDefines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L47) | Gets parser target macro facts; these must not override the consuming compiler's builtins. | — | — | +| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cxxStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L52) | Gets the minimum C++ standard, or zero when no C++ translation unit is required. | — | — | +| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L57) | Gets the minimum C standard, or zero when no C translation unit is required. | — | — | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.requiredExports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L62) | Gets the exact native symbols that the component must provide. | — | — | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportSymbols { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L67) | Gets static table entry points, or an empty array for dynamic-only bindings. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBindingCompilation.staticExportAbi { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L72) | Gets the static table ABI digest, or an empty value when no static table exists. | — | — | +| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportNames { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L77) | Gets static table entries in address-resolution order. | — | — | +| [`public static Inno.Build.Toolchains.NativeBindingCompilation Inno.Build.Toolchains.NativeBindingCompilation.Load(string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L97) | Reads the current engine-owned compilation description from a selected native output.
`nativeDirectory`: The complete selected generation's native directory. | The validated description; missing or malformed descriptions fail explicitly. | `System.IO.IOException`: The description cannot be read.
`System.Text.Json.JsonException`: The description does not match the current contract.
`System.IO.InvalidDataException`: A root, path or content identity is invalid. | +| [`public string Inno.Build.Toolchains.NativeBindingCompilation.ResolveRoot(string engineRoot, string nativeDirectory, string rootId)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L124) | Resolves a component-local root using the current checkout and selected output.
`engineRoot`: The current checkout's absolute root.
`nativeDirectory`: The selected generation's absolute native output.
`rootId`: An ordinal root identity from this description. | The physical root; no other component's root table participates. | `System.IO.InvalidDataException`: The root identity is unknown. | +| [`public void Inno.Build.Toolchains.NativeBindingCompilation.VerifyInputs(System.Func resolveRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L152) | Verifies the exact bytes at original or remapped roots before their use.
`resolveRoot`: Resolves a root identity to the selected physical or copied directory.
`cancellationToken`: Cancels verification without publishing a result. | — | `System.IO.InvalidDataException`: A required file has a different length or digest.
`System.IO.IOException`: A required input is missing or cannot be read.
`System.OperationCanceledException`: Verification was canceled. | +| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingCompilation.GetInputs(string engineRoot, string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L179) | Enumerates exact file declarations for standalone or aggregate recipe fingerprinting.
`engineRoot`: The current checkout's absolute root.
`nativeDirectory`: The selected native artifact directory. | All generated, user and SDK inputs with their current physical locations. | — | +| [`public Inno.Build.Toolchains.NativeBindingCompilation.NativeBindingCompilation()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L14) | Describes the complete binding generation selected by one native build request. | — | — | +| [`public static void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.WriteSelection(string path, System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IReadOnlyDictionary linkage)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L38) | Writes the exact native binding identities for a subsequent managed build without reselecting generations. Identical content preserves the existing import timestamp; changed content is installed atomically.
`path`: The MSBuild selection document in an intermediate directory exclusively owned by the caller.
`generations`: Explicit checkout-relative native project paths and their complete selected generations.
`linkage`: Explicit linkage of each selected component, used by its managed native initialization boundary. | — | `System.ArgumentException`: A project name is unsafe or repeated, or component linkage is invalid or incomplete.
`System.ArgumentNullException`: The generation or linkage closure is null.
`System.IO.IOException`: The selection cannot be read or its complete replacement cannot be installed. | +| [`public static void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.ValidateSelection(string path, System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IReadOnlyDictionary linkage)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L81) | Rejects publication when the managed selection differs from the complete native generations to deploy.
`path`: The operation-owned selection consumed during managed compilation.
`generations`: The complete current native project closure and its selected generations.
`linkage`: The exact component linkage used to build the native closure. | — | `System.IO.InvalidDataException`: The selection differs in project closure, fingerprint, source location, linkage, or structure.
`System.ArgumentException`: The current generation closure has an unsafe or repeated project name.
`System.ArgumentNullException`: The current generation closure is null.
`System.IO.FileNotFoundException`: The managed selection is no longer available.
`System.Xml.XmlException`: The selection is not a valid XML document. | +| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.fingerprint { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L93) | Gets the immutable input identity assigned to this target generation. | — | — | +| [`public required bool Inno.Build.Toolchains.NativeBindingGenerationDescriptor.usesRuntimeImports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L99) | Gets whether the generated context uses runtime-resolved imports instead of a module provider. This initialization choice is independent of static or shared native linkage. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.bindingsPath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L104) | Gets the absolute managed source path selected by the component project. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.nativeDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L109) | Gets the complete native artifact and compilation-requirement directory, or empty when none is required. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.artifactDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L114) | Gets the immutable artifact root protected by reading leases, or an empty value for mutable host sources. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBindingGenerationDescriptor.artifactOwnerDirectory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L120) | Gets the publisher root coordinating immutable artifact readers and retirement, or empty for host sources. This owner remains unchanged when component sources are copied into a frozen build workspace. | — | — | +| [`public static Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBindingGenerationDescriptor.Load(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L140) | Reads and validates the result of a completed component generation request.
`path`: The request-owned descriptor file written after successful generation. | The selected generation whose managed source and optional bridge are present. | `System.IO.InvalidDataException`: The descriptor is missing required output identities or contains unavailable outputs.
`System.Text.Json.JsonException`: The descriptor cannot be read as the current contract.
`System.IO.IOException`: An immutable artifact escapes its declared publisher or traverses a linked directory. | +| [`public void Inno.Build.Toolchains.NativeBindingGenerationDescriptor.Write(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L166) | Atomically writes this completed generation to a request-owned descriptor file.
`path`: The descriptor destination; a unique path keeps concurrent requests independent. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor.NativeBindingGenerationDescriptor()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L14) | Inherited or compiler-provided contract. | — | — | +| [`public enum Inno.Build.Toolchains.NativeBindingOutputMode : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L11) | Selects the publication owner of generated sources. | — | — | +| [`Inno.Build.Toolchains.NativeBindingOutputMode.HostSource = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L16) | Replaces the component's authored host source and bridge outputs together. | — | — | +| [`Inno.Build.Toolchains.NativeBindingOutputMode.TargetArtifacts = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L20) | Publishes immutable target outputs under the component's generation fingerprint. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationRequest`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L26) | Freezes the complete binding request before ownership waits or candidate creation. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingGenerationRequest.NativeBindingGenerationRequest(System.Collections.Generic.IEnumerable components, string targetId, Inno.Build.Toolchains.NativeBindingOutputMode outputMode, bool checkOnly = false, System.Collections.Generic.IReadOnlyDictionary? expectedFingerprints = null, System.Collections.Generic.IReadOnlyDictionary? componentOptions = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L52) | Captures explicit owners and a target without discovering components or parsing a build project.
`components`: The component owners, each declaring one binding definition.
`targetId`: The target identity selecting a declared configuration; host output uses the host definition.
`outputMode`: Whether to write authored host sources or isolated target artifacts.
`checkOnly`: Whether to compare complete output without publishing a candidate.
`expectedFingerprints`: Optional expected identities indexed by Native project; mismatches reject the request.
`componentOptions`: Explicit component configurations keyed by native owner project, or null for component defaults. | — | `System.ArgumentException`: A target, owner, output mode or expected identity is invalid or duplicated. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBindingGenerationRequest.components { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L86) | Gets the immutable component closure owned by this request. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingGenerationRequest.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L90) | Gets the explicit configuration target, independent of the build execution host. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingOutputMode Inno.Build.Toolchains.NativeBindingGenerationRequest.outputMode { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L94) | Gets the selected output ownership policy. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingGenerationRequest.checkOnly { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L98) | Gets whether publication is forbidden and current output must match fresh generation. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeBindingGenerationRequest.expectedFingerprints { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L102) | Gets the expected identities which must match before any candidate is generated. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeBindingGenerationRequest.componentOptions { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationRequest.cs#L107) | Gets explicit component binding and linkage choices. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeBindingGenerationResult : System.Collections.Generic.IReadOnlyDictionary, System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L14) | Owns the reading protection of a complete binding closure until its final compiler consumer finishes. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingGenerationResult.NativeBindingGenerationResult(System.Collections.Generic.IReadOnlyDictionary generations, System.Collections.Generic.IEnumerable ownership)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L34) | Freezes a complete generation map and accepts its already acquired reading ownership.
`generations`: The complete Native project to generation mapping.
`ownership`: Protection transferred on successful construction; retained by the caller on failure. | — | `System.ArgumentNullException`: Either collection is null.
`System.ArgumentException`: A generation or protection entry is null. | +| [`public int Inno.Build.Toolchains.NativeBindingGenerationResult.Count { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L52) | Gets the number of elements in the collection. | The number of elements in the collection. | `System.ObjectDisposedException`: Reading ownership has ended. | +| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingGenerationResult.Keys { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L61) | Gets an enumerable collection that contains the keys in the read-only dictionary. | An enumerable collection that contains the keys in the read-only dictionary. | `System.ObjectDisposedException`: Reading ownership has ended. | +| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingGenerationResult.Values { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L70) | Gets an enumerable collection that contains the values in the read-only dictionary. | An enumerable collection that contains the values in the read-only dictionary. | `System.ObjectDisposedException`: Reading ownership has ended. | +| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor Inno.Build.Toolchains.NativeBindingGenerationResult.this[string key] { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L87) | Gets a borrowed descriptor whose outputs remain protected until this result is disposed.
`key`: The checkout-relative Native owner project. | The selected generation; neither its paths nor retained descriptors extend reading ownership. | `System.ObjectDisposedException`: Reading ownership has ended.
`System.Collections.Generic.KeyNotFoundException`: The project is outside this closure. | +| [`public bool Inno.Build.Toolchains.NativeBindingGenerationResult.ContainsKey(string key)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L93) | Determines whether the read-only dictionary contains an element that has the specified key.
`key`: The key to locate. | if the read-only dictionary contains an element that has the specified key; otherwise, . | `System.ArgumentNullException`: is .
`System.ObjectDisposedException`: Reading ownership has ended. | +| [`public bool Inno.Build.Toolchains.NativeBindingGenerationResult.TryGetValue(string key, out Inno.Build.Toolchains.NativeBindingGenerationDescriptor value)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L102) | Gets the value that is associated with the specified key.
`key`: The key to locate.
`value`: When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. | if the object that implements the interface contains an element that has the specified key; otherwise, . | `System.ArgumentNullException`: is .
`System.ObjectDisposedException`: Reading ownership has ended. | +| [`public System.Collections.Generic.IEnumerator> Inno.Build.Toolchains.NativeBindingGenerationResult.GetEnumerator()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L114) | Returns an enumerator that iterates through the collection. | An enumerator that can be used to iterate through the collection. | `System.ObjectDisposedException`: Reading ownership has ended. | +| [`public void Inno.Build.Toolchains.NativeBindingGenerationResult.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationResult.cs#L122) | Releases all protection in reverse acquisition order; repeated disposal has no effect. | — | `System.AggregateException`: One or more owners failed to release after every owner was attempted. | +| [`public sealed record Inno.Build.Toolchains.NativeBindingIncludeDirectory : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement.
`rootId`: The component-local input root.
`relativePath`: The root-relative directory, or empty for the root itself.
`system`: Whether this directory contains system headers.
`framework`: Whether the compiler must use framework search semantics. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingIncludeDirectory.NativeBindingIncludeDirectory(string rootId, string relativePath, bool system, bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement.
`rootId`: The component-local input root.
`relativePath`: The root-relative directory, or empty for the root itself.
`system`: Whether this directory contains system headers.
`framework`: Whether the compiler must use framework search semantics. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L19) | The component-local input root. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L20) | The root-relative directory, or empty for the root itself. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.system { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L21) | Whether this directory contains system headers. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.framework { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L22) | Whether the compiler must use framework search semantics. | — | — | +| [`public override string Inno.Build.Toolchains.NativeBindingIncludeDirectory.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator !=(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator ==(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBindingIncludeDirectory.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(Inno.Build.Toolchains.NativeBindingIncludeDirectory? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Build.Toolchains.NativeBindingIncludeDirectory.Deconstruct(out string rootId, out string relativePath, out bool system, out bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.NativeBindingPreparation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingPreparation.cs#L12) | Prepares a coherent target closure through the borrowed generator, without invoking MSBuild. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeBindingPreparation.PrepareAsync(Inno.Build.Toolchains.NativeBuildContext context, System.Collections.Generic.IReadOnlyList components, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? componentOptions = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingPreparation.cs#L35) | Requests one binding batch for all generated components of a product.
`context`: The operation owning a frozen target selection and configured generator.
`components`: The declared component closure; components without bindings are excluded.
`cancellationToken`: Cancels generation and drains active work before returning.
`componentOptions`: Exact per-owner component choices, or null for component defaults. | An owned generation closure, empty for a product without bindings. Retain it through final consumption. | `System.InvalidOperationException`: The tool selection or required binding generator is absent. | +| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceFile : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation.
`rootId`: The component-local input root.
`relativePath`: The portable root-relative filename.
`length`: The expected byte length.
`sha256`: The lowercase SHA-256 content identity.
`compile`: Whether this file is a translation unit rather than an include dependency. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingSourceFile.NativeBindingSourceFile(string rootId, string relativePath, long length, string sha256, bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation.
`rootId`: The component-local input root.
`relativePath`: The portable root-relative filename.
`length`: The expected byte length.
`sha256`: The lowercase SHA-256 content identity.
`compile`: Whether this file is a translation unit rather than an include dependency. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L22) | The component-local input root. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L23) | The portable root-relative filename. | — | — | +| [`public long Inno.Build.Toolchains.NativeBindingSourceFile.length { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L24) | The expected byte length. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.sha256 { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L25) | The lowercase SHA-256 content identity. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.compile { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L26) | Whether this file is a translation unit rather than an include dependency. | — | — | +| [`public override string Inno.Build.Toolchains.NativeBindingSourceFile.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator !=(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator ==(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBindingSourceFile.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(Inno.Build.Toolchains.NativeBindingSourceFile? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Build.Toolchains.NativeBindingSourceFile.Deconstruct(out string rootId, out string relativePath, out long length, out string sha256, out bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Inherited or compiler-provided contract. | — | — | +| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceRoot : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract.
`id`: An ordinal identity scoped to this component's selected generation.
`directory`: The checkout-relative or absolute external location; empty for generated output.
`generated`: Whether this root denotes the selected native output directory.
`frozen`: Whether source files must be copied before compilation. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingSourceRoot.NativeBindingSourceRoot(string id, string directory, bool generated, bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract.
`id`: An ordinal identity scoped to this component's selected generation.
`directory`: The checkout-relative or absolute external location; empty for generated output.
`generated`: Whether this root denotes the selected native output directory.
`frozen`: Whether source files must be copied before compilation. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.id { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L19) | An ordinal identity scoped to this component's selected generation. | — | — | +| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.directory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L20) | The checkout-relative or absolute external location; empty for generated output. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.generated { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L21) | Whether this root denotes the selected native output directory. | — | — | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.frozen { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L22) | Whether source files must be copied before compilation. | — | — | +| [`public override string Inno.Build.Toolchains.NativeBindingSourceRoot.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator !=(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator ==(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBindingSourceRoot.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(Inno.Build.Toolchains.NativeBindingSourceRoot? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Build.Toolchains.NativeBindingSourceRoot.Deconstruct(out string id, out string directory, out bool generated, out bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.NativeBuildFingerprint`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L13) | Derives recipe identities from ordered declarations, logical input names and complete source bytes. | — | — | +| [`public static string Inno.Build.Toolchains.NativeBuildFingerprint.Create(System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L39) | Hashes a complete declared input closure independently of checkout location and file timestamps.
`declarations`: Ordered target, configuration, SDK, compiler and linker arguments. Order is significant.
`inputs`: Files and directories with explicit logical identities; overlapping reading locations are hashed once.
`cancellationToken`: Cancels enumeration or complete content hashing before a fingerprint is returned. | A lowercase SHA-256 identity that changes when a declaration, input inventory or input byte changes. | `System.ArgumentNullException`: A required input collection is null.
`System.IO.IOException`: An input cannot be read or contains a directory-link cycle.
`System.OperationCanceledException`: Content hashing was canceled. | +| [`public static string Inno.Build.Toolchains.NativeBuildFingerprint.Create(Inno.Build.Toolchains.NativeBuildContext context, System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable inputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildFingerprint.cs#L68) | Hashes declarations using the current operation's initial snapshot or explicit fresh verification phase.
`context`: The owner of this phase's complete input inventory and content hashes.
`declarations`: Ordered non-file inputs; declaration order remains significant.
`inputs`: Complete inputs with stable logical identities and physical read locations.
`cancellationToken`: Cancels complete enumeration and hashing. | The SHA-256 identity; repeated physical reads within this phase are reused. | — | +| [`public sealed record Inno.Build.Toolchains.NativeBuildInput : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Separates the reproducible identity of an input from its physical reading location. | — | — | +| [`public Inno.Build.Toolchains.NativeBuildInput.NativeBuildInput(string logicalPath, string physicalPath)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L23) | Declares one file or directory consumed by a native recipe.
`logicalPath`: A nonempty, slash-separated identity relative to a declared logical root.
`physicalPath`: The absolute file or directory used only for reading and execution. | — | `System.ArgumentException`: An identity is empty, contains a control character or backslash, or the physical path is relative. | +| [`public string Inno.Build.Toolchains.NativeBuildInput.logicalPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L42) | Gets the identity included in the recipe fingerprint. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildInput.physicalPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L47) | Gets the reading location, which is not implicitly included in the fingerprint. | — | — | +| [`public static Inno.Build.Toolchains.NativeBuildInput Inno.Build.Toolchains.NativeBuildInput.FromPath(string engineRoot, string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L61) | Declares a checkout-relative input, or an external SDK location whose path affects tool selection.
`engineRoot`: The absolute checkout root assigned the logical prefix engine/.
`path`: A checkout-relative input or an absolute SDK input. | A normalized declaration preserving external SDK location identity. | — | +| [`public override string Inno.Build.Toolchains.NativeBuildInput.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildInput.operator !=(Inno.Build.Toolchains.NativeBuildInput? left, Inno.Build.Toolchains.NativeBuildInput? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildInput.operator ==(Inno.Build.Toolchains.NativeBuildInput? left, Inno.Build.Toolchains.NativeBuildInput? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBuildInput.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBuildInput.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBuildInput.Equals(Inno.Build.Toolchains.NativeBuildInput? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildInput.cs#L9) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed record Inno.Build.Toolchains.NativeBuildPhaseStatistics : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Describes actual full reads and physical read reuse within one explicitly bounded input phase. | — | — | +| [`public required string Inno.Build.Toolchains.NativeBuildPhaseStatistics.phase { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L11) | Gets the named operation phase which owns these read results. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L15) | Gets the number of complete physical file reads. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.bytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L19) | Gets the number of bytes actually read for hashing. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildPhaseStatistics.reusedReads { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L23) | Gets repeated physical hash requests satisfied from this phase's cache. | — | — | +| [`public override string Inno.Build.Toolchains.NativeBuildPhaseStatistics.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.operator !=(Inno.Build.Toolchains.NativeBuildPhaseStatistics? left, Inno.Build.Toolchains.NativeBuildPhaseStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.operator ==(Inno.Build.Toolchains.NativeBuildPhaseStatistics? left, Inno.Build.Toolchains.NativeBuildPhaseStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBuildPhaseStatistics.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBuildPhaseStatistics.Equals(Inno.Build.Toolchains.NativeBuildPhaseStatistics? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public Inno.Build.Toolchains.NativeBuildPhaseStatistics.NativeBuildPhaseStatistics()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildPhaseStatistics.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeBuildProduct : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L11) | Identifies one complete, integrity-checked native artifact and its exact published file closure. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildProduct.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L46) | Gets the component identity supplied by the owning toolchain. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildProduct.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L51) | Gets the target ABI selected for this build. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildProduct.fingerprint { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L56) | Gets the content fingerprint covering sources, tool executables and toolchain declarations. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildProduct.directory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L61) | Gets the immutable artifact root containing Outputs and its integrity manifest. | — | — | +| [`public Inno.Build.Toolchains.NativeBindingGenerationDescriptor? Inno.Build.Toolchains.NativeBuildProduct.bindingGeneration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L67) | Gets the exact binding generation compiled into this product, or null for a component without bindings. Managed consumers must retain this selection rather than resolve a new generation after native preparation. | — | — | +| [`public string? Inno.Build.Toolchains.NativeBuildProduct.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L72) | Gets the shared-library suffix declared by the selected SDK, or null for an SDK-independent producer. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildProduct.files { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L77) | Gets the exact absolute output file paths validated by the publisher. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildProduct.artifacts { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L82) | Gets the exact producer-declared output and link roles. | — | — | +| [`public void Inno.Build.Toolchains.NativeBuildProduct.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildProduct.cs#L88) | Releases the artifact after all deployment, compilation and linking consumers finish. File paths must not be used afterwards; repeated disposal has no effect. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeBuildRecipe`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L11) | Freezes a component's input closure, ordered tool selection and intermediate owner. | — | — | +| [`public Inno.Build.Toolchains.NativeBuildRecipe.NativeBuildRecipe(Inno.Build.Toolchains.NativeComponentDescriptor owner, string component, string targetId, System.Collections.Generic.IEnumerable inputs, System.Collections.Generic.IEnumerable declarations)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L38) | Creates a complete recipe without inferring implementation identity from an assembly MVID.
`owner`: The explicit component descriptor identifying source and intermediate owners.
`component`: The component's portable artifact path segment.
`targetId`: The ABI's portable artifact path segment.
`inputs`: The complete source, generator, executor and SDK input closure.
`declarations`: Ordered compiler, linker, include-search and non-file inputs; order is significant. | — | `System.ArgumentException`: A component or target is not a portable segment, or an input is null.
`System.ArgumentNullException`: A required owner or collection is null. | +| [`public Inno.Build.Toolchains.NativeComponentDescriptor Inno.Build.Toolchains.NativeBuildRecipe.owner { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L65) | Gets the declared intermediate owner independently of assembly location and MVID. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildRecipe.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L70) | Gets the component's artifact path segment. | — | — | +| [`public string Inno.Build.Toolchains.NativeBuildRecipe.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L75) | Gets the target ABI's artifact path segment. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildRecipe.inputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L80) | Gets the frozen declarations of physical inputs and their logical identities. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildRecipe.declarations { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L85) | Gets ordered non-file inputs; callers must explicitly sort unordered metadata before construction. | — | — | +| [`public static Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.CreateForComponent(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor owner, string component, string targetId, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable declarations, System.Collections.Generic.IEnumerable implementationPaths)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L120) | Declares a component's source, execution and SDK inputs before binding outputs need to exist. The artifact publisher attaches the exact selected generation before hashing or compiling.
`context`: The operation's checkout and previously selected host tools.
`owner`: The component project whose implementation participates in recipe identity.
`component`: The native component artifact segment.
`targetId`: The selected target ABI.
`inputPaths`: All handwritten native sources, component tools and build definitions. Selected managed bindings and generated bridges are attached by the publisher.
`declarations`: Ordered component arguments and generation identities.
`implementationPaths`: The complete explicit implementation inputs used by this component, excluding unrelated product code. | The frozen component declaration, without generating bindings or reading generated output. Unrelated build and Editor implementation assemblies do not affect it. | `System.IO.FileNotFoundException`: A required executor or component project is absent from the checkout. | +| [`public Inno.Build.Toolchains.NativeBuildRecipe Inno.Build.Toolchains.NativeBuildRecipe.WithDependency(Inno.Build.Toolchains.NativeBuildInput input, string fingerprint)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildRecipe.cs#L190) | Adds one explicitly selected published dependency while preserving the recipe's frozen execution tools.
`input`: The dependency file whose bytes participate in compilation.
`fingerprint`: The immutable dependency artifact identity selected by the product. | A new declaration with the dependency input and identity; this recipe is unchanged. | `System.ArgumentException`: The dependency identity is empty.
`System.ArgumentNullException`: The input is null. | +| [`public sealed record Inno.Build.Toolchains.NativeBuildStatistics : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Reports actual input reading and compiler process work accumulated by one build context. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.hashedFiles { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L13) | Gets the number of complete file hashes read, including required stability verification. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.hashedBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L18) | Gets the actual bytes consumed by input hashing, excluding output integrity validation. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.nativeProcesses { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L24) | Gets native execution processes started through the context's frozen tool selection. SDK discovery and managed task bootstrapping are recorded by their separate build logs. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeBuildStatistics.phases { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L29) | Gets immutable phase-specific read accounting accumulated by this operation. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.bindingBatches { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L34) | Gets complete binding batches requested by this operation. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.bindingGenerations { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L39) | Gets actual bridge or managed generation invocations, excluding valid artifact reuse. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.materializedBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L44) | Gets bytes copied to freeze mutable repository inputs. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.outputFiles { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L49) | Gets independent artifact output hashes performed for integrity verification and publication. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.outputBytes { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L54) | Gets bytes read from actual outputs, separately from source snapshot verification. | — | — | +| [`public long Inno.Build.Toolchains.NativeBuildStatistics.managedProcesses { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L59) | Gets managed SDK processes started for generator extension preparation and managed input evaluation. | — | — | +| [`public override string Inno.Build.Toolchains.NativeBuildStatistics.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildStatistics.operator !=(Inno.Build.Toolchains.NativeBuildStatistics? left, Inno.Build.Toolchains.NativeBuildStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.Toolchains.NativeBuildStatistics.operator ==(Inno.Build.Toolchains.NativeBuildStatistics? left, Inno.Build.Toolchains.NativeBuildStatistics? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.Toolchains.NativeBuildStatistics.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Build.Toolchains.NativeBuildStatistics.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Build.Toolchains.NativeBuildStatistics.Equals(Inno.Build.Toolchains.NativeBuildStatistics? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public Inno.Build.Toolchains.NativeBuildStatistics.NativeBuildStatistics()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBuildStatistics.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.NativeCMakeExecutor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L13) | Executes component-owned CMake definitions using an already frozen target SDK selection. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeCMakeExecutor.PrepareSourceAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor component, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L39) | Copies the recipe's declared checkout inputs before a cold CMake invocation.
`context`: The producer scope supplied by NativeArtifactPublisher while holding its intermediate lease.
`component`: The sole owner of the intermediate source copy.
`cancellationToken`: Cancels copying before the source candidate is installed. | The same prepared input descriptor for subsequent invocations in this producer scope. | `System.InvalidOperationException`: No recipe scope exists or an input changed during copying.
`System.OperationCanceledException`: Copying was canceled before publication.
`System.ArgumentNullException`: The context or component is null. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.NativeCMakeExecutor.BuildAsync(Inno.Build.Toolchains.NativeBuildContext context, Inno.Build.Toolchains.NativeComponentDescriptor component, string sourceDirectory, string? target, System.Collections.Generic.IReadOnlyList componentArguments, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L86) | Configures and builds an explicit component target without detecting the current platform.
`context`: The recipe-owned producer scope supplied by NativeArtifactPublisher, with frozen tools and inputs.
`component`: The unique component owner of target-scoped intermediate output.
`sourceDirectory`: The declared component-owned source directory, resolved to the operation's independent copy.
`target`: The declared CMake build target, or null for the complete selected project.
`componentArguments`: Semantic component options; SDK and generator arguments come from the selection.
`cancellationToken`: Cancels and drains the complete tool process tree. | The owned CMake build directory after successful compilation. | `System.InvalidOperationException`: Tools are unassigned or configuration or compilation fails.
`System.OperationCanceledException`: The operation was canceled.
`System.ArgumentException`: Product definitions conflict with a semantic definition owned by the component recipe. | +| [`public static string Inno.Build.Toolchains.NativeCMakeExecutor.FindOutput(Inno.Build.Toolchains.NativeBuildContext context, string buildDirectory, string filePattern)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeExecutor.cs#L171) | Selects one declared output while excluding CMake's compiler probes and other build configurations.
`context`: The selected configuration and target generator policy.
`buildDirectory`: The component-owned CMake intermediate directory.
`filePattern`: The component's exact basename or bounded filename pattern. | The sole matching output, or an explicit failure for a missing or ambiguous result. | `System.IO.FileNotFoundException`: The component did not produce exactly one expected output. | +| [`public sealed class Inno.Build.Toolchains.NativeCMakeSource`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L15) | Describes copied recipe inputs owned by one cold CMake producer. External SDK locations remain assigned to the frozen toolchain. | — | — | +| [`public string Inno.Build.Toolchains.NativeCMakeSource.root { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L52) | Gets the isolated source root; this is neither the checkout nor an output installation directory. | — | — | +| [`public string Inno.Build.Toolchains.NativeCMakeSource.ResolvePath(string path)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L69) | Resolves a declared checkout or selected binding input to its copied counterpart.
`path`: An absolute recipe input path. External SDK paths retain their original location. | The copied checkout or binding input, or the unchanged external SDK location. | `System.ArgumentException`: The path is empty or relative.
`System.InvalidOperationException`: A checkout or selected binding path was not declared by the recipe. | +| [`public string Inno.Build.Toolchains.NativeCMakeSource.ResolveDefinition(string argument)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCMakeSource.cs#L94) | Resolves declared input paths in one CMake definition without redirecting output or SDK locations.
`argument`: An ordered CMake argument; definition values may contain semicolon-separated paths. | The definition with known input paths redirected, or the original nondefinition argument. | `System.ArgumentNullException`: The argument is null. | +| [`public sealed class Inno.Build.Toolchains.NativeCompilerInputs`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L11) | Freezes SDK-resolved native compiler and header inputs without interpreting environment conventions. | — | — | +| [`public Inno.Build.Toolchains.NativeCompilerInputs.NativeCompilerInputs(string? cCompiler = null, string? cppCompiler = null, string? sysRoot = null, System.Collections.Generic.IEnumerable? systemIncludeDirectories = null, System.Collections.Generic.IEnumerable? cppSystemIncludeDirectories = null, System.Collections.Generic.IEnumerable? defines = null, System.Collections.Generic.IEnumerable? arguments = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L40) | Captures explicit parser inputs supplied by the target SDK provider.
`cCompiler`: The selected C compiler, or null when this selection does not provide one.
`cppCompiler`: The selected C++ compiler, or null when this selection does not provide one.
`sysRoot`: The SDK root, or null when the compiler does not use a sysroot.
`systemIncludeDirectories`: Ordered system header directories.
`cppSystemIncludeDirectories`: Ordered C++ standard library directories.
`defines`: Ordered target preprocessor definitions.
`arguments`: Ordered parser arguments supplied by the SDK. | — | `System.ArgumentException`: An assigned physical path is not absolute. | +| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.cCompiler { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L61) | Gets the explicit C compiler, or null when unavailable. | — | — | +| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.cppCompiler { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L65) | Gets the explicit C++ compiler, or null when unavailable. | — | — | +| [`public string? Inno.Build.Toolchains.NativeCompilerInputs.sysRoot { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L69) | Gets the target SDK root, or null when unnecessary. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.systemIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L73) | Gets ordered SDK header directories. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.cppSystemIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L77) | Gets ordered C++ standard library directories. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.defines { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L81) | Gets ordered target definitions. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeCompilerInputs.arguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeCompilerInputs.cs#L85) | Gets ordered target parser arguments. | — | — | +| [`public enum Inno.Build.Toolchains.NativeLibraryKind : int`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L11) | Specifies the linkage required by a product's native component operation. | — | — | +| [`Inno.Build.Toolchains.NativeLibraryKind.Static = 0`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L16) | Produces archives for the selected final linker. | — | — | +| [`Inno.Build.Toolchains.NativeLibraryKind.Shared = 1`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L21) | Produces libraries loaded by the deployed product. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeComponentBuildOptions`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L27) | Freezes product-owned linkage, ordered component definitions and their configuration inputs. | — | — | +| [`public Inno.Build.Toolchains.NativeComponentBuildOptions.NativeComponentBuildOptions(Inno.Build.Toolchains.NativeLibraryKind libraryKind, System.Collections.Generic.IEnumerable? cmakeArguments = null, System.Collections.Generic.IEnumerable? inputPaths = null, string? bindingLibraryName = null, System.Collections.Generic.IEnumerable? bindingIncludeDirectories = null, Inno.Build.Toolchains.NativeLinkPlan? linkPlan = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L55) | Captures component configuration without selecting an SDK or starting a build.
`libraryKind`: The explicitly requested linkage; a recipe must support it before execution starts.
`cmakeArguments`: Ordered, unique CMake definitions belonging to this component, excluding SDK and linkage selection.
`inputPaths`: Portable checkout-relative configuration files included in content hashing and stability checks.
`linkPlan`: Component-specific platform link settings, or null for the selected SDK plan.
`bindingLibraryName`: The explicit native import identity, or null to retain the component identity.
`bindingIncludeDirectories`: Additional checkout-relative binding headers owned by an integration. | — | `System.ArgumentException`: Linkage is invalid, definitions are ambiguous or override SDK selection, or an input escapes the checkout. | +| [`public Inno.Build.Toolchains.NativeLibraryKind Inno.Build.Toolchains.NativeComponentBuildOptions.libraryKind { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L91) | Gets the linkage selected by the product rather than by the execution host. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L96) | Gets the immutable ordered component definitions; order contributes to artifact identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L101) | Gets logical configuration input paths whose actual bytes participate in the recipe. | — | — | +| [`public string? Inno.Build.Toolchains.NativeComponentBuildOptions.bindingLibraryName { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L106) | Gets the explicit native import identity, or null for the component default. | — | — | +| [`public Inno.Build.Toolchains.NativeLinkPlan? Inno.Build.Toolchains.NativeComponentBuildOptions.linkPlan { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L111) | Gets component-specific platform link settings, or null for the target defaults. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeComponentBuildOptions.bindingIncludeDirectories { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentBuildOptions.cs#L116) | Gets additional checkout-relative binding header roots. | — | — | +| [`public sealed class Inno.Build.Toolchains.NativeComponentDescriptor`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L10) | Declares the unique source and build owners of a reusable native component. | — | — | +| [`public Inno.Build.Toolchains.NativeComponentDescriptor.NativeComponentDescriptor(string id, string nativeProject, string toolchainProject, Inno.Build.Toolchains.NativeStaticBuildDefinition? staticBuild = null, string? bindingDefinition = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L33) | Freezes portable checkout-relative owner locations without assembly-name or directory inference.
`id`: The portable component artifact identity.
`nativeProject`: The checkout-relative native project file.
`toolchainProject`: The checkout-relative build implementation project file.
`staticBuild`: The backend-owned static recipe, or null when this component has none.
`bindingDefinition`: The portable binding definition, or null when the component generates no bindings. | — | `System.ArgumentException`: An identity or path is empty, absolute, or escapes its checkout. | +| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L59) | Gets the portable artifact identity. | — | — | +| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.nativeProject { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L64) | Gets the explicit checkout-relative Native owner project. | — | — | +| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.toolchainProject { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L69) | Gets the explicit checkout-relative recipe owner project. | — | — | +| [`public Inno.Build.Toolchains.NativeStaticBuildDefinition? Inno.Build.Toolchains.NativeComponentDescriptor.staticBuild { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L74) | Gets the declared static build capability without requiring a platform-specific component project. | — | — | +| [`public string? Inno.Build.Toolchains.NativeComponentDescriptor.bindingDefinition { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L79) | Gets the declared binding definition, or null for a component without generated bindings. | — | — | +| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.GetNativeRoot(string engineRoot)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L90) | Resolves the declared native owner within an explicitly supplied checkout.
`engineRoot`: The absolute operation-owned checkout. | The absolute native project directory, without creating it. | — | +| [`public string Inno.Build.Toolchains.NativeComponentDescriptor.GetToolchainRoot(string engineRoot)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeComponentDescriptor.cs#L101) | Resolves the declared recipe owner within an explicitly supplied checkout.
`engineRoot`: The absolute operation-owned checkout. | The absolute toolchain project directory, without creating it. | — | +| [`public delegate System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Creates explicitly selected deployment metadata from a frozen Native closure.
`products`: Borrowed complete products whose leases remain held through metadata preparation.
`files`: Exact relative deployment paths mapped to verified source files. | Relative metadata paths mapped to immutable bytes; collisions with payloads are rejected. | — | +| [`public Inno.Build.Toolchains.NativeDeploymentMetadataFactory.NativeDeploymentMetadataFactory(object @object, nint method)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public virtual System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory.Invoke(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Creates explicitly selected deployment metadata from a frozen Native closure.
`products`: Borrowed complete products whose leases remain held through metadata preparation.
`files`: Exact relative deployment paths mapped to verified source files. | Relative metadata paths mapped to immutable bytes; collisions with payloads are rejected. | — | +| [`public virtual System.IAsyncResult Inno.Build.Toolchains.NativeDeploymentMetadataFactory.BeginInvoke(System.Collections.Generic.IReadOnlyList products, System.Collections.Generic.IReadOnlyDictionary files, System.AsyncCallback callback, object @object)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public virtual System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeDeploymentMetadataFactory.EndInvoke(System.IAsyncResult result)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeDeploymentMetadataFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public static class Inno.Build.Toolchains.NativeInputMaterializer`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeInputMaterializer.cs#L13) | Materializes frozen native inputs without rewriting identical intermediate files. | — | — | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.NativeInputMaterializer.CopyAsync(Inno.Build.Toolchains.NativeBuildContext context, string source, string destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeInputMaterializer.cs#L39) | Copies a declared input atomically when its destination differs, preserving identical file timestamps.
`context`: The operation whose initial source bytes and hashing statistics are shared across components.
`source`: The absolute input file already declared by the component recipe.
`destination`: The absolute intermediate file owned exclusively by the component producer.
`cancellationToken`: Cancels copying before publication and preserves the prior intermediate file. | True when bytes were copied and published; false when the existing content was identical. | `System.InvalidOperationException`: The source changes relative to the operation's frozen input snapshot.
`System.IO.IOException`: The source, destination or atomic candidate cannot be read or published. | +| [`public sealed class Inno.Build.Toolchains.NativeLinkPlan`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L10) | Freezes platform library formats and linker configuration independently of component recipes. | — | — | +| [`public Inno.Build.Toolchains.NativeLinkPlan.NativeLinkPlan(string? sharedLibraryExtension = null, string? importLibraryExtension = null, string? staticLibraryExtension = null, System.Collections.Generic.IEnumerable? cmakeArguments = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L30) | Captures explicitly supported library formats and ordered linker configuration.
`sharedLibraryExtension`: The shared-library suffix, or null when shared linkage is unsupported.
`importLibraryExtension`: The separate import-library suffix, or null when the shared library is the link input.
`staticLibraryExtension`: The archive suffix, or null when static linkage is not contributed.
`cmakeArguments`: Ordered platform-owned CMake linker definitions. | — | `System.ArgumentException`: No format is supported, an import format lacks shared linkage, a suffix is invalid, or an argument is null. | +| [`public string? Inno.Build.Toolchains.NativeLinkPlan.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L52) | Gets the shared-library suffix, or null when shared linkage is unsupported. | — | — | +| [`public string? Inno.Build.Toolchains.NativeLinkPlan.importLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L56) | Gets the separate import suffix, or null when no separate import library is used. | — | — | +| [`public string? Inno.Build.Toolchains.NativeLinkPlan.staticLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L60) | Gets the static archive suffix, or null when static linkage is unavailable. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeLinkPlan.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L64) | Gets ordered platform linker definitions. | — | — | +| [`public string Inno.Build.Toolchains.NativeLinkPlan.GetStaticFileName(string logicalName)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L78) | Formats an explicitly declared logical archive using the selected platform format.
`logicalName`: The component-owned logical archive name. | The exact platform file name. | `System.NotSupportedException`: Static linkage has no registered archive format. | +| [`public string Inno.Build.Toolchains.NativeLinkPlan.GetFileExtension(Inno.Build.Toolchains.NativeLibraryKind kind)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeLinkPlan.cs#L95) | Resolves an explicitly supported linkage format before staging or generation begins.
`kind`: The component's requested linkage. | The selected file extension; unsupported linkage fails immediately. | `System.NotSupportedException`: The platform does not contribute this linkage format.
`System.ArgumentOutOfRangeException`: The linkage kind is not defined. | +| [`public sealed class Inno.Build.Toolchains.NativeStaticBuildDefinition`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L10) | Declares a backend-owned static-library recipe and its complete checkout input closure. | — | — | +| [`public Inno.Build.Toolchains.NativeStaticBuildDefinition.NativeStaticBuildDefinition(string cmakeFile, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable logicalOutputs, string? nativeSourceDirectory = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L33) | Freezes the component's CMake entry point, source dependencies and exact installed archive names.
`cmakeFile`: The portable checkout-relative component definition.
`inputPaths`: The portable checkout-relative source and build dependencies.
`logicalOutputs`: The exact archive file names required after installation.
`nativeSourceDirectory`: The declared checkout-relative root used by the component's native CMake fragment, or null when no such fragment is consumed. | — | `System.ArgumentException`: A path escapes its root or an archive name is invalid or duplicated.
`System.ArgumentNullException`: A required collection is null. | +| [`public string Inno.Build.Toolchains.NativeStaticBuildDefinition.cmakeFile { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L58) | Gets the component-owned CMake definition, which participates in artifact identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeStaticBuildDefinition.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L63) | Gets the immutable complete source dependency paths relative to the checkout. | — | — | +| [`public string? Inno.Build.Toolchains.NativeStaticBuildDefinition.nativeSourceDirectory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L68) | Gets the declared native fragment's source root, or null when the recipe only consumes its other inputs. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeStaticBuildDefinition.logicalOutputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeStaticBuildDefinition.cs#L73) | Gets the exact required output closure within this component's target directory. | — | — | +| [`public static class Inno.Build.Toolchains.NativeToolchainPreparation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L11) | Supplies shared environment isolation and SDK input validation without selecting a platform. | — | — | +| [`public static System.Collections.Generic.Dictionary Inno.Build.Toolchains.NativeToolchainPreparation.CreateEnvironment()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L26) | Creates a child-process environment that clears undeclared ambient compiler policy. | A caller-owned dictionary to populate with explicitly selected SDK variables. | — | +| [`public static Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeToolchainPreparation.Freeze(string targetId, Inno.Build.Toolchains.BuildHostDescriptor host, System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IReadOnlyDictionary environment, System.Collections.Generic.IEnumerable inputs, System.Collections.Generic.IEnumerable cmakeArguments, Inno.Build.Toolchains.NativeLinkPlan linkPlan, bool multiConfiguration, Inno.Build.Toolchains.NativeCompilerInputs? compilerInputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainPreparation.cs#L65) | Validates and freezes a provider's complete tool selection before product staging.
`targetId`: The explicit requested target.
`host`: The declared tool execution host.
`tools`: Selected executable paths.
`environment`: Isolated child environment.
`inputs`: Selected compiler and SDK inputs.
`cmakeArguments`: Ordered target configuration arguments.
`multiConfiguration`: Whether configuration is chosen during build.
`linkPlan`: The sole source of target library formats and linker capabilities.
`compilerInputs`: The selected SDK parser inputs, or null for a selection without parsing capability. | An immutable selection with validated physical tool and SDK inputs. | `System.IO.FileNotFoundException`: A selected SDK input or executable is absent. | +| [`public sealed class Inno.Build.Toolchains.NativeToolchainSelection`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L12) | Freezes target compiler tools and SDK inputs independently of discovery and platform policy. | — | — | +| [`public Inno.Build.Toolchains.NativeToolchainSelection.NativeToolchainSelection(string targetId, Inno.Build.Toolchains.BuildHostDescriptor host, System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IReadOnlyDictionary environment, System.Collections.Generic.IEnumerable inputPaths, System.Collections.Generic.IEnumerable cmakeArguments, Inno.Build.Toolchains.NativeLinkPlan linkPlan, bool multiConfiguration, Inno.Build.Toolchains.NativeCompilerInputs? compilerInputs = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L55) | Captures an immutable, explicit compiler and SDK selection supplied by a platform provider.
`targetId`: The exact native target identity.
`host`: The machine on which the selected tools execute.
`tools`: Absolute executable paths indexed by explicit command names.
`environment`: Frozen SDK values applied to owned tools and in-process binding configuration without changing process state.
`inputPaths`: Compiler, SDK and tool files participating in product identity.
`cmakeArguments`: Ordered configuration arguments pinning compiler, generator and SDK.
`multiConfiguration`: Whether configuration is selected during build rather than configure.
`linkPlan`: The sole source of target library formats and linker capabilities.
`compilerInputs`: Explicit SDK parser inputs, or null when no native parser is provided. | — | `System.ArgumentException`: The selection is incomplete or has unassigned target facts.
`System.ArgumentNullException`: A required descriptor or collection is null.
`System.IO.FileNotFoundException`: A selected executable or SDK input is unavailable before preparation begins. | +| [`public string Inno.Build.Toolchains.NativeToolchainSelection.targetId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L115) | Gets the target identity, without inferring it from the execution host. | — | — | +| [`public Inno.Build.Toolchains.BuildHostDescriptor Inno.Build.Toolchains.NativeToolchainSelection.host { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L120) | Gets the declared tool execution host. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.inputPaths { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L125) | Gets the immutable SDK and compiler input closure. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.declarations { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L130) | Gets ordered tool and environment facts included in the recipe fingerprint. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.NativeToolchainSelection.cmakeArguments { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L135) | Gets configuration arguments selecting the compiler, generator and SDK. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.NativeToolchainSelection.environment { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L140) | Gets frozen SDK values supplied to owned tools and in-process binding configuration. | — | — | +| [`public string Inno.Build.Toolchains.NativeToolchainSelection.sharedLibraryExtension { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L148) | Gets the target's shared-library suffix. | — | `System.NotSupportedException`: The target does not support shared libraries. | +| [`public bool Inno.Build.Toolchains.NativeToolchainSelection.multiConfiguration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L153) | Gets whether the selected generator supports build-time configuration selection. | — | — | +| [`public Inno.Build.Toolchains.NativeCompilerInputs Inno.Build.Toolchains.NativeToolchainSelection.compilerInputs { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L158) | Gets the target provider's explicit parser inputs. | — | — | +| [`public Inno.Build.Toolchains.NativeLinkPlan Inno.Build.Toolchains.NativeToolchainSelection.linkPlan { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L163) | Gets the platform-owned linker capabilities. | — | — | +| [`public Inno.Build.Toolchains.NativeToolchainSelection Inno.Build.Toolchains.NativeToolchainSelection.WithTools(System.Collections.Generic.IReadOnlyDictionary tools, System.Collections.Generic.IEnumerable inputs)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L183) | Adds integration-owned tools without changing the platform SDK or replacing existing commands.
`tools`: Explicit absolute executables required by the selected backend integration.
`inputs`: Integration implementation and configuration files participating in tool selection. | A new immutable selection containing both the platform and integration inputs. | `System.ArgumentException`: A contribution conflicts with an already selected command.
`System.IO.FileNotFoundException`: A contributed tool or input does not exist. | +| [`public string Inno.Build.Toolchains.NativeToolchainSelection.ResolveExecutable(string name)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L212) | Resolves an executable from the frozen selection rather than a mutable process PATH.
`name`: The explicit command registered by the platform provider. | The selected executable; an absent command fails immediately. | `System.InvalidOperationException`: The provider did not register the requested command. | +| [`public bool Inno.Build.Toolchains.NativeToolchainSelection.TryResolveExecutable(string name, out string executable)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeToolchainSelection.cs#L231) | Checks an optional tool capability without discovering a command from the process environment.
`name`: The declared tool identity to check.
`executable`: The frozen absolute path when present, or an empty string when absent. | True only when this selection explicitly supplies the requested tool. | `System.ArgumentException`: The tool identity is empty. | +| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildPlan`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L14) | Executes a frozen product closure with one target toolchain and no implicit backend selection. | — | — | +| [`public Inno.Build.Toolchains.ProductNativeBuildPlan.ProductNativeBuildPlan(string productId, System.Collections.Generic.IReadOnlyList steps, Inno.Build.Toolchains.NativeDeploymentMetadataFactory? deploymentMetadata = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L36) | Validates the complete ordered component graph before tools or staging can start.
`productId`: The product identity whose native closure is selected.
`steps`: Unique, dependency-ordered component operations.
`deploymentMetadata`: Optional product-owned metadata preparation, without backend discovery. | — | `System.ArgumentException`: The product, component identity or dependency ordering is invalid.
`System.ArgumentNullException`: The step collection is null. | +| [`public string Inno.Build.Toolchains.ProductNativeBuildPlan.productId { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L60) | Gets the explicit product identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildPlan.steps { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L65) | Gets the frozen dependency-ordered component closure. | — | — | +| [`public Inno.Build.Toolchains.NativeDeploymentMetadataFactory? Inno.Build.Toolchains.ProductNativeBuildPlan.deploymentMetadata { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L70) | Gets the product's explicit metadata preparation, or null when no metadata is required. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildPlan.CreateRecipes(Inno.Build.Toolchains.NativeBuildContext context)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L91) | Resolves the complete selected source recipe closure before binding generation or native compilation.
`context`: The borrowed operation with a frozen target SDK; each step supplies its own component options. | Immutable recipes in declaration order, including each component's selected tools and parameters. Actual generated bytes and dependency artifacts are added by the producer before publication. | `System.ArgumentNullException`: The context is null.
`System.InvalidOperationException`: Tools are unassigned or a declaration returns a foreign owner, component or target.
`System.NotSupportedException`: A static component does not support the selected target. | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Build.Toolchains.ProductNativeBuildPlan.CreateDeploymentFiles(System.Collections.Generic.IReadOnlyList products)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildPlan.cs#L133) | Resolves exact deployment paths from the declared layout rather than component-name conventions.
`products`: The complete result of this plan, in declaration order. | Owned immutable relative-output to source-file mappings for atomic deployment. | `System.ArgumentException`: Products differ from the plan or deployment paths collide or escape their root. | +| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildResult : System.Collections.Generic.IReadOnlyList, System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L12) | Owns a complete ordered Native product closure and releases its reading leases together. | — | — | +| [`public Inno.Build.Toolchains.ProductNativeBuildResult.ProductNativeBuildResult(System.Collections.Generic.IEnumerable products, Inno.Build.Toolchains.NativeBindingGenerationResult bindings, string? aggregateDirectory = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L30) | Takes ownership of a complete native and binding closure.
`products`: Owned products in dependency order.
`bindings`: The owned matching binding batch.
`aggregateDirectory`: A complete aggregate install directory, or null for individual products. | — | — | +| [`public int Inno.Build.Toolchains.ProductNativeBuildResult.Count { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L43) | Gets the number of products in the completed build plan. | — | — | +| [`public string? Inno.Build.Toolchains.ProductNativeBuildResult.aggregateDirectory { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L48) | Gets the complete aggregate install directory, or null for individually published products. | — | — | +| [`public Inno.Build.Toolchains.NativeBuildProduct Inno.Build.Toolchains.ProductNativeBuildResult.this[int index] { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L63) | Borrows a product whose lease is owned by this result until disposal.
`index`: The zero-based position in the plan's declaration order. | The borrowed product; the caller must finish access before disposing the result. | `System.ObjectDisposedException`: The result no longer owns the products. | +| [`public System.Collections.Generic.IEnumerator Inno.Build.Toolchains.ProductNativeBuildResult.GetEnumerator()`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L81) | Enumerates borrowed products in plan order without transferring their ownership. | An enumerator which must finish before this result is disposed. | `System.ObjectDisposedException`: The result has already released its leases. | +| [`public void Inno.Build.Toolchains.ProductNativeBuildResult.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildResult.cs#L94) | Releases products in reverse dependency order after all consumers have finished. Repeated disposal has no effect. | — | `System.AggregateException`: One or more reading owners failed to release after every owner was attempted. | +| [`public sealed class Inno.Build.Toolchains.ProductNativeBuildStep`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L12) | Binds one product component to an owned build operation and explicit deployment layout. | — | — | +| [`public Inno.Build.Toolchains.ProductNativeBuildStep.ProductNativeBuildStep(string id, Inno.Build.Toolchains.NativeComponentDescriptor component, Inno.Build.Toolchains.NativeComponentBuildOptions options, System.Collections.Generic.IReadOnlyList dependencies, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? build, System.Func? createRecipe, System.Func deploymentPath, string? configuration = null, System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? bindDependencies = null)`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L52) | Freezes a component step without embedding any backend list in the shared executor.
`id`: The expected published component identity.
`component`: The unique source and recipe owners.
`options`: Explicit product linkage, ordered component parameters and their input closure.
`dependencies`: Earlier component identities required by this operation.
`build`: The awaited standalone producer, or null when the component is executed by a static aggregate.
`createRecipe`: Declares the standalone producer's source inputs without creating generated outputs, or null for a component whose complete source definition is owned by a static aggregate.
`deploymentPath`: Maps a relative output and target to a deployment path, or null for build-only inputs.
`configuration`: An explicit component configuration, or null to use the product configuration.
`bindDependencies`: Optional backend-owned transformation adding the exact published dependency inputs to its source recipe. It must not generate outputs or execute tools. | — | `System.ArgumentException`: The identity or dependency set is invalid.
`System.ArgumentNullException`: A required owner, producer or path policy is null. | +| [`public string Inno.Build.Toolchains.ProductNativeBuildStep.id { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L92) | Gets the exact expected component identity. | — | — | +| [`public Inno.Build.Toolchains.NativeComponentDescriptor Inno.Build.Toolchains.ProductNativeBuildStep.component { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L97) | Gets the explicit source and recipe owners. | — | — | +| [`public Inno.Build.Toolchains.NativeComponentBuildOptions Inno.Build.Toolchains.ProductNativeBuildStep.options { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L102) | Gets product-owned configuration independently of the selected SDK. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.Toolchains.ProductNativeBuildStep.dependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L107) | Gets dependencies that must precede this step. | — | — | +| [`public System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task>? Inno.Build.Toolchains.ProductNativeBuildStep.build { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L112) | Gets the standalone producer, or null for a component owned by a static aggregate executor. | — | — | +| [`public System.Func? Inno.Build.Toolchains.ProductNativeBuildStep.createRecipe { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L118) | Gets the producer's source declaration, or null when a static aggregate owns the component recipe. The declaration must not generate bindings, run a compiler or create staging. | — | — | +| [`public System.Func Inno.Build.Toolchains.ProductNativeBuildStep.deploymentPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L123) | Gets the explicit output layout policy; null results exclude build-only files. | — | — | +| [`public string? Inno.Build.Toolchains.ProductNativeBuildStep.configuration { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L128) | Gets the step's explicit configuration, or null to borrow the containing product configuration. | — | — | +| [`public System.Func, Inno.Build.Toolchains.NativeBuildRecipe>? Inno.Build.Toolchains.ProductNativeBuildStep.bindDependencies { get; }`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeBuildStep.cs#L134) | Gets the backend-owned dependency transformation, or null for an independent source recipe. The supplied products are borrowed only during recipe construction. | — | — | +| [`public static class Inno.Build.Toolchains.ProductNativeDeployment`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L15) | Materializes an explicitly built host-native closure into one application's deployment directory. | — | — | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ProductNativeDeployment.InstallAsync(System.Collections.Generic.IReadOnlyList products, Inno.Build.Toolchains.ProductNativeBuildPlan plan, string applicationDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L46) | Validates the application's native tree and atomically replaces it when its exact contents differ.
`products`: Validated runtime and optional authoring products returned by the declared product plan. All products must use the same target; each component can appear once.
`cancellationToken`: Cancels ownership acquisition or staging before the atomic installation starts.
`plan`: The explicit component closure and deployment mapping used to produce these products.
`applicationDirectory`: The managed output directory receiving the complete native tree. | Completion after the complete native tree has been validated under exclusive publication ownership. Identical deployments retain their files, including libraries loaded by a running application. | `System.ArgumentException`: The product closure is empty, contains duplicate components or mixes targets.
`System.IO.IOException`: A product cannot be validated, copied or installed; the prior native tree remains available.
`System.OperationCanceledException`: The operation was canceled before installation; the previous native tree is unchanged. | +| [`public static System.Threading.Tasks.ValueTask Inno.Build.Toolchains.ProductNativeDeployment.StageAsync(System.Collections.Generic.IReadOnlyList products, Inno.Build.Toolchains.ProductNativeBuildPlan plan, string applicationDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/ProductNativeDeployment.cs#L87) | Writes a new native tree into an unpublished candidate exclusively owned by the caller.
`products`: The pinned, validated products to copy into the candidate.
`plan`: The explicit component layout and neutral deployment metadata.
`applicationDirectory`: An existing caller-owned candidate with no native subtree.
`cancellationToken`: Cancels copying or final input verification before the caller publishes. | Completion after copying and verifying payload, without creating transaction metadata in the candidate. | `System.ArgumentException`: The product closure or candidate path is invalid.
`System.IO.DirectoryNotFoundException`: The caller has not created its owned candidate.
`System.IO.IOException`: The native tree already exists, or an input cannot be verified or copied.
`System.OperationCanceledException`: Cancellation prevents completing the unpublished candidate. | +| [`public static class Inno.Build.Toolchains.ToolchainEnvironment`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L15) | Provides deterministic host-process and workspace operations shared by native dependency toolchains. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary environment)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L50) | Executes a declared absolute tool with an explicitly resolved SDK environment and records the operation cost.
`context`: The operation owning execution statistics and frozen recipe inputs.
`fileName`: The absolute executable declared by the recipe; no host-native SDK is inferred.
`arguments`: Individual arguments passed without shell interpretation.
`workingDirectory`: The owned source or intermediate directory.
`cancellationToken`: Cancels execution and drains the complete process tree.
`environment`: The frozen environment supplied by the selected SDK resolver. | Completion after successful exit and output delivery. | `System.ArgumentException`: The executable is not an absolute declared tool path.
`System.InvalidOperationException`: The process fails to start or exits unsuccessfully.
`System.OperationCanceledException`: Execution was canceled. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L93) | Runs a host-native process with the context's frozen compiler and SDK selection.
`context`: A context resolved by NativeToolchainSelection before artifact fingerprinting.
`fileName`: A selected build command or an absolute executable declared as a component input.
`arguments`: Individual arguments passed without shell interpretation.
`workingDirectory`: The source snapshot or intermediate directory owned by the operation.
`cancellationToken`: Cancels and retires the complete process tree. | Completion after successful exit and complete output delivery. | `System.InvalidOperationException`: Host tools were not resolved, a command is unavailable or a process fails.
`System.OperationCanceledException`: The operation was canceled. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(Inno.Build.Toolchains.NativeBuildContext context, string fileName, string arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L134) | Runs a quoted host command with a frozen compiler and SDK environment.
`context`: The context resolved before artifact fingerprinting.
`fileName`: A selected command or an absolute executable declared in the component inputs.
`arguments`: Arguments quoted for the selected executable's command-line parser.
`workingDirectory`: The operation's source snapshot or intermediate directory.
`cancellationToken`: Cancels and retires the complete process tree. | Completion after the command exits successfully and its streams drain. | `System.InvalidOperationException`: Tools are unresolved or execution fails.
`System.OperationCanceledException`: The operation was canceled. | +| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.ResolveExecutable(string name)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L164) | Resolves one explicitly selected executable before it becomes a build input.
`name`: An executable path or a command name available on the current process PATH. | The absolute executable path used for process execution and artifact identity. | `System.ArgumentException`: The name is empty.
`System.IO.FileNotFoundException`: The executable is unavailable at the supplied path or on PATH. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L216) | Runs a child build with structured arguments, cancellation and hidden windows.
`fileName`: The executable to launch.
`arguments`: The individual arguments, passed without shell interpretation.
`workingDirectory`: The repository or staging directory owned by the operation.
`cancellationToken`: Cancels the child process and its complete process tree.
`environment`: Optional process-local toolchain variables; the parent environment is unchanged. Null values remove inherited variables, while empty values remain explicitly empty. | Completion after both output streams have drained and the process succeeds. | `System.InvalidOperationException`: The process fails to start or exits unsuccessfully.
`System.OperationCanceledException`: The operation was canceled.
`System.IO.IOException`: Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment, System.IO.TextWriter standardOutput, System.IO.TextWriter standardError)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L261) | Runs a child build with caller-owned output destinations and the shared process retirement protocol.
`fileName`: The executable selected by the build composition.
`arguments`: Structured arguments passed without shell interpretation.
`workingDirectory`: The project directory controlling SDK and workload resolution.
`cancellationToken`: Cancels the complete process tree and drains both output streams.
`environment`: Optional child-only toolchain variables. Null values remove inherited variables; empty values remain explicitly empty.
`standardOutput`: The caller-owned standard output destination; the runner does not dispose it.
`standardError`: The caller-owned standard error destination; the runner does not dispose it. | Completion after successful exit and complete output delivery; failures propagate. | `System.InvalidOperationException`: The executable cannot start or exits unsuccessfully.
`System.IO.IOException`: An output destination fails; the owned process tree is terminated before failure returns.
`System.OperationCanceledException`: The operation was canceled after retiring its owned process tree. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.CaptureOutputAsync(string fileName, System.Collections.Generic.IReadOnlyList arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IReadOnlyDictionary? environment = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L309) | Captures a tool's standard output while forwarding errors and preserving the common process lifecycle.
`fileName`: The executable to launch.
`arguments`: Structured arguments passed without shell interpretation.
`workingDirectory`: The directory used for project and SDK resolution.
`cancellationToken`: Cancels the child process tree and waits for its complete exit.
`environment`: Optional child-only environment variables. Null values remove inherited variables; empty values remain explicitly empty. | The complete standard output after successful exit; failures and cancellation propagate. | `System.InvalidOperationException`: The tool fails to start or exits unsuccessfully.
`System.OperationCanceledException`: The operation was canceled.
`System.IO.IOException`: Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | +| [`public static void Inno.Build.Toolchains.ToolchainEnvironment.ValidateConfiguration(string configuration)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L330) | Validates the configuration shared by all native component builds.
`configuration`: The debug or release token supplied by the build workflow. | — | `System.ArgumentException`: The configuration is unsupported. | +| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.FindRepoRoot()`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L345) | Resolves the repository containing the currently executing toolchain assembly. | The absolute path of the repository root. | `System.InvalidOperationException`: Thrown when no repository marker exists above the toolchain assembly directory. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.RunAsync(string fileName, string arguments, string workingDirectory, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L390) | Runs a child build using a complete argument string, without a shell.
`fileName`: The executable resolved by the host operating system.
`arguments`: Arguments quoted for the selected executable's command-line parser.
`workingDirectory`: The directory owned by the operation.
`cancellationToken`: Cancels the complete process tree and waits for output to finish draining. | Completion after successful exit; failures and cancellation propagate. | `System.InvalidOperationException`: The process fails to start or exits unsuccessfully.
`System.OperationCanceledException`: The operation was canceled.
`System.IO.IOException`: Redirected output cannot be read or forwarded; the process tree is terminated before failure returns. | +| [`public static bool Inno.Build.Toolchains.ToolchainEnvironment.ContainsAny(string value, params string[] needles)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L417) | Determines whether a value contains at least one token without regard to casing.
`value`: The value searched for candidate tokens.
`needles`: The candidate tokens tested against the value. | when at least one token occurs in the value; otherwise . | — | +| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.NormalizeOutputName(string fileName, string config)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L444) | Normalizes a native artifact name and appends its build configuration.
`fileName`: The source artifact file name.
`config`: The normalized configuration appended to the artifact stem. | The deterministic output file name. | — | +| [`public static string Inno.Build.Toolchains.ToolchainEnvironment.TrimConfigSuffix(string baseName)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L463) | Removes a trailing native debug or release token from an artifact stem.
`baseName`: The artifact stem to normalize. | The normalized artifact stem without a configuration suffix. | — | +| [`public static void Inno.Build.Toolchains.ToolchainEnvironment.DeleteDirectory(string path)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L486) | Deletes one explicitly resolved toolchain output directory when it exists.
`path`: The exact directory selected by a toolchain command. | — | `System.ArgumentException`: Thrown when is empty or does not resolve to an absolute path. | +| [`public static System.Threading.Tasks.Task Inno.Build.Toolchains.ToolchainEnvironment.CaptureOutputAsync(System.Diagnostics.ProcessStartInfo start, System.Threading.CancellationToken cancellationToken)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainEnvironment.cs#L512) | Captures platform tool discovery through the same cancellable process lifecycle as builds.
`start`: The prepared discovery command, including any platform-specific argument quoting.
`cancellationToken`: Cancels discovery and waits for its process tree to exit. | Complete standard output after successful discovery; errors and cancellation propagate. | — | +| [`public static class Inno.Build.Toolchains.ToolchainLayout`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L6) | Defines stable repository-relative names shared by native dependency toolchains. | — | — | +| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_REPOSITORY_MARKER_FILE = "InnoEngine.sln"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L11) | Identifies the repository marker used while resolving a toolchain workspace. | — | — | +| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_EXTERNAL_DIRECTORY_NAME = "extern"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L16) | Identifies the directory containing checked-out native dependency sources. | — | — | +| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_DEBUG_CONFIGURATION = "debug"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L21) | Identifies the normalized debug configuration token. | — | — | +| [`public const string Inno.Build.Toolchains.ToolchainLayout.C_RELEASE_CONFIGURATION = "release"`](../../build/toolchains/Inno.Build.Toolchains/ToolchainLayout.cs#L26) | Identifies the normalized release configuration token. | — | — | +| [`public sealed class Inno.Build.Toolchains.ToolchainWorkingDirectory : System.IDisposable`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L10) | Retains a provider-owned tool execution path until all dependent processes finish. | — | — | +| [`public Inno.Build.Toolchains.ToolchainWorkingDirectory.ToolchainWorkingDirectory(string toolPath, System.IDisposable? ownership = null)`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L26) | Captures an absolute execution path and its optional release protocol.
`toolPath`: The execution directory selected by the host provider.
`ownership`: The alias and lease owner, or null for direct physical execution. | — | `System.ArgumentException`: The execution path is not absolute. | +| [`public string Inno.Build.Toolchains.ToolchainWorkingDirectory.toolPath { get; }`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L39) | Gets the execution path, valid until disposal. | — | — | +| [`public void Inno.Build.Toolchains.ToolchainWorkingDirectory.Dispose()`](../../build/toolchains/Inno.Build.Toolchains/ToolchainWorkingDirectory.cs#L44) | Releases provider resources after all dependent processes have stopped. | — | — | -### 输入路径别名与创作归属 - -同一校验阶段按最终物理路径去重哈希,但保留每次读取的原路径和 checkout 相对逻辑身份。macOS 的 `/var` 别名、目录链接与 Windows junction 不会把所属源码误记成外部 SDK 输入;固定编译副本仍按所属源码布局准备。独立锁等待、编译及提交后重新校验,不跨阶段复用内容结论。 - -## 原生生成编译契约 API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| 当前签名 | 语义 | -| --- | --- | -| [`public sealed class Inno.Build.Toolchains.NativeBindingCompilation`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | Describes a selected component's native requirements independently of its source generator. | -| [`public required Inno.Build.Toolchains.NativeBindingSourceRoot[] Inno.Build.Toolchains.NativeBindingCompilation.roots { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L27) | Gets root bindings whose identities are local to this component. | -| [`public required Inno.Build.Toolchains.NativeBindingSourceFile[] Inno.Build.Toolchains.NativeBindingCompilation.files { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L32) | Gets all exact input files; only files marked compile are translation units. | -| [`public required Inno.Build.Toolchains.NativeBindingIncludeDirectory[] Inno.Build.Toolchains.NativeBindingCompilation.includeDirectories { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L37) | Gets include requirements in their original search order. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.defines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L42) | Gets ordered definitions for manifest translation units; upstream target configuration remains component-owned. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.compilerDefines { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L47) | Gets parser target macro facts; these must not override the consuming compiler's builtins. | -| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cxxStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L52) | Gets the minimum C++ standard, or zero when no C++ translation unit is required. | -| [`public required int Inno.Build.Toolchains.NativeBindingCompilation.cStandard { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L57) | Gets the minimum C standard, or zero when no C translation unit is required. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.requiredExports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L62) | Gets the exact native symbols that the component must provide. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportSymbols { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L67) | Gets static table entry points, or an empty array for dynamic-only bindings. | -| [`public required string Inno.Build.Toolchains.NativeBindingCompilation.staticExportAbi { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L72) | Gets the static table ABI digest, or an empty value when no static table exists. | -| [`public required string[] Inno.Build.Toolchains.NativeBindingCompilation.staticExportNames { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L77) | Gets static table entries in address-resolution order. | -| [`public static Inno.Build.Toolchains.NativeBindingCompilation Inno.Build.Toolchains.NativeBindingCompilation.Load(string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L97) | Reads the current engine-owned compilation description from a selected native output. | -| [`public string Inno.Build.Toolchains.NativeBindingCompilation.ResolveRoot(string engineRoot, string nativeDirectory, string rootId)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L124) | Resolves a component-local root using the current checkout and selected output. | -| [`public void Inno.Build.Toolchains.NativeBindingCompilation.VerifyInputs(System.Func resolveRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L152) | Verifies the exact bytes at original or remapped roots before their use. | -| [`public System.Collections.Generic.IEnumerable Inno.Build.Toolchains.NativeBindingCompilation.GetInputs(string engineRoot, string nativeDirectory)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L179) | Enumerates exact file declarations for standalone or aggregate recipe fingerprinting. | -| [`public Inno.Build.Toolchains.NativeBindingCompilation.NativeBindingCompilation()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingCompilation.cs#L16) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public sealed record Inno.Build.Toolchains.NativeBindingIncludeDirectory : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement. | -| [`public Inno.Build.Toolchains.NativeBindingIncludeDirectory.NativeBindingIncludeDirectory(string rootId, string relativePath, bool system, bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Preserves the order and search semantics of one native include requirement. | -| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L19) | The component-local input root. | -| [`public string Inno.Build.Toolchains.NativeBindingIncludeDirectory.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L20) | The root-relative directory, or empty for the root itself. | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.system { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L21) | Whether this directory contains system headers. | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.framework { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L22) | Whether the compiler must use framework search semantics. | -| [`public override string Inno.Build.Toolchains.NativeBindingIncludeDirectory.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Returns a string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator !=(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.operator ==(Inno.Build.Toolchains.NativeBindingIncludeDirectory? left, Inno.Build.Toolchains.NativeBindingIncludeDirectory? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public override int Inno.Build.Toolchains.NativeBindingIncludeDirectory.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Serves as the default hash function. | -| [`public override bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Determines whether the specified object is equal to the current object. | -| [`public bool Inno.Build.Toolchains.NativeBindingIncludeDirectory.Equals(Inno.Build.Toolchains.NativeBindingIncludeDirectory? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | Indicates whether the current object is equal to another object of the same type. | -| [`public void Inno.Build.Toolchains.NativeBindingIncludeDirectory.Deconstruct(out string rootId, out string relativePath, out bool system, out bool framework)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingIncludeDirectory.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceFile : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation. | -| [`public Inno.Build.Toolchains.NativeBindingSourceFile.NativeBindingSourceFile(string rootId, string relativePath, long length, string sha256, bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Identifies exact generated or external bytes needed by a native compilation. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.rootId { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L22) | The component-local input root. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.relativePath { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L23) | The portable root-relative filename. | -| [`public long Inno.Build.Toolchains.NativeBindingSourceFile.length { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L24) | The expected byte length. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceFile.sha256 { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L25) | The lowercase SHA-256 content identity. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.compile { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L26) | Whether this file is a translation unit rather than an include dependency. | -| [`public override string Inno.Build.Toolchains.NativeBindingSourceFile.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Returns a string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator !=(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceFile.operator ==(Inno.Build.Toolchains.NativeBindingSourceFile? left, Inno.Build.Toolchains.NativeBindingSourceFile? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public override int Inno.Build.Toolchains.NativeBindingSourceFile.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Serves as the default hash function. | -| [`public override bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Determines whether the specified object is equal to the current object. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceFile.Equals(Inno.Build.Toolchains.NativeBindingSourceFile? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | Indicates whether the current object is equal to another object of the same type. | -| [`public void Inno.Build.Toolchains.NativeBindingSourceFile.Deconstruct(out string rootId, out string relativePath, out long length, out string sha256, out bool compile)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceFile.cs#L21) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public sealed record Inno.Build.Toolchains.NativeBindingSourceRoot : System.IEquatable`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract. | -| [`public Inno.Build.Toolchains.NativeBindingSourceRoot.NativeBindingSourceRoot(string id, string directory, bool generated, bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Identifies one component-local root without exposing a generator-specific contract. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.id { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L19) | An ordinal identity scoped to this component's selected generation. | -| [`public string Inno.Build.Toolchains.NativeBindingSourceRoot.directory { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L20) | The checkout-relative or absolute external location; empty for generated output. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.generated { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L21) | Whether this root denotes the selected native output directory. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.frozen { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L22) | Whether source files must be copied before compilation. | -| [`public override string Inno.Build.Toolchains.NativeBindingSourceRoot.ToString()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Returns a string that represents the current object. | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator !=(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public static bool Inno.Build.Toolchains.NativeBindingSourceRoot.operator ==(Inno.Build.Toolchains.NativeBindingSourceRoot? left, Inno.Build.Toolchains.NativeBindingSourceRoot? right)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | -| [`public override int Inno.Build.Toolchains.NativeBindingSourceRoot.GetHashCode()`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Serves as the default hash function. | -| [`public override bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(object? obj)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Determines whether the specified object is equal to the current object. | -| [`public bool Inno.Build.Toolchains.NativeBindingSourceRoot.Equals(Inno.Build.Toolchains.NativeBindingSourceRoot? other)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | Indicates whether the current object is equal to another object of the same type. | -| [`public void Inno.Build.Toolchains.NativeBindingSourceRoot.Deconstruct(out string id, out string directory, out bool generated, out bool frozen)`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingSourceRoot.cs#L18) | 该 record 的值比较、复制或格式化成员;不持有生成结果的 lease。 | - - +## 项目依赖 -| 声明 | 行为与约束 | +| 工程 | 边界 | | --- | --- | -| [`public required bool Inno.Build.Toolchains.NativeBindingGenerationDescriptor.usesRuntimeImports { get; init; }`](../../build/toolchains/Inno.Build.Toolchains/Native/NativeBindingGenerationDescriptor.cs#L99) | Gets whether the generated context uses runtime-resolved imports instead of a module provider. This initialization choice is independent of static or shared native linkage. | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.IO` | 公开签名依赖 | diff --git a/docs/build/Inno.Build.md b/docs/build/Inno.Build.md index 0c3e59a1c..d7069ab9b 100644 --- a/docs/build/Inno.Build.md +++ b/docs/build/Inno.Build.md @@ -1,5 +1,12 @@ # Inno.Build +## 脚本 SDK 接入 + +`SdkScriptCompilationEnvironment` 是 Build 到脚本编译器的适配边界:Host 提供实际 dotnet host 与 Project 根目录,每次捕获选定 SDK 的引用和符号,再返回中立的 `ScriptCompilationInputs`。它不把 MSBuild、Roslyn 或平台类型暴露给编译器,不维护 reference pack 版本列表。 + +SDK 从创作源 owner 的目录选定。求值工程与还原中间态由 `Library/Scripting/SdkInputs` 下的 Core IO process owner 管理,操作排空后释放。取消不会留下可用发布,错误保留 SDK 输出。该适配器不修改 SDK、NuGet 全局缓存或 Project 的 global.json,也不生成依赖锁清单。 + + [Build 索引](README.md) · [Wiki 首页](../README.md) · [Runtime](../runtime/Inno.Runtime.md) ## 职责与边界 @@ -101,40 +108,17 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 ## 当前源码公开 API 清单 -当前声明由源码语义清单核对,包含 public 和可供外部派生实现使用的 protected 成员。构建契约不作为游戏脚本导出;线程、失败与所有权要求见前文及每项 XML。 - -### `Inno.Build.BuildDiagnosticSeverity` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`Inno.Build.BuildDiagnosticSeverity.Error = 2`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L23) | Reports a condition that prevented a valid build. | -| [`Inno.Build.BuildDiagnosticSeverity.Information = 0`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L13) | Provides non-failing build context. | -| [`Inno.Build.BuildDiagnosticSeverity.Warning = 1`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L18) | Reports a condition that did not prevent output creation. | -| [`public enum Inno.Build.BuildDiagnosticSeverity : int`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L8) | Identifies the severity of one structured build diagnostic. | - -### `Inno.Build.BuildDiagnostic` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildDiagnostic.BuildDiagnostic(Inno.Build.BuildDiagnosticSeverity severity, string code, string message)`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L46) | Creates an immutable build diagnostic.
`severity`:The impact of the diagnostic on build success.
`code`:The stable diagnostic code.
`message`:The human-readable explanation.
T:System.ArgumentException:Thrown when code or message is empty. | -| [`public string Inno.Build.BuildDiagnostic.code { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L66) | Gets the stable diagnostic code. | -| [`public string Inno.Build.BuildDiagnostic.message { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L71) | Gets the human-readable explanation. | -| [`public Inno.Build.BuildDiagnosticSeverity Inno.Build.BuildDiagnostic.severity { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L61) | Gets the impact of the diagnostic on build success. | -| [`public sealed class Inno.Build.BuildDiagnostic`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L29) | Describes one stable machine-readable build problem or informational event. | - -### `Inno.Build.BuildPipeline` - @@ -143,20 +127,6 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildPipeline.BuildPipeline(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Plugins.Authoring.PluginEnvironment plugins, Inno.Core.Settings.ProjectSettingsStore settings, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Reload.GenerationCoordinator generations, Inno.Scripting.Compiler.ScriptCompiler compiler, string supportPackRoot, System.Collections.Generic.IEnumerable gameTargets, Inno.Build.Managed.ManagedDeploymentCatalog managedDeployments, Inno.Build.IPlayerSupportPackProvisioner? supportPackProvisioner = null)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L68) | Creates a build pipeline from installed Player Support Packs and platform packagers.
`supportPackRoot`:The directory containing one child directory per .
`assets`:The active authoring asset pipeline captured by builds.
`generations`:The host-wide admission gate that remains pinned until the build snapshot is released.
`plugins`:The active Plugin environment captured by builds.
`settings`:The current project settings store captured by builds.
`serialization`:The serialization registry used to pin and encode the build snapshot.
`compiler`:The project compiler used to produce a fresh runtime-only assembly generation for each game build.
`gameTargets`:The complete set of replaceable platform package implementations available to this host.
`managedDeployments`:Explicit managed publishers selected independently of the platform packagers.
`supportPackProvisioner`:Optional build-time provider that checks current source and SDK inputs during explicit pack preparation.
T:System.ArgumentException:Thrown when the Support Pack root is empty, no target is provided, or a target identity is duplicated. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.BuildGameAsync(Inno.Build.GameBuildRequest request, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L253) | Builds and installs one complete source-free game deployment with rollback protection.
约束:Prepare current packs with EnsurePlayerSupportPackAsync before invoking this method on the authoring owner's thread. The synchronous snapshot phase never waits for asynchronous provisioning. Coordinate readers and writers of the output directory during its two installation moves. Post-commit backup, staging and progress observer failures are warnings on the successful result.
`request`:The exact profile, destination, and activated runtime compilation generation.
`progress`:Optional observer for monotonic stage progress.
`cancellationToken`:The token that cancels work before directory installation begins.
返回:The durable output identity and deployment metrics.
T:System.OperationCanceledException:Thrown when cancellation is requested before commit.
T:System.IO.DirectoryNotFoundException:The target Support Pack is missing; prepare it before starting the owner-thread build snapshot.
T:System.IO.InvalidDataException:The target Support Pack is present but invalid.
T:System.IO.IOException:Output installation fails before a complete candidate is committed.
T:System.AggregateException:Output installation and restoration both fail; the previous tree remains at the reported backup path. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.BuildPluginAsync(Inno.Build.PluginBuildRequest request, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L287) | Builds and atomically commits one deterministic project Plugin package.
`request`:The package identity, destination, and dependency embedding policy.
`progress`:Optional observer for monotonic stage progress.
`cancellationToken`:The token that cancels work before atomic commit.
返回:The durable package identity and source-content metrics.
T:System.OperationCanceledException:Thrown when cancellation is requested before commit. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.EnsurePlayerSupportPackAsync(Inno.Build.BuildTargetId target, Inno.Build.Managed.ManagedDeploymentId? deployment, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L203) | Validates the platform and managed deployment before asynchronously preparing its Support Pack.
`target`:The registered platform and architecture target to prepare.
`deployment`:The selected managed publisher, or null to use the platform's default deployment. Unsupported pairs fail before any provisioner or external tool starts.
`cancellationToken`:Cancellation before the Pack installation commits.
返回:Reading ownership of the verified Pack, to be disposed after starting its consuming build. No authoring asset database operations run during preparation.
T:System.NotSupportedException:No game target is registered for the requested identity.
T:System.InvalidOperationException:The managed publisher is absent or cannot publish the selected platform's runtime identifier.
T:System.IO.DirectoryNotFoundException:The Pack is absent and this host has no provisioner.
T:System.IO.InvalidDataException:An installed or newly published Pack fails deployment validation. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildPipeline.GetManagedDeployments(Inno.Build.BuildTargetId target)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L144) | Gets the managed deployment choices supported by a registered publication platform.
`target`:The registered publication target whose managed runtime requirements are queried.
返回:Immutable deployment identities in stable order; an unknown target throws explicitly.
T:System.ArgumentException:The publication target is not registered in this build composition. | -| [`public bool Inno.Build.BuildPipeline.TryGetGameTargetDisplayName(Inno.Build.BuildTargetId target, out string displayName)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L161) | Tries to get the adapter-owned display name for a registered game target.
`target`:The stable target identity to query.
`displayName`:Receives the registered user-facing name, or an empty value when no target is registered.
返回:when the target is registered; otherwise, . | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildPipeline.availableGameTargets { get; }`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L130) | Gets an immutable view of every registered build target in stable identity order. | -| [`public sealed class Inno.Build.BuildPipeline`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L21) | Orchestrates deterministic Plugin and game builds over isolated staging and atomic commits. | - -### `Inno.Build.BuildProfile` - @@ -169,40 +139,11 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildProfile.BuildProfile()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L15) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public Inno.Build.BuildProfile Inno.Build.BuildProfile.Copy()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L91) | Copies product and deployment settings before an asynchronous build retains the request.
返回:A newly owned profile with the same current-format scalar settings and stable selections. | -| [`public void Inno.Build.BuildProfile.Validate()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L109) | Validates product identity, startup content, target, and window dimensions.
T:System.IO.InvalidDataException:Thrown when the profile cannot produce a portable Player deployment. | -| [`public string Inno.Build.BuildProfile.applicationId { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L28) | Gets or sets the stable lowercase identity used for persistent application data. | -| [`public Inno.Build.Managed.ManagedDeploymentId? Inno.Build.BuildProfile.managedDeployment { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L61) | Gets or sets an explicit managed publisher; null uses the platform composition's default. | -| [`public string Inno.Build.BuildProfile.persistentDataPath { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L41) | Gets or sets the writable data folder relative to the operating system's local application data directory. An empty value uses . | -| [`public string Inno.Build.BuildProfile.productName { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L34) | Gets or sets the player-facing product and output name. | -| [`public string Inno.Build.BuildProfile.startupScene { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L47) | Gets or sets the mount-qualified startup scene path. | -| [`public Inno.Build.BuildTargetId Inno.Build.BuildProfile.target { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L52) | Gets or sets the platform target. | -| [`public int Inno.Build.BuildProfile.windowHeight { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L80) | Gets or sets the initial logical window height. | -| [`public int Inno.Build.BuildProfile.windowWidth { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L74) | Gets or sets the initial logical window width. | -| [`public sealed class Inno.Build.BuildProfile : Inno.Core.Serialization.ISerializable`](../../build/pipeline/Inno.Build/BuildProfile.cs#L15) | Defines reusable product and startup settings for deterministic game builds. | - -### `Inno.Build.BuildProfileStore` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildProfileStore.BuildProfileStore(string path, Inno.Core.Serialization.SerializationRegistry serialization)`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L36) | Creates a store for one current-format build profile document.
`path`:The project-owned path of the build profile document.
`serialization`:The serialization registry whose active generation owns the generated converter.
T:System.ArgumentException:Thrown when is empty.
T:System.ArgumentNullException:Thrown when is . | -| [`public Inno.Build.BuildProfile Inno.Build.BuildProfileStore.Load()`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L63) | Loads and validates the current build profile document.
返回:A newly owned build profile that callers may edit independently.
T:System.IO.FileNotFoundException:Thrown when the project does not contain a build profile document.
T:System.IO.InvalidDataException:Thrown when the document is corrupt or contains an invalid profile. | -| [`public void Inno.Build.BuildProfileStore.Save(Inno.Build.BuildProfile profile)`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L99) | Validates and atomically saves the supplied build profile.
`profile`:The complete current profile to persist.
T:System.ArgumentNullException:Thrown when is .
T:System.IO.InvalidDataException:Thrown when the profile cannot produce a supported Player deployment.
T:System.IO.IOException:Thrown when the candidate document cannot be written or atomically installed. | -| [`public bool Inno.Build.BuildProfileStore.exists { get; }`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L49) | Gets whether the current project contains a build profile document. | -| [`public sealed class Inno.Build.BuildProfileStore`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L16) | Persists one project's current game build profile through the engine serialization contract.
约束:The store writes a complete candidate beside the destination and replaces the active document only after serialization and validation succeed. | - -### `Inno.Build.BuildProgress` - @@ -214,23 +155,6 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildProgress.BuildProgress(string stage, double fraction, string message)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L28) | Creates a validated progress update.
`stage`:The stable build stage name.
`fraction`:The completed fraction in the inclusive range from zero to one.
`message`:A concise human-readable status message.
T:System.ArgumentException:Thrown when stage or message is empty.
T:System.ArgumentOutOfRangeException:Thrown when fraction is outside the supported range. | -| [`public bool Inno.Build.BuildProgress.Equals(Inno.Build.BuildProgress other)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.BuildProgress.Equals(object obj)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`:The object to compare with the current instance.
返回:if and this instance are the same type and represent the same value; otherwise, . | -| [`public override int Inno.Build.BuildProgress.GetHashCode()`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | -| [`public override string Inno.Build.BuildProgress.ToString()`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | -| [`public static bool Inno.Build.BuildProgress.operator ==(Inno.Build.BuildProgress left, Inno.Build.BuildProgress right)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.BuildProgress.operator !=(Inno.Build.BuildProgress left, Inno.Build.BuildProgress right)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public double Inno.Build.BuildProgress.fraction { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L52) | Gets the completed fraction in the inclusive range from zero to one. | -| [`public string Inno.Build.BuildProgress.message { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L57) | Gets a concise human-readable status message. | -| [`public string Inno.Build.BuildProgress.stage { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L47) | Gets the stable build stage name. | -| [`public readonly record struct Inno.Build.BuildProgress : System.IEquatable`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Reports monotonic progress for one asynchronous build operation. | - -### `Inno.Build.BuildResult` - @@ -241,22 +165,6 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public int Inno.Build.BuildResult.artifactBundleCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L61) | Gets the number of content-addressed artifact bundles represented by the output. | -| [`public int Inno.Build.BuildResult.assetCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L56) | Gets the number of deployed runtime assets or packaged source assets. | -| [`public string? Inno.Build.BuildResult.contentHash { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L51) | Gets the deterministic content identity, or when the build failed. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildResult.diagnostics { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L76) | Gets structured diagnostics emitted during the build. | -| [`public int Inno.Build.BuildResult.embeddedPluginCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L71) | Gets the number of embedded dependency Plugin packages. | -| [`public string? Inno.Build.BuildResult.outputPath { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L41) | Gets the atomically committed output path, or when the build failed. | -| [`public int Inno.Build.BuildResult.runtimeAssemblyCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L66) | Gets the number of deployed runtime assemblies. | -| [`public bool Inno.Build.BuildResult.succeeded { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L36) | Gets whether the build committed a complete output. | -| [`public Inno.Build.BuildTargetId? Inno.Build.BuildResult.target { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L46) | Gets the game target, or for a Plugin package. | -| [`public sealed class Inno.Build.BuildResult`](../../build/pipeline/Inno.Build/BuildResult.cs#L9) | Reports the immutable outcome, diagnostics, and optional durable output of one build. | - -### `Inno.Build.BuildSettings` - @@ -273,28 +181,6 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildSettings.BuildSettings()`](../../build/pipeline/Inno.Build/BuildSettings.cs#L14) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public Inno.Build.BuildSettings Inno.Build.BuildSettings.Copy()`](../../build/pipeline/Inno.Build/BuildSettings.cs#L165) | Creates an isolated copy suitable for a temporary export draft.
返回:A newly owned settings object. | -| [`public static Inno.Build.BuildSettings Inno.Build.BuildSettings.CreateDefault(string projectName, string startupScene, Inno.Build.BuildTargetId target)`](../../build/pipeline/Inno.Build/BuildSettings.cs#L113) | Creates canonical defaults for a new project.
`projectName`:The project directory name used for initial labels and output paths.
`startupScene`:The initial mount-qualified startup Scene, or an empty value when unavailable.
`target`:The host-appropriate initial build target.
返回:A newly owned build settings object.
T:System.ArgumentException:Thrown when is empty or is invalid. | -| [`public Inno.Build.BuildProfile Inno.Build.BuildSettings.CreateGameProfile(Inno.Core.Settings.ProjectId projectId)`](../../build/pipeline/Inno.Build/BuildSettings.cs#L146) | Creates an isolated game profile from the current defaults.
`projectId`:The authoritative project identity copied into the game profile.
返回:A newly owned profile that may be changed for one build without mutating these settings. | -| [`public Inno.Build.Managed.ManagedDeploymentId? Inno.Build.BuildSettings.gameManagedDeployment { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L86) | Gets or sets the default managed publisher; null follows the selected platform's default. | -| [`public string Inno.Build.BuildSettings.gameOutputDirectory { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L57) | Gets or sets the default game output directory, relative to the project root or absolute. | -| [`public string Inno.Build.BuildSettings.gamePersistentDataPath { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L45) | Gets or sets the default writable data folder below the operating system's local application data directory. An empty value uses the project ID. | -| [`public string Inno.Build.BuildSettings.gameProductName { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L38) | Gets or sets the default player-facing product name. | -| [`public string Inno.Build.BuildSettings.gameStartupScene { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L51) | Gets or sets the default mount-qualified startup Scene path. | -| [`public Inno.Build.BuildTargetId Inno.Build.BuildSettings.gameTarget { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L74) | Gets or sets the default game build target. | -| [`public int Inno.Build.BuildSettings.gameWindowHeight { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L69) | Gets or sets the default initial logical window height. | -| [`public int Inno.Build.BuildSettings.gameWindowWidth { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L63) | Gets or sets the default initial logical window width. | -| [`public bool Inno.Build.BuildSettings.includePluginDependencies { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L32) | Gets or sets whether Plugin dependency packages are embedded by default. | -| [`public string Inno.Build.BuildSettings.pluginDisplayName { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L20) | Gets or sets the default user-facing Plugin name. | -| [`public string Inno.Build.BuildSettings.pluginOutputPath { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L26) | Gets or sets the default Plugin package destination, relative to the project root or absolute. | -| [`public sealed class Inno.Build.BuildSettings : Inno.Core.Serialization.ISerializable`](../../build/pipeline/Inno.Build/BuildSettings.cs#L14) | Defines project-owned defaults copied into each temporary game or Plugin export request. | - -### `Inno.Build.BuildSettingsStore` - @@ -304,21 +190,6 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildSettingsStore.BuildSettingsStore(string path, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Build.BuildSettings defaultSettings)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L37) | Creates a store for one project's current build settings document.
`path`:The project-owned Settings.Build.inno path.
`serialization`:The active engine serialization registry.
`defaultSettings`:Canonical project defaults used while no document exists.
T:System.ArgumentException:Thrown when is empty.
T:System.ArgumentNullException:Thrown when or is null. | -| [`public byte[] Inno.Build.BuildSettingsStore.CaptureDocument()`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L122) | Captures the current effective build defaults as a native document.
返回:A newly owned current-format document payload. | -| [`public Inno.Build.BuildSettings Inno.Build.BuildSettingsStore.Load()`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L87) | Loads the saved settings or returns an isolated copy of the canonical defaults.
返回:A newly owned settings object.
T:System.IO.InvalidDataException:Thrown when the saved document is malformed. | -| [`public void Inno.Build.BuildSettingsStore.RestoreDocument(System.ReadOnlySpan document)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L140) | Validates and atomically restores a native build settings document.
`document`:The complete document payload.
T:System.IO.InvalidDataException:Thrown when the document is malformed.
T:System.IO.IOException:Thrown when the document cannot be committed. | -| [`public void Inno.Build.BuildSettingsStore.Save(Inno.Build.BuildSettings settings)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L108) | Atomically replaces the project-owned build defaults.
`settings`:The complete defaults to persist.
T:System.ArgumentNullException:Thrown when is null.
T:System.IO.InvalidDataException:Thrown when the settings document is malformed.
T:System.IO.IOException:Thrown when the candidate cannot be committed. | -| [`public void Inno.Build.BuildSettingsStore.ValidateDocument(System.ReadOnlySpan document)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L156) | Validates a native build settings document without changing the active file.
`document`:The document payload to inspect.
T:System.IO.InvalidDataException:Thrown when the document is malformed. | -| [`public Inno.Build.BuildSettings Inno.Build.BuildSettingsStore.defaultSettings { get; }`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L69) | Gets an isolated copy of the canonical project defaults. | -| [`public bool Inno.Build.BuildSettingsStore.exists { get; }`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L57) | Gets whether a project-owned build settings document exists. | -| [`public sealed class Inno.Build.BuildSettingsStore`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L13) | Persists project-owned export defaults independently from temporary export requests. | - -### `Inno.Build.BuildTargetId` - @@ -328,113 +199,34 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.BuildTargetId.BuildTargetId(string value)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L19) | Creates a stable target identity.
`value`:A lowercase portable identifier composed of letters, numbers, and hyphens.
T:System.ArgumentException:Thrown when is not a portable target identity. | -| [`public bool Inno.Build.BuildTargetId.Equals(Inno.Build.BuildTargetId other)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`:An object to compare with this object.
返回:if the current object is equal to the parameter; otherwise, . | -| [`public override bool Inno.Build.BuildTargetId.Equals(object obj)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`:The object to compare with the current instance.
返回:if and this instance are the same type and represent the same value; otherwise, . | -| [`public override int Inno.Build.BuildTargetId.GetHashCode()`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | -| [`public override string Inno.Build.BuildTargetId.ToString()`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L41) | Formats the portable target identity.
返回:The exact stable target identity. | -| [`public static bool Inno.Build.BuildTargetId.operator ==(Inno.Build.BuildTargetId left, Inno.Build.BuildTargetId right)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public static bool Inno.Build.BuildTargetId.operator !=(Inno.Build.BuildTargetId left, Inno.Build.BuildTargetId right)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public string Inno.Build.BuildTargetId.value { get; }`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L33) | Gets the portable target identity. | -| [`public readonly record struct Inno.Build.BuildTargetId : System.IEquatable`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Identifies one stable platform and architecture combination supported by the game build pipeline. | - -### `Inno.Build.GameBuildRequest` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.GameBuildRequest.GameBuildRequest()`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L9) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public void Inno.Build.GameBuildRequest.Validate()`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L27) | Validates destination and product inputs without mutating output.
T:System.IO.InvalidDataException:Thrown when the request is incomplete. | -| [`public required string Inno.Build.GameBuildRequest.outputDirectory { get; init; }`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L19) | Gets or initializes the parent directory that receives the atomically committed platform output. | -| [`public required Inno.Build.BuildProfile Inno.Build.GameBuildRequest.profile { get; init; }`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L14) | Gets or initializes the reusable product profile. | -| [`public sealed class Inno.Build.GameBuildRequest`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L9) | Requests one game build from the current validated authoring generation. | - -### `Inno.Build.GameBuildContentContext` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public string Inno.Build.GameBuildContentContext.outputDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L29) | Gets the empty staging directory that receives source-free target artifacts. | -| [`public Inno.Build.BuildProfile Inno.Build.GameBuildContentContext.profile { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L24) | Gets the validated product and target profile. | -| [`public Inno.Core.Serialization.SerializationGeneration Inno.Build.GameBuildContentContext.serialization { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L34) | Gets the immutable converter generation pinned by the surrounding build transaction. | -| [`public sealed class Inno.Build.GameBuildContentContext`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L9) | Supplies isolated staging and profile data for target-specific offline artifact generation. | - -### `Inno.Build.GameBuildPackageContext` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public string Inno.Build.GameBuildPackageContext.contentDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L37) | Gets the source-free packaged content directory to deploy. | -| [`public Inno.Build.Managed.ManagedDeploymentResult Inno.Build.GameBuildPackageContext.managedDeployment { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L42) | Gets the verified managed publication produced independently of platform packaging. | -| [`public string Inno.Build.GameBuildPackageContext.outputDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L47) | Gets the empty staging parent where the target must create exactly one output. | -| [`public Inno.Build.BuildProfile Inno.Build.GameBuildPackageContext.profile { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L27) | Gets the validated product profile. | -| [`public string Inno.Build.GameBuildPackageContext.supportPackDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L32) | Gets the verified read-only Support Pack directory for this target. | -| [`public sealed class Inno.Build.GameBuildPackageContext`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L8) | Supplies an isolated staging layout to a replaceable platform packager. | - -### `Inno.Build.GameBuildTargetBinding` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.GameBuildTargetBinding.GameBuildTargetBinding(Inno.Build.IGameBuildTarget packager, Inno.Build.IGameContentCompiler compiler)`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L25) | Validates a complete binding without starting tools or taking ownership of services.
`packager`:The borrowed platform packaging implementation.
`compiler`:The borrowed content implementation for the same target and authoring generation.
T:System.ArgumentNullException:Either implementation is null.
T:System.ArgumentException:A target is unassigned or the two implementation identities disagree. | -| [`public Inno.Build.IGameContentCompiler Inno.Build.GameBuildTargetBinding.compiler { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L45) | Gets the content implementation borrowing this pipeline's authoring generation. | -| [`public Inno.Build.IGameBuildTarget Inno.Build.GameBuildTargetBinding.packager { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L40) | Gets the platform implementation responsible for layout and Support Pack validation. | -| [`public sealed class Inno.Build.GameBuildTargetBinding`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L8) | Binds independent packaging and content implementations for one publication target. | - -### `Inno.Build.IGameBuildTarget` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.IGameBuildTarget.PackageAsync(Inno.Build.GameBuildPackageContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L47) | Composes one platform output from a verified Support Pack and source-free content directory.
`context`:The isolated package context owned by the current build.
`cancellationToken`:The token that cancels work before the build is committed.
返回:The single platform output path created beneath .
T:System.OperationCanceledException:Thrown when packaging is canceled. | -| [`Inno.Build.Managed.ManagedDeploymentId Inno.Build.IGameBuildTarget.defaultManagedDeployment { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L20) | Gets the managed publisher selected when the profile does not specify an override. | -| [`string Inno.Build.IGameBuildTarget.displayName { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L30) | Gets the user-facing target name presented by authoring hosts. | -| [`Inno.Build.BuildTargetId Inno.Build.IGameBuildTarget.id { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L15) | Gets the stable target identity implemented by this packager. | -| [`string Inno.Build.IGameBuildTarget.runtimeIdentifier { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L25) | Gets the managed toolchain target identifier required by this platform composition. | -| [`public interface Inno.Build.IGameBuildTarget : Inno.Build.IPlayerSupportPackValidator`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L10) | Defines the replaceable platform packaging boundary for one game build target. | - -### `Inno.Build.IGameContentCompiler` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.IGameContentCompiler.CompileAsync(Inno.Build.GameBuildContentContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L31) | Writes target content into the current operation's isolated staging directory.
`context`:The borrowed authoring generation and operation-owned content destination.
`cancellationToken`:Cancels compilation before publication; associated work must finish before returning.
返回:An operation completing after every required artifact is staged, or faulting on failure.
T:System.OperationCanceledException:Cancellation was requested before compilation completed. | -| [`Inno.Build.BuildTargetId Inno.Build.IGameContentCompiler.target { get; }`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L14) | Gets the exact publication target whose content this compiler produces. | -| [`public interface Inno.Build.IGameContentCompiler`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L9) | Compiles source-free runtime content independently of application packaging. | - -### `Inno.Build.PlatformTargetDescriptor` - @@ -443,68 +235,18 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.PlatformTargetDescriptor.PlatformTargetDescriptor(Inno.Build.BuildTargetId id, string platform, string architecture, string abi, string runtimeIdentifier, bool supportsEditor)`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L34) | Creates an explicit platform, processor, ABI and managed-runtime mapping.
`id`:The stable publication target identity.
`platform`:The platform package owning system integration.
`architecture`:The target processor architecture, such as x64, arm64 or wasm32.
`abi`:The native ABI selected by this platform implementation.
`runtimeIdentifier`:The exact managed toolchain runtime identifier.
`supportsEditor`:Whether this implemented target supports the shared Editor product.
T:System.ArgumentException:An identity or required target fact is unassigned. | -| [`public string Inno.Build.PlatformTargetDescriptor.abi { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L73) | Gets the explicitly selected native ABI. | -| [`public string Inno.Build.PlatformTargetDescriptor.architecture { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L68) | Gets the explicitly declared processor architecture. | -| [`public Inno.Build.BuildTargetId Inno.Build.PlatformTargetDescriptor.id { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L58) | Gets the stable publication target identity. | -| [`public string Inno.Build.PlatformTargetDescriptor.platform { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L63) | Gets the owning platform package. | -| [`public string Inno.Build.PlatformTargetDescriptor.runtimeIdentifier { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L78) | Gets the managed runtime mapping without deriving it from the target identity. | -| [`public bool Inno.Build.PlatformTargetDescriptor.supportsEditor { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L83) | Gets whether this target provides an implemented Editor host. | -| [`public sealed class Inno.Build.PlatformTargetDescriptor`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L8) | Declares immutable product target facts independently of the machine executing build tools. | - -### `Inno.Build.IPlayerSupportPackProvisioner` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`System.Threading.Tasks.ValueTask Inno.Build.IPlayerSupportPackProvisioner.ProvisionAsync(Inno.Build.BuildTargetId target, string supportPackRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackProvisioner.cs#L26) | Checks current source inputs and selects a complete immutable pack before the build resumes.
`target`:The platform and architecture target whose current inputs are required.
`supportPackRoot`:The catalog directory where the verified pack must be installed.
`cancellationToken`:Cancellation before the installation commits.
返回:An operation that completes after the pack has been installed. | -| [`public interface Inno.Build.IPlayerSupportPackProvisioner`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackProvisioner.cs#L9) | Prepares current Player Support Pack inputs without coupling game builds to an SDK or platform toolchain. | - -### `Inno.Build.IPlayerSupportPackValidator` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`void Inno.Build.IPlayerSupportPackValidator.Validate(string directory)`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackValidator.cs#L17) | Rejects an incomplete or incompatible platform closure.
`directory`:The prepared or installed Support Pack directory.
T:System.IO.InvalidDataException:A required platform input is absent or incompatible. | -| [`public interface Inno.Build.IPlayerSupportPackValidator`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackValidator.cs#L6) | Validates platform-owned runtime or link inputs without coupling the pack catalog to target names. | - -### `Inno.Build.PlayerSupportPackCatalog` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.PlayerSupportPackCatalog.PlayerSupportPackCatalog(string root)`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L49) | Creates a catalog rooted at the directory containing target-specific Player Support Packs.
`root`:The directory whose target children contain immutable fingerprint directories and a current index.
T:System.ArgumentException:Thrown when is empty. | -| [`public Inno.Build.PlayerSupportPackLease Inno.Build.PlayerSupportPackCatalog.Acquire(Inno.Build.BuildTargetId target, Inno.Build.IPlayerSupportPackValidator validator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L144) | Resolves one installed Support Pack after validating its deployment-only closure.
`target`:The platform and architecture identity of the required Support Pack.
返回:Reading ownership of the complete pack; dispose it after its final consumer finishes.
T:System.IO.DirectoryNotFoundException:Thrown when no Support Pack is installed for .
T:System.IO.InvalidDataException:Thrown when the pack is empty, contains build-time payload, or lacks its Player executable or native runtimes.
`validator`:The target-owned validator for runtime or linker inputs.
`cancellationToken`:Cancels ownership acquisition or complete content validation before returning a lease.
T:System.OperationCanceledException:Acquisition or validation was canceled; no reading ownership is transferred.
T:System.TimeoutException:Exclusive repair or retirement retains the artifact for more than thirty seconds. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.PlayerSupportPackCatalog.PublishAsync(Inno.Build.BuildTargetId target, string stagingDirectory, Inno.Build.IPlayerSupportPackValidator validator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), System.Func? verifyInputs = null)`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L198) | Validates and publishes an immutable pack, then atomically selects it for new readers.
`target`:The platform and architecture owning the staged inputs.
`stagingDirectory`:A complete, exclusively owned staging tree on the same filesystem as this catalog. Successful publication consumes this directory unless identical inputs already exist.
`validator`:The target validator for runtime and linker inputs.
`cancellationToken`:Cancels validation or lease acquisition before selecting the completed pack.
`verifyInputs`:Optional final source-input gate after output validation and any publication wait. Omit only when the caller exclusively owns a fully prepared tree with no external preparation inputs.
返回:Reading ownership of the selected immutable pack; earlier readers retain their own protection.
T:System.IO.InvalidDataException:The candidate is incomplete or contains forbidden inputs.
T:System.IO.IOException:The completed directory or its atomic current index cannot be installed.
T:System.OperationCanceledException:Cancellation occurred before the current index was committed. | -| [`public System.Threading.Tasks.ValueTask Inno.Build.PlayerSupportPackCatalog.SelectAsync(Inno.Build.BuildTargetId target, string fingerprint, Inno.Build.IPlayerSupportPackValidator validator, System.Func verifyInputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L85) | Validates one immutable content identity and atomically selects it for subsequent readers.
`target`:The explicit target whose prepared Pack is requested.
`fingerprint`:The exact lowercase SHA-256 content identity recorded by the publisher.
`validator`:The borrowed platform closure validator.
`verifyInputs`:The final awaited source-input gate after output validation and before selection.
`cancellationToken`:Cancels validation or selection before replacing the current index.
返回:Reading ownership of the complete requested Pack; dispose it after every consumer finishes.
T:System.ArgumentException:The content identity is invalid or the target path escapes the catalog.
T:System.IO.InvalidDataException:The requested directory or its complete file set and hashes are invalid.
T:System.OperationCanceledException:Validation or selection was canceled. | -| [`public sealed class Inno.Build.PlayerSupportPackCatalog`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L17) | Resolves and validates installed Player Support Packs without loading authoring services. | - -### `Inno.Build.PlayerSupportPackLease` - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public void Inno.Build.PlayerSupportPackLease.Dispose()`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L42) | Releases reading rights after all compilation, copying and linking have completed. Repeated disposal has no effect. | -| [`public string Inno.Build.PlayerSupportPackLease.directory { get; }`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L29) | Gets the verified absolute directory while this lease owns reading rights.
T:System.ObjectDisposedException:The consumer has already released its reading rights. | -| [`public sealed class Inno.Build.PlayerSupportPackLease : System.IDisposable`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L10) | Keeps verified Player compilation and linker inputs alive until their consumer finishes. | - -### `Inno.Build.PluginBuildRequest` - @@ -513,46 +255,180 @@ ContentPackWriter 与 ContentPackReader 共用 ContentKey/Index 协议。冻结 + + + -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public Inno.Build.PluginBuildRequest.PluginBuildRequest()`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L11) | 由 C# 生成,遵循所属类型的值语义与本页的生命周期约束。 | -| [`public void Inno.Build.PluginBuildRequest.Validate()`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L45) | Validates package identity and destination syntax.
T:System.IO.InvalidDataException:Thrown when the request cannot identify a portable Plugin package. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Build.PluginBuildRequest.dependencies { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L37) | Gets or initializes the installed plugins this package actually requires. Their transitive dependencies are included in the package manifest. | -| [`public required string Inno.Build.PluginBuildRequest.displayName { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L21) | Gets or initializes the user-facing Plugin name. | -| [`public bool Inno.Build.PluginBuildRequest.includeDependencies { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L31) | Gets or initializes whether the complete transitive dependency closure is embedded. | -| [`public required string Inno.Build.PluginBuildRequest.outputPath { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L26) | Gets or initializes the destination .iplugin package path. | -| [`public required string Inno.Build.PluginBuildRequest.pluginId { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L16) | Gets or initializes the globally stable lowercase Plugin identity. | -| [`public sealed class Inno.Build.PluginBuildRequest`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L11) | Requests one deterministic project-to-Plugin package without a companion authoring asset. | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public enum Inno.Build.BuildDiagnosticSeverity : int`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L8) | Identifies the severity of one structured build diagnostic. | — | — | +| [`Inno.Build.BuildDiagnosticSeverity.Information = 0`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L13) | Provides non-failing build context. | — | — | +| [`Inno.Build.BuildDiagnosticSeverity.Warning = 1`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L18) | Reports a condition that did not prevent output creation. | — | — | +| [`Inno.Build.BuildDiagnosticSeverity.Error = 2`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L23) | Reports a condition that prevented a valid build. | — | — | +| [`public sealed class Inno.Build.BuildDiagnostic`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L29) | Describes one stable machine-readable build problem or informational event. | — | — | +| [`public Inno.Build.BuildDiagnostic.BuildDiagnostic(Inno.Build.BuildDiagnosticSeverity severity, string code, string message)`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L46) | Creates an immutable build diagnostic.
`severity`: The impact of the diagnostic on build success.
`code`: The stable diagnostic code.
`message`: The human-readable explanation. | — | `System.ArgumentException`: Thrown when code or message is empty. | +| [`public Inno.Build.BuildDiagnosticSeverity Inno.Build.BuildDiagnostic.severity { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L61) | Gets the impact of the diagnostic on build success. | — | — | +| [`public string Inno.Build.BuildDiagnostic.code { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L66) | Gets the stable diagnostic code. | — | — | +| [`public string Inno.Build.BuildDiagnostic.message { get; }`](../../build/pipeline/Inno.Build/BuildDiagnostic.cs#L71) | Gets the human-readable explanation. | — | — | +| [`public sealed class Inno.Build.BuildPipeline`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L21) | Orchestrates deterministic Plugin and game builds over isolated staging and atomic commits. | — | — | +| [`public Inno.Build.BuildPipeline.BuildPipeline(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Plugins.Authoring.PluginEnvironment plugins, Inno.Core.Settings.ProjectSettingsStore settings, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Reload.GenerationCoordinator generations, Inno.Scripting.Compiler.ScriptCompiler compiler, string supportPackRoot, System.Collections.Generic.IEnumerable gameTargets, Inno.Build.Managed.ManagedDeploymentCatalog managedDeployments, Inno.Build.IPlayerSupportPackProvisioner? supportPackProvisioner = null)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L68) | Creates a build pipeline from installed Player Support Packs and platform packagers.
`supportPackRoot`: The directory containing one child directory per .
`assets`: The active authoring asset pipeline captured by builds.
`generations`: The host-wide admission gate that remains pinned until the build snapshot is released.
`plugins`: The active Plugin environment captured by builds.
`settings`: The current project settings store captured by builds.
`serialization`: The serialization registry used to pin and encode the build snapshot.
`compiler`: The project compiler used to produce a fresh runtime-only assembly generation for each game build.
`gameTargets`: The complete set of replaceable platform package implementations available to this host.
`managedDeployments`: Explicit managed publishers selected independently of the platform packagers.
`supportPackProvisioner`: Optional build-time provider that checks current source and SDK inputs during explicit pack preparation. | — | `System.ArgumentException`: Thrown when the Support Pack root is empty, no target is provided, or a target identity is duplicated. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildPipeline.availableGameTargets { get; }`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L130) | Gets an immutable view of every registered build target in stable identity order. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildPipeline.GetManagedDeployments(Inno.Build.BuildTargetId target)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L144) | Gets the managed deployment choices supported by a registered publication platform.
`target`: The registered publication target whose managed runtime requirements are queried. | Immutable deployment identities in stable order; an unknown target throws explicitly. | `System.ArgumentException`: The publication target is not registered in this build composition. | +| [`public bool Inno.Build.BuildPipeline.TryGetGameTargetDisplayName(Inno.Build.BuildTargetId target, out string displayName)`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L161) | Tries to get the adapter-owned display name for a registered game target.
`target`: The stable target identity to query.
`displayName`: Receives the registered user-facing name, or an empty value when no target is registered. | when the target is registered; otherwise, . | — | +| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.EnsurePlayerSupportPackAsync(Inno.Build.BuildTargetId target, Inno.Build.Managed.ManagedDeploymentId? deployment, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L203) | Validates the platform and managed deployment before asynchronously preparing its Support Pack.
`target`: The registered platform and architecture target to prepare.
`deployment`: The selected managed publisher, or null to use the platform's default deployment. Unsupported pairs fail before any provisioner or external tool starts.
`cancellationToken`: Cancellation before the Pack installation commits. | Reading ownership of the verified Pack, to be disposed after starting its consuming build. No authoring asset database operations run during preparation. | `System.NotSupportedException`: No game target is registered for the requested identity.
`System.InvalidOperationException`: The managed publisher is absent or cannot publish the selected platform's runtime identifier.
`System.IO.DirectoryNotFoundException`: The Pack is absent and this host has no provisioner.
`System.IO.InvalidDataException`: An installed or newly published Pack fails deployment validation. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.BuildGameAsync(Inno.Build.GameBuildRequest request, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L253) | Builds and installs one complete source-free game deployment with rollback protection.
`request`: The exact profile, destination, and activated runtime compilation generation.
`progress`: Optional observer for monotonic stage progress.
`cancellationToken`: The token that cancels work before directory installation begins. | The durable output identity and deployment metrics. | `System.OperationCanceledException`: Thrown when cancellation is requested before commit.
`System.IO.DirectoryNotFoundException`: The target Support Pack is missing; prepare it before starting the owner-thread build snapshot.
`System.IO.InvalidDataException`: The target Support Pack is present but invalid.
`System.IO.IOException`: Output installation fails before a complete candidate is committed.
`System.AggregateException`: Output installation and restoration both fail; the previous tree remains at the reported backup path. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.BuildPipeline.BuildPluginAsync(Inno.Build.PluginBuildRequest request, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/BuildPipeline.cs#L287) | Builds and atomically commits one deterministic project Plugin package.
`request`: The package identity, destination, and dependency embedding policy.
`progress`: Optional observer for monotonic stage progress.
`cancellationToken`: The token that cancels work before atomic commit. | The durable package identity and source-content metrics. | `System.OperationCanceledException`: Thrown when cancellation is requested before commit. | +| [`public sealed class Inno.Build.BuildProfile : Inno.Core.Serialization.ISerializable`](../../build/pipeline/Inno.Build/BuildProfile.cs#L15) | Defines reusable product and startup settings for deterministic game builds. | — | — | +| [`public string Inno.Build.BuildProfile.applicationId { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L28) | Gets or sets the stable lowercase identity used for persistent application data. | — | — | +| [`public string Inno.Build.BuildProfile.productName { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L34) | Gets or sets the player-facing product and output name. | — | — | +| [`public string Inno.Build.BuildProfile.persistentDataPath { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L41) | Gets or sets the writable data folder relative to the operating system's local application data directory. An empty value uses . | — | — | +| [`public string Inno.Build.BuildProfile.startupScene { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L47) | Gets or sets the mount-qualified startup scene path. | — | — | +| [`public Inno.Build.BuildTargetId Inno.Build.BuildProfile.target { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L52) | Gets or sets the platform target. | — | — | +| [`public Inno.Build.Managed.ManagedDeploymentId? Inno.Build.BuildProfile.managedDeployment { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L61) | Gets or sets an explicit managed publisher; null uses the platform composition's default. | — | — | +| [`public int Inno.Build.BuildProfile.windowWidth { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L74) | Gets or sets the initial logical window width. | — | — | +| [`public int Inno.Build.BuildProfile.windowHeight { get; set; }`](../../build/pipeline/Inno.Build/BuildProfile.cs#L80) | Gets or sets the initial logical window height. | — | — | +| [`public Inno.Build.BuildProfile Inno.Build.BuildProfile.Copy()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L91) | Copies product and deployment settings before an asynchronous build retains the request. | A newly owned profile with the same current-format scalar settings and stable selections. | — | +| [`public void Inno.Build.BuildProfile.Validate()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L109) | Validates product identity, startup content, target, and window dimensions. | — | `System.IO.InvalidDataException`: Thrown when the profile cannot produce a portable Player deployment. | +| [`public Inno.Build.BuildProfile.BuildProfile()`](../../build/pipeline/Inno.Build/BuildProfile.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.BuildProfileStore`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L16) | Persists one project's current game build profile through the engine serialization contract. | — | — | +| [`public Inno.Build.BuildProfileStore.BuildProfileStore(string path, Inno.Core.Serialization.SerializationRegistry serialization)`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L36) | Creates a store for one current-format build profile document.
`path`: The project-owned path of the build profile document.
`serialization`: The serialization registry whose active generation owns the generated converter. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Build.BuildProfileStore.exists { get; }`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L49) | Gets whether the current project contains a build profile document. | — | — | +| [`public Inno.Build.BuildProfile Inno.Build.BuildProfileStore.Load()`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L63) | Loads and validates the current build profile document. | A newly owned build profile that callers may edit independently. | `System.IO.FileNotFoundException`: Thrown when the project does not contain a build profile document.
`System.IO.InvalidDataException`: Thrown when the document is corrupt or contains an invalid profile. | +| [`public void Inno.Build.BuildProfileStore.Save(Inno.Build.BuildProfile profile)`](../../build/pipeline/Inno.Build/BuildProfileStore.cs#L99) | Validates and atomically saves the supplied build profile.
`profile`: The complete current profile to persist. | — | `System.ArgumentNullException`: Thrown when is .
`System.IO.InvalidDataException`: Thrown when the profile cannot produce a supported Player deployment.
`System.IO.IOException`: Thrown when the candidate document cannot be written or atomically installed. | +| [`public readonly record struct Inno.Build.BuildProgress : System.IEquatable`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Reports monotonic progress for one asynchronous build operation. | — | — | +| [`public Inno.Build.BuildProgress.BuildProgress(string stage, double fraction, string message)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L28) | Creates a validated progress update.
`stage`: The stable build stage name.
`fraction`: The completed fraction in the inclusive range from zero to one.
`message`: A concise human-readable status message. | — | `System.ArgumentException`: Thrown when stage or message is empty.
`System.ArgumentOutOfRangeException`: Thrown when fraction is outside the supported range. | +| [`public string Inno.Build.BuildProgress.stage { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L47) | Gets the stable build stage name. | — | — | +| [`public double Inno.Build.BuildProgress.fraction { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L52) | Gets the completed fraction in the inclusive range from zero to one. | — | — | +| [`public string Inno.Build.BuildProgress.message { get; }`](../../build/pipeline/Inno.Build/BuildProgress.cs#L57) | Gets a concise human-readable status message. | — | — | +| [`public override string Inno.Build.BuildProgress.ToString()`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Build.BuildProgress.operator !=(Inno.Build.BuildProgress left, Inno.Build.BuildProgress right)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.BuildProgress.operator ==(Inno.Build.BuildProgress left, Inno.Build.BuildProgress right)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.BuildProgress.GetHashCode()`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Build.BuildProgress.Equals(object obj)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Build.BuildProgress.Equals(Inno.Build.BuildProgress other)`](../../build/pipeline/Inno.Build/BuildProgress.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Build.BuildResult`](../../build/pipeline/Inno.Build/BuildResult.cs#L9) | Reports the immutable outcome, diagnostics, and optional durable output of one build. | — | — | +| [`public bool Inno.Build.BuildResult.succeeded { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L36) | Gets whether the build committed a complete output. | — | — | +| [`public string? Inno.Build.BuildResult.outputPath { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L41) | Gets the atomically committed output path, or when the build failed. | — | — | +| [`public Inno.Build.BuildTargetId? Inno.Build.BuildResult.target { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L46) | Gets the game target, or for a Plugin package. | — | — | +| [`public string? Inno.Build.BuildResult.contentHash { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L51) | Gets the deterministic content identity, or when the build failed. | — | — | +| [`public int Inno.Build.BuildResult.assetCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L56) | Gets the number of deployed runtime assets or packaged source assets. | — | — | +| [`public int Inno.Build.BuildResult.artifactBundleCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L61) | Gets the number of content-addressed artifact bundles represented by the output. | — | — | +| [`public int Inno.Build.BuildResult.runtimeAssemblyCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L66) | Gets the number of deployed runtime assemblies. | — | — | +| [`public int Inno.Build.BuildResult.embeddedPluginCount { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L71) | Gets the number of embedded dependency Plugin packages. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.BuildResult.diagnostics { get; }`](../../build/pipeline/Inno.Build/BuildResult.cs#L76) | Gets structured diagnostics emitted during the build. | — | — | +| [`public sealed class Inno.Build.BuildSettings : Inno.Core.Serialization.ISerializable`](../../build/pipeline/Inno.Build/BuildSettings.cs#L14) | Defines project-owned defaults copied into each temporary game or Plugin export request. | — | — | +| [`public string Inno.Build.BuildSettings.pluginDisplayName { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L20) | Gets or sets the default user-facing Plugin name. | — | — | +| [`public string Inno.Build.BuildSettings.pluginOutputPath { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L26) | Gets or sets the default Plugin package destination, relative to the project root or absolute. | — | — | +| [`public bool Inno.Build.BuildSettings.includePluginDependencies { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L32) | Gets or sets whether Plugin dependency packages are embedded by default. | — | — | +| [`public string Inno.Build.BuildSettings.gameProductName { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L38) | Gets or sets the default player-facing product name. | — | — | +| [`public string Inno.Build.BuildSettings.gamePersistentDataPath { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L45) | Gets or sets the default writable data folder below the operating system's local application data directory. An empty value uses the project ID. | — | — | +| [`public string Inno.Build.BuildSettings.gameStartupScene { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L51) | Gets or sets the default mount-qualified startup Scene path. | — | — | +| [`public string Inno.Build.BuildSettings.gameOutputDirectory { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L57) | Gets or sets the default game output directory, relative to the project root or absolute. | — | — | +| [`public int Inno.Build.BuildSettings.gameWindowWidth { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L63) | Gets or sets the default initial logical window width. | — | — | +| [`public int Inno.Build.BuildSettings.gameWindowHeight { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L69) | Gets or sets the default initial logical window height. | — | — | +| [`public Inno.Build.BuildTargetId Inno.Build.BuildSettings.gameTarget { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L74) | Gets or sets the default game build target. | — | — | +| [`public Inno.Build.Managed.ManagedDeploymentId? Inno.Build.BuildSettings.gameManagedDeployment { get; set; }`](../../build/pipeline/Inno.Build/BuildSettings.cs#L86) | Gets or sets the default managed publisher; null follows the selected platform's default. | — | — | +| [`public static Inno.Build.BuildSettings Inno.Build.BuildSettings.CreateDefault(string projectName, string startupScene, Inno.Build.BuildTargetId target)`](../../build/pipeline/Inno.Build/BuildSettings.cs#L113) | Creates canonical defaults for a new project.
`projectName`: The project directory name used for initial labels and output paths.
`startupScene`: The initial mount-qualified startup Scene, or an empty value when unavailable.
`target`: The host-appropriate initial build target. | A newly owned build settings object. | `System.ArgumentException`: Thrown when is empty or is invalid. | +| [`public Inno.Build.BuildProfile Inno.Build.BuildSettings.CreateGameProfile(Inno.Core.Settings.ProjectId projectId)`](../../build/pipeline/Inno.Build/BuildSettings.cs#L146) | Creates an isolated game profile from the current defaults.
`projectId`: The authoritative project identity copied into the game profile. | A newly owned profile that may be changed for one build without mutating these settings. | — | +| [`public Inno.Build.BuildSettings Inno.Build.BuildSettings.Copy()`](../../build/pipeline/Inno.Build/BuildSettings.cs#L165) | Creates an isolated copy suitable for a temporary export draft. | A newly owned settings object. | — | +| [`public Inno.Build.BuildSettings.BuildSettings()`](../../build/pipeline/Inno.Build/BuildSettings.cs#L14) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.BuildSettingsStore`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L13) | Persists project-owned export defaults independently from temporary export requests. | — | — | +| [`public Inno.Build.BuildSettingsStore.BuildSettingsStore(string path, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Build.BuildSettings defaultSettings)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L37) | Creates a store for one project's current build settings document.
`path`: The project-owned Settings.Build.inno path.
`serialization`: The active engine serialization registry.
`defaultSettings`: Canonical project defaults used while no document exists. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when or is null. | +| [`public bool Inno.Build.BuildSettingsStore.exists { get; }`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L57) | Gets whether a project-owned build settings document exists. | — | — | +| [`public Inno.Build.BuildSettings Inno.Build.BuildSettingsStore.defaultSettings { get; }`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L69) | Gets an isolated copy of the canonical project defaults. | — | — | +| [`public Inno.Build.BuildSettings Inno.Build.BuildSettingsStore.Load()`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L87) | Loads the saved settings or returns an isolated copy of the canonical defaults. | A newly owned settings object. | `System.IO.InvalidDataException`: Thrown when the saved document is malformed. | +| [`public void Inno.Build.BuildSettingsStore.Save(Inno.Build.BuildSettings settings)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L108) | Atomically replaces the project-owned build defaults.
`settings`: The complete defaults to persist. | — | `System.ArgumentNullException`: Thrown when is null.
`System.IO.InvalidDataException`: Thrown when the settings document is malformed.
`System.IO.IOException`: Thrown when the candidate cannot be committed. | +| [`public byte[] Inno.Build.BuildSettingsStore.CaptureDocument()`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L122) | Captures the current effective build defaults as a native document. | A newly owned current-format document payload. | — | +| [`public void Inno.Build.BuildSettingsStore.RestoreDocument(System.ReadOnlySpan document)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L140) | Validates and atomically restores a native build settings document.
`document`: The complete document payload. | — | `System.IO.InvalidDataException`: Thrown when the document is malformed.
`System.IO.IOException`: Thrown when the document cannot be committed. | +| [`public void Inno.Build.BuildSettingsStore.ValidateDocument(System.ReadOnlySpan document)`](../../build/pipeline/Inno.Build/BuildSettingsStore.cs#L156) | Validates a native build settings document without changing the active file.
`document`: The document payload to inspect. | — | `System.IO.InvalidDataException`: Thrown when the document is malformed. | +| [`public readonly record struct Inno.Build.BuildTargetId : System.IEquatable`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Identifies one stable platform and architecture combination supported by the game build pipeline. | — | — | +| [`public Inno.Build.BuildTargetId.BuildTargetId(string value)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L19) | Creates a stable target identity.
`value`: A lowercase portable identifier composed of letters, numbers, and hyphens. | — | `System.ArgumentException`: Thrown when is not a portable target identity. | +| [`public string Inno.Build.BuildTargetId.value { get; }`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L33) | Gets the portable target identity. | — | — | +| [`public override string Inno.Build.BuildTargetId.ToString()`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L41) | Formats the portable target identity. | The exact stable target identity. | — | +| [`public static bool Inno.Build.BuildTargetId.operator !=(Inno.Build.BuildTargetId left, Inno.Build.BuildTargetId right)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Build.BuildTargetId.operator ==(Inno.Build.BuildTargetId left, Inno.Build.BuildTargetId right)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Build.BuildTargetId.GetHashCode()`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Build.BuildTargetId.Equals(object obj)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Build.BuildTargetId.Equals(Inno.Build.BuildTargetId other)`](../../build/pipeline/Inno.Build/BuildTargetId.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Build.GameBuildRequest`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L9) | Requests one game build from the current validated authoring generation. | — | — | +| [`public required Inno.Build.BuildProfile Inno.Build.GameBuildRequest.profile { get; init; }`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L14) | Gets or initializes the reusable product profile. | — | — | +| [`public required string Inno.Build.GameBuildRequest.outputDirectory { get; init; }`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L19) | Gets or initializes the parent directory that receives the atomically committed platform output. | — | — | +| [`public void Inno.Build.GameBuildRequest.Validate()`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L27) | Validates destination and product inputs without mutating output. | — | `System.IO.InvalidDataException`: Thrown when the request is incomplete. | +| [`public Inno.Build.GameBuildRequest.GameBuildRequest()`](../../build/pipeline/Inno.Build/GameBuildRequest.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.GameBuildContentContext`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L9) | Supplies isolated staging and profile data for target-specific offline artifact generation. | — | — | +| [`public Inno.Build.BuildProfile Inno.Build.GameBuildContentContext.profile { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L24) | Gets the validated product and target profile. | — | — | +| [`public string Inno.Build.GameBuildContentContext.outputDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L29) | Gets the empty staging directory that receives source-free target artifacts. | — | — | +| [`public Inno.Core.Serialization.SerializationGeneration Inno.Build.GameBuildContentContext.serialization { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildContentContext.cs#L34) | Gets the immutable converter generation pinned by the surrounding build transaction. | — | — | +| [`public sealed class Inno.Build.GameBuildPackageContext`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L8) | Supplies an isolated staging layout to a replaceable platform packager. | — | — | +| [`public Inno.Build.BuildProfile Inno.Build.GameBuildPackageContext.profile { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L27) | Gets the validated product profile. | — | — | +| [`public string Inno.Build.GameBuildPackageContext.supportPackDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L32) | Gets the verified read-only Support Pack directory for this target. | — | — | +| [`public string Inno.Build.GameBuildPackageContext.contentDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L37) | Gets the source-free packaged content directory to deploy. | — | — | +| [`public Inno.Build.Managed.ManagedDeploymentResult Inno.Build.GameBuildPackageContext.managedDeployment { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L42) | Gets the verified managed publication produced independently of platform packaging. | — | — | +| [`public string Inno.Build.GameBuildPackageContext.outputDirectory { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildPackageContext.cs#L47) | Gets the empty staging parent where the target must create exactly one output. | — | — | +| [`public sealed class Inno.Build.GameBuildTargetBinding`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L8) | Binds independent packaging and content implementations for one publication target. | — | — | +| [`public Inno.Build.GameBuildTargetBinding.GameBuildTargetBinding(Inno.Build.IGameBuildTarget packager, Inno.Build.IGameContentCompiler compiler)`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L25) | Validates a complete binding without starting tools or taking ownership of services.
`packager`: The borrowed platform packaging implementation.
`compiler`: The borrowed content implementation for the same target and authoring generation. | — | `System.ArgumentNullException`: Either implementation is null.
`System.ArgumentException`: A target is unassigned or the two implementation identities disagree. | +| [`public Inno.Build.IGameBuildTarget Inno.Build.GameBuildTargetBinding.packager { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L40) | Gets the platform implementation responsible for layout and Support Pack validation. | — | — | +| [`public Inno.Build.IGameContentCompiler Inno.Build.GameBuildTargetBinding.compiler { get; }`](../../build/pipeline/Inno.Build/Game/GameBuildTargetBinding.cs#L45) | Gets the content implementation borrowing this pipeline's authoring generation. | — | — | +| [`public interface Inno.Build.IGameBuildTarget : Inno.Build.IPlayerSupportPackValidator`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L10) | Defines the replaceable platform packaging boundary for one game build target. | — | — | +| [`Inno.Build.BuildTargetId Inno.Build.IGameBuildTarget.id { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L15) | Gets the stable target identity implemented by this packager. | — | — | +| [`Inno.Build.Managed.ManagedDeploymentId Inno.Build.IGameBuildTarget.defaultManagedDeployment { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L20) | Gets the managed publisher selected when the profile does not specify an override. | — | — | +| [`string Inno.Build.IGameBuildTarget.runtimeIdentifier { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L25) | Gets the managed toolchain target identifier required by this platform composition. | — | — | +| [`string Inno.Build.IGameBuildTarget.displayName { get; }`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L30) | Gets the user-facing target name presented by authoring hosts. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.IGameBuildTarget.PackageAsync(Inno.Build.GameBuildPackageContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Game/IGameBuildTarget.cs#L47) | Composes one platform output from a verified Support Pack and source-free content directory.
`context`: The isolated package context owned by the current build.
`cancellationToken`: The token that cancels work before the build is committed. | The single platform output path created beneath . | `System.OperationCanceledException`: Thrown when packaging is canceled. | +| [`public interface Inno.Build.IGameContentCompiler`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L9) | Compiles source-free runtime content independently of application packaging. | — | — | +| [`Inno.Build.BuildTargetId Inno.Build.IGameContentCompiler.target { get; }`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L14) | Gets the exact publication target whose content this compiler produces. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.IGameContentCompiler.CompileAsync(Inno.Build.GameBuildContentContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Game/IGameContentCompiler.cs#L31) | Writes target content into the current operation's isolated staging directory.
`context`: The borrowed authoring generation and operation-owned content destination.
`cancellationToken`: Cancels compilation before publication; associated work must finish before returning. | An operation completing after every required artifact is staged, or faulting on failure. | `System.OperationCanceledException`: Cancellation was requested before compilation completed. | +| [`public sealed class Inno.Build.PlatformTargetDescriptor`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L8) | Declares immutable product target facts independently of the machine executing build tools. | — | — | +| [`public Inno.Build.PlatformTargetDescriptor.PlatformTargetDescriptor(Inno.Build.BuildTargetId id, string platform, string architecture, string abi, string runtimeIdentifier, bool supportsEditor)`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L34) | Creates an explicit platform, processor, ABI and managed-runtime mapping.
`id`: The stable publication target identity.
`platform`: The platform package owning system integration.
`architecture`: The target processor architecture, such as x64, arm64 or wasm32.
`abi`: The native ABI selected by this platform implementation.
`runtimeIdentifier`: The exact managed toolchain runtime identifier.
`supportsEditor`: Whether this implemented target supports the shared Editor product. | — | `System.ArgumentException`: An identity or required target fact is unassigned. | +| [`public Inno.Build.BuildTargetId Inno.Build.PlatformTargetDescriptor.id { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L58) | Gets the stable publication target identity. | — | — | +| [`public string Inno.Build.PlatformTargetDescriptor.platform { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L63) | Gets the owning platform package. | — | — | +| [`public string Inno.Build.PlatformTargetDescriptor.architecture { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L68) | Gets the explicitly declared processor architecture. | — | — | +| [`public string Inno.Build.PlatformTargetDescriptor.abi { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L73) | Gets the explicitly selected native ABI. | — | — | +| [`public string Inno.Build.PlatformTargetDescriptor.runtimeIdentifier { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L78) | Gets the managed runtime mapping without deriving it from the target identity. | — | — | +| [`public bool Inno.Build.PlatformTargetDescriptor.supportsEditor { get; }`](../../build/pipeline/Inno.Build/PlatformTargetDescriptor.cs#L83) | Gets whether this target provides an implemented Editor host. | — | — | +| [`public interface Inno.Build.IPlayerSupportPackProvisioner`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackProvisioner.cs#L9) | Prepares current Player Support Pack inputs without coupling game builds to an SDK or platform toolchain. | — | — | +| [`System.Threading.Tasks.ValueTask Inno.Build.IPlayerSupportPackProvisioner.ProvisionAsync(Inno.Build.BuildTargetId target, string supportPackRoot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackProvisioner.cs#L26) | Checks current source inputs and selects a complete immutable pack before the build resumes.
`target`: The platform and architecture target whose current inputs are required.
`supportPackRoot`: The catalog directory where the verified pack must be installed.
`cancellationToken`: Cancellation before the installation commits. | An operation that completes after the pack has been installed. | — | +| [`public interface Inno.Build.IPlayerSupportPackValidator`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackValidator.cs#L6) | Validates platform-owned runtime or link inputs without coupling the pack catalog to target names. | — | — | +| [`void Inno.Build.IPlayerSupportPackValidator.Validate(string directory)`](../../build/pipeline/Inno.Build/Player/IPlayerSupportPackValidator.cs#L17) | Rejects an incomplete or incompatible platform closure.
`directory`: The prepared or installed Support Pack directory. | — | `System.IO.InvalidDataException`: A required platform input is absent or incompatible. | +| [`public sealed class Inno.Build.PlayerSupportPackCatalog`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L17) | Resolves and validates installed Player Support Packs without loading authoring services. | — | — | +| [`public Inno.Build.PlayerSupportPackCatalog.PlayerSupportPackCatalog(string root)`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L49) | Creates a catalog rooted at the directory containing target-specific Player Support Packs.
`root`: The directory whose target children contain immutable fingerprint directories and a current index. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.PlayerSupportPackCatalog.SelectAsync(Inno.Build.BuildTargetId target, string fingerprint, Inno.Build.IPlayerSupportPackValidator validator, System.Func verifyInputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L85) | Validates one immutable content identity and atomically selects it for subsequent readers.
`target`: The explicit target whose prepared Pack is requested.
`fingerprint`: The exact lowercase SHA-256 content identity recorded by the publisher.
`validator`: The borrowed platform closure validator.
`verifyInputs`: The final awaited source-input gate after output validation and before selection.
`cancellationToken`: Cancels validation or selection before replacing the current index. | Reading ownership of the complete requested Pack; dispose it after every consumer finishes. | `System.ArgumentException`: The content identity is invalid or the target path escapes the catalog.
`System.IO.InvalidDataException`: The requested directory or its complete file set and hashes are invalid.
`System.OperationCanceledException`: Validation or selection was canceled. | +| [`public Inno.Build.PlayerSupportPackLease Inno.Build.PlayerSupportPackCatalog.Acquire(Inno.Build.BuildTargetId target, Inno.Build.IPlayerSupportPackValidator validator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L144) | Resolves one installed Support Pack after validating its deployment-only closure.
`target`: The platform and architecture identity of the required Support Pack.
`validator`: The target-owned validator for runtime or linker inputs.
`cancellationToken`: Cancels ownership acquisition or complete content validation before returning a lease. | Reading ownership of the complete pack; dispose it after its final consumer finishes. | `System.IO.DirectoryNotFoundException`: Thrown when no Support Pack is installed for .
`System.IO.InvalidDataException`: Thrown when the pack is empty, contains build-time payload, or lacks its Player executable or native runtimes.
`System.OperationCanceledException`: Acquisition or validation was canceled; no reading ownership is transferred.
`System.TimeoutException`: Exclusive repair or retirement retains the artifact for more than thirty seconds. | +| [`public System.Threading.Tasks.ValueTask Inno.Build.PlayerSupportPackCatalog.PublishAsync(Inno.Build.BuildTargetId target, string stagingDirectory, Inno.Build.IPlayerSupportPackValidator validator, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), System.Func? verifyInputs = null)`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackCatalog.cs#L198) | Validates and publishes an immutable pack, then atomically selects it for new readers.
`target`: The platform and architecture owning the staged inputs.
`stagingDirectory`: A complete, exclusively owned staging tree on the same filesystem as this catalog. Successful publication consumes this directory unless identical inputs already exist.
`validator`: The target validator for runtime and linker inputs.
`cancellationToken`: Cancels validation or lease acquisition before selecting the completed pack.
`verifyInputs`: Optional final source-input gate after output validation and any publication wait. Omit only when the caller exclusively owns a fully prepared tree with no external preparation inputs. | Reading ownership of the selected immutable pack; earlier readers retain their own protection. | `System.IO.InvalidDataException`: The candidate is incomplete or contains forbidden inputs.
`System.IO.IOException`: The completed directory or its atomic current index cannot be installed.
`System.OperationCanceledException`: Cancellation occurred before the current index was committed. | +| [`public sealed class Inno.Build.PlayerSupportPackLease : System.IDisposable`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L10) | Keeps verified Player compilation and linker inputs alive until their consumer finishes. | — | — | +| [`public string Inno.Build.PlayerSupportPackLease.directory { get; }`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L29) | Gets the verified absolute directory while this lease owns reading rights. | — | `System.ObjectDisposedException`: The consumer has already released its reading rights. | +| [`public void Inno.Build.PlayerSupportPackLease.Dispose()`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L42) | Releases reading rights after all compilation, copying and linking have completed. Repeated disposal has no effect. | — | — | +| [`public sealed class Inno.Build.PluginBuildRequest`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L11) | Requests one deterministic project-to-Plugin package without a companion authoring asset. | — | — | +| [`public required string Inno.Build.PluginBuildRequest.pluginId { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L16) | Gets or initializes the globally stable lowercase Plugin identity. | — | — | +| [`public required string Inno.Build.PluginBuildRequest.displayName { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L21) | Gets or initializes the user-facing Plugin name. | — | — | +| [`public required string Inno.Build.PluginBuildRequest.outputPath { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L26) | Gets or initializes the destination .iplugin package path. | — | — | +| [`public bool Inno.Build.PluginBuildRequest.includeDependencies { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L31) | Gets or initializes whether the complete transitive dependency closure is embedded. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Build.PluginBuildRequest.dependencies { get; init; }`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L37) | Gets or initializes the installed plugins this package actually requires. Their transitive dependencies are included in the package manifest. | — | — | +| [`public void Inno.Build.PluginBuildRequest.Validate()`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L45) | Validates package identity and destination syntax. | — | `System.IO.InvalidDataException`: Thrown when the request cannot identify a portable Plugin package. | +| [`public Inno.Build.PluginBuildRequest.PluginBuildRequest()`](../../build/pipeline/Inno.Build/PluginBuildRequest.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Build.SdkScriptCompilationEnvironment : Inno.Scripting.Compiler.IScriptCompilationEnvironmentProvider`](../../build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs#L17) | Evaluates the authoring project's selected SDK and maps its framework contract into scripting inputs. | — | — | +| [`public Inno.Build.SdkScriptCompilationEnvironment.SdkScriptCompilationEnvironment(string dotnetHost, string projectRoot)`](../../build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs#L34) | Creates a host-owned input provider without selecting installed reference packs independently.
`dotnetHost`: The .NET host executable selected by the composition root.
`projectRoot`: The authoring project whose ancestor SDK configuration governs compilation and IDE use. | — | `System.ArgumentException`: A host or project location is empty. | +| [`public Inno.Scripting.Compiler.ScriptCompilationInputs Inno.Build.SdkScriptCompilationEnvironment.Capture(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../build/pipeline/Inno.Build/Scripting/SdkScriptCompilationEnvironment.cs#L45) | Captures current SDK inputs without choosing a framework inside the scripting compiler.
`cancellationToken`: Cancels input preparation before any script artifact is published. | Complete immutable inputs; missing or inconsistent SDK inputs must fail explicitly. | — | ## 项目依赖 -- [Inno.Content](../assets/Inno.Content.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization.Generators](../core/Inno.Core.Serialization.Generators.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Plugins.Authoring](../plugins/Inno.Plugins.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene.Assets](../scene/Inno.Scene.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Compiler](../scripting/Inno.Scripting.Compiler.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Plugins](../plugins/Inno.Plugins.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Build.Managed](Inno.Build.Managed.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 - -### `Inno.Build.PlayerSupportPackLease` - - - - - -| 声明 | 行为与结果 | 所有权/线程 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Build.PlayerSupportPackLease.Dispose()`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L42) | Releases reading rights after all compilation, copying and linking have completed. Repeated disposal has no effect. | 当前实例唯一拥有本次读取权;可重复 Dispose。 | 释放后不能再取得 directory;不接管调用者进程。 | -| [`public string Inno.Build.PlayerSupportPackLease.directory { get; }`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L29) | Gets the verified absolute directory while this lease owns reading rights. | 当前实例唯一拥有本次读取权;可重复 Dispose。 | 释放后不能再取得 directory;不接管调用者进程。 | -| [`Inno.Build.PlayerSupportPackLease`](../../build/pipeline/Inno.Build/Player/PlayerSupportPackLease.cs#L10) | Keeps verified Player compilation and linker inputs alive until their consumer finishes. | 当前实例唯一拥有本次读取权;可重复 Dispose。 | 释放后不能再取得 directory;不接管调用者进程。 | +| 工程 | 边界 | +| --- | --- | +| `Inno.Build.Toolchains` | 实现依赖;不传递 compile surface | +| `Inno.Content` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 公开签名依赖 | +| `Inno.Core.Serialization.Generators` | 编译器 Analyzer;不进入运行时 | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Plugins.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Plugins` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Build.Managed` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/build/README.md b/docs/build/README.md index a17e3427e..3123c40bb 100644 --- a/docs/build/README.md +++ b/docs/build/README.md @@ -42,6 +42,10 @@ Windows、MacOS、Browser 的 target/source/validator/template 属于各平台 B 需要验证全新托管输出或并行验收时,可对 `Invoke-ManagedChecks.ps1` 指定独立的 `-ArtifactsPath`;构建、普通测试、隔离发现和隔离执行共用该 SDK 输出 owner。 +共同 props 在 SDK 默认输出路径建立前,为具有产品输入的工程选择独立 `ArtifactsProjectName`:身份覆盖 Native target、工具 target、产品 props 和 Native build fingerprint。中立工程与 Roslyn 工具保持中立,SDK 自身的 configuration、TFM 和 RID pivots 不变;还原和编译消费同一 owner。调用者显式设置的 SDK 输出属性仍由调用者负责,构建工具必须读取求值后的实际路径,不能拼接 `ArtifactsPath/bin/`。这避免两个正式开发入口在同一个 SDK 构建图中争写共享工程的 DLL;不靠串行构建或清空真实 RID 规避。 + +Editor 开发入口只声明需要还原其宿主路由选中的产品工程。共享 `RestoreProductTargetGraph` 使用已有 `RestoreOwnedProjectTask`、Core IO lease 和 Restore/compilation 隔离,先完成该闭包,再开始入口的直接及传递引用构建。平台产品独立构建仍按同一协议准备自身;入口不保存平台名单或另建依赖解析器。 + Reload fixture 使用普通 `ProjectReference` 声明构建依赖,通过 SDK 的 `OutputItemType` 接收实际产物,再部署到测试宿主的 Modules 目录;不拼接 `bin//`,也不从另一输出 owner 借用 fixture。 Wiki 脚本 fixture 从测试工程实际求值的 framework `ReferencePath` 编译,并在所属输出目录保存构建清单;运行测试的 runtime 补丁与 SDK reference pack 可以不同,不按 runtime 补丁号推导 reference pack 路径。 diff --git a/docs/core/Inno.Core.IO.md b/docs/core/Inno.Core.IO.md index b9ee0693a..12ff5e2d4 100644 --- a/docs/core/Inno.Core.IO.md +++ b/docs/core/Inno.Core.IO.md @@ -14,8 +14,18 @@ `IByteDocumentStore` 仅用于小型文档。`FileByteDocumentStore` 复用 AtomicFile;`ReadOnlyByteDocumentStore` 捕获 owned bytes,写入明确失败。它不是 Asset catalog 或事件协议。 +`FileByteDocumentStore.Read` 用同一个声明读取和删除共享的句柄固定当前发布文件,完整读取后才释放;返回的 bytes 独立归调用方。Windows 原子重命名的目标名称可能在系统关闭重命名句柄前可见,读取方必须允许该句柄完成;这不表示允许原地写入。写入方始终原子替换文件,读取期间替换不改变已打开句柄选中的内容。缺失返回 null,空文档返回空数组,其他读取失败及过大的文档明确抛出异常,不以重试或空内容掩盖错误。 + +`AtomicFile` 只发布已经完整写入的同目录候选。`overwrite: false` 的冲突检查和发布必须是同一个文件系统操作,不能先检查不存在再 rename。Windows 使用系统的不可覆盖移动;Unix 使用公开 POSIX `link` 原语原子创建目标,再退休候选,不调用 SDK 私有导出。此处只选择宿主文件系统原语,不选择引擎 target、backend 或产品平台。Unix 文件系统必须支持同目录 hard link;不支持时明确抛出 `IOException`,不回退成可能暴露部分内容的复制。不能提供该宿主原语的执行环境明确抛出 `PlatformNotSupportedException`,其 inner exception 保留能力缺失原因;不维护引擎平台名单。普通原子替换不受影响。 + +`Install` 在发布失败时保留调用方候选;发布成功后移除候选。若目标已经发布而候选删除失败,异常明确说明目标完整存在,调用方不能重试覆盖他人的目标。`WriteAllBytes` 自己拥有 staging 并负责在退出时清理。空的现有文档也属于已有目标,不能因为长度为零而覆盖。父目录和候选仍需要调用方的所有权保护;原子发布不抵御非合作进程替换整棵父目录。 + +`AtomicFile.IsCandidatePath` 只识别本 publisher 保留的完整 staging 文件名,供 source discovery 排除尚未发布的临时 bytes。识别名称不授予读取、清理或删除权限。 + `AtomicDirectory.Publish` 只向不存在的目标发布完整 staging。需要替换目录时使用 `Install`,由调用方先协调全部读写 owner;这两个原子目录入口不会自行取得读取 lease。提交后的旧目录清理失败与提交前失败具有不同结果,不能把清理异常解释为新产物未安装。 +`AtomicDirectory.Install(source, destination, cancellationToken)` 沿用 Core 的有界 rename 策略。准备或提交前等待可取消;旧目标已移到 backup 时,恢复使用不受已取消 token 影响的同一文件系统协议。提交后的取消不撤销完整产物,backup 清理失败仍通过 `DirectoryCleanupException` 区分。若安装和恢复都失败,`AggregateException` 保留两次失败及旧目录位置,不能删除候选或旧目录伪装恢复成功。已有调用方省略 token 时保持不可取消的调用语义,不保留第二个 overload。 + `DirectoryCleanupException` 只表示提交后的 backup 删除失败;`installedDirectory` 指向已经完整安装的新目录,`backupDirectory` 指向保留的旧目录。消费方记录独立清理诊断并继续使用已提交输出,不重试安装、不回滚新目录。其他 IO 异常仍表示安装阶段失败,遵循原来的恢复语义。 `OwnedReadStream` 独立持有读取 pin,外层 lease 提前关闭不破坏打开的读取流。关闭 stream 才释放它自己的 pin;同一 lease 的多个 stream 分别结束各自的读取生命周期。 @@ -74,22 +84,24 @@ AtomicFile.Install 要求两个 parent 的规范拼写完全一致;候选与 - + | 声明 | 行为、参数与结果 | | --- | --- | -| [`public static void Inno.Core.IO.AtomicDirectory.Install(string source, string destination)`](../../src/foundation/core/Inno.Core.IO/AtomicDirectory.cs#L96) | Installs a disjoint candidate tree and restores the previous destination if installation fails.
`source`:The complete candidate directory.
`destination`:The destination directory.
约束:The caller must serialize readers and writers while the two directory moves run. Backup cleanup runs after commit; its failure preserves the installed candidate and remaining backup.
T:System.ArgumentException:Either path is blank, or the candidate and destination trees overlap.
T:System.IO.DirectoryNotFoundException:The candidate directory does not exist.
T:System.IO.IOException:Installation fails before the candidate becomes authoritative.
T:Inno.Core.IO.DirectoryCleanupException:Installation committed, but the old backup could not be removed. The installed tree remains complete.
T:System.UnauthorizedAccessException:The operating system denies access before the candidate can be installed.
T:System.AggregateException:Installation and restoration both fail; neither tree is deleted to force a rollback. | +| [`public static void Inno.Core.IO.AtomicDirectory.Install(string source, string destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/foundation/core/Inno.Core.IO/AtomicDirectory.cs#L104) | Installs a disjoint candidate tree and restores the previous destination if installation fails.
`source`: The complete candidate directory.
`destination`: The destination directory.
`cancellationToken`: Cancels preparation or bounded rename waits before the candidate commits.
约束:The caller must serialize readers and writers while the two directory moves run. Cancellation before commit restores the previous destination without canceling rollback. A committed candidate is not canceled afterward. Backup cleanup runs after commit; its failure preserves the installed candidate and remaining backup.
`System.ArgumentException`: Either path is blank, or the candidate and destination trees overlap.
`System.IO.DirectoryNotFoundException`: The candidate directory does not exist.
`System.IO.IOException`: Installation fails before the candidate becomes authoritative.
`Inno.Core.IO.DirectoryCleanupException`: Installation committed, but the old backup could not be removed. The installed tree remains complete.
`System.UnauthorizedAccessException`: The operating system denies access before the candidate can be installed.
`System.AggregateException`: Installation and restoration both fail; neither tree is deleted to force a rollback.
`System.OperationCanceledException`: Cancellation is requested before the candidate commits; any moved previous tree is restored. | | [`public static void Inno.Core.IO.AtomicDirectory.Publish(string source, string destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/foundation/core/Inno.Core.IO/AtomicDirectory.cs#L43) | Publishes a complete directory at an unoccupied path without replacing an existing destination.
`source`:The complete candidate directory, disjoint from the destination.
`destination`:The unoccupied publication path on the same filesystem.
`cancellationToken`:Cancels preparation or bounded waits before the rename commits.
约束:Windows access and sharing failures are retried for at most two seconds to tolerate temporary readers. Failure preserves the candidate and any existing destination. A committed rename is not canceled afterward.
T:System.ArgumentException:A path is blank or the two directory trees overlap.
T:System.IO.DirectoryNotFoundException:The source directory does not exist.
T:System.IO.IOException:The destination exists, the filesystem cannot rename the tree, or a transient block persists.
T:System.UnauthorizedAccessException:The operating system continues denying access after the bounded retry window.
T:System.OperationCanceledException:Cancellation is requested before the directory is published. | | [`public static class Inno.Core.IO.AtomicDirectory`](../../src/foundation/core/Inno.Core.IO/AtomicDirectory.cs#L10) | Provides rollback-safe installation of complete directory trees. | ### `Inno.Core.IO.AtomicFile` + | 声明 | 行为、参数与结果 | | --- | --- | +| [`public static bool Inno.Core.IO.AtomicFile.IsCandidatePath(string path)`](../../src/foundation/core/Inno.Core.IO/AtomicFile.cs#L20) | Recognizes the reserved complete atomic-publication candidate name.
`path`: File name or path; no file is opened.
Returns true only for a nonempty destination plus the exact marker and 32-digit hexadecimal identity. Does not establish ownership or authorize cleanup.
`ArgumentNullException`: The path is null. | | [`public static void Inno.Core.IO.AtomicFile.Install(string source, string destination, bool overwrite = true)`](../../src/foundation/core/Inno.Core.IO/AtomicFile.cs#L64) | Atomically installs an existing same-directory candidate file.
`source`:The complete candidate file beside the destination.
`destination`:The destination file path beside the candidate.
`overwrite`:Whether an existing destination may be replaced; false never overwrites an external creator. | | [`public static void Inno.Core.IO.AtomicFile.WriteAllBytes(string path, System.ReadOnlySpan data, bool overwrite = true)`](../../src/foundation/core/Inno.Core.IO/AtomicFile.cs#L23) | Writes a complete byte payload and atomically installs it at the destination.
`path`:The destination file path.
`data`:The complete file payload.
`overwrite`:Whether an existing destination may be replaced; false atomically rejects name collisions. | | [`public static class Inno.Core.IO.AtomicFile`](../../src/foundation/core/Inno.Core.IO/AtomicFile.cs#L9) | Provides durable same-directory file replacement without exposing partial destination content. | @@ -131,8 +143,8 @@ AtomicFile.Install 要求两个 parent 的规范拼写完全一致;候选与 | 声明 | 行为、参数与结果 | | --- | --- | | [`public Inno.Core.IO.FileByteDocumentStore.FileByteDocumentStore(string path)`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L22) | Selects one host-owned absolute document location.
`path`:The absolute file path; its parent directory is prepared only when writing.
T:System.ArgumentException:The path is empty or relative. | -| [`public byte[]? Inno.Core.IO.FileByteDocumentStore.Read()`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L40) | Obtains a newly owned complete document snapshot.
返回:Owned document bytes, or null when no document exists; an empty document remains distinct from absence. | -| [`public void Inno.Core.IO.FileByteDocumentStore.Write(System.ReadOnlySpan data)`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L57) | Replaces the document atomically after preparation succeeds.
`data`:The complete candidate bytes; the implementation cannot retain borrowed caller memory.
T:System.NotSupportedException:The source is read-only. | +| [`public byte[]? Inno.Core.IO.FileByteDocumentStore.Read()`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L50) | Obtains a newly owned complete document snapshot.
返回:Owned document bytes, or null when no document exists; an empty document remains distinct from absence.
`System.IO.IOException`:无法完整读取或文档超过支持的数组长度。
`System.UnauthorizedAccessException`:读取被拒绝或目标是目录。读取句柄允许原子重命名完成,且固定选中的发布内容。 | +| [`public void Inno.Core.IO.FileByteDocumentStore.Write(System.ReadOnlySpan data)`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L73) | Replaces the document atomically after preparation succeeds.
`data`:The complete candidate bytes; the implementation cannot retain borrowed caller memory.
T:System.NotSupportedException:The source is read-only. | | [`public bool Inno.Core.IO.FileByteDocumentStore.canWrite { get; }`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L34) | Gets whether this source permits atomic replacement. | | [`public string Inno.Core.IO.FileByteDocumentStore.documentName { get; }`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L31) | Gets a stable diagnostic name for this document, independent of its storage implementation. | | [`public bool Inno.Core.IO.FileByteDocumentStore.exists { get; }`](../../src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs#L37) | Gets whether a document currently exists; callers must still handle absence during a later read. | diff --git a/docs/core/Inno.Core.Serialization.md b/docs/core/Inno.Core.Serialization.md index ffeee4f16..279c88e8c 100644 --- a/docs/core/Inno.Core.Serialization.md +++ b/docs/core/Inno.Core.Serialization.md @@ -4,6 +4,43 @@ `Inno.Core.Serialization` 提供确定性的二进制对象图格式、基于 attribute 的属性持久化,以及通过 TypeRegistry 自动发现的 Converter。它既支持“序列化完整 `ISerializable` 根对象”,也支持用 Writer/Reader 显式定义 schema。 +### 完整状态捕获与复用 + +Host 可以使用 `CaptureSnapshot(value, context, previous)` 捕获不可变 `SerializationSnapshot`。默认完整执行当前 generation 的 getter 和 Converter;未变化的中立节点以 copy-on-write 复用。Converter 可显式声明完整捕获 key,证明未变化时复用已有节点;普通 `Serialize` 从不查询 key。`ToArray()` 使用同一二进制编码器返回独立 bytes,修改返回数组不会修改捕获结果。快照只持有中立标量、字符串和复制的二进制数据,不持有源对象、`Type`、Converter、delegate 或 generation lease,可在原 Registry 退休后编码。 + +该入口不导出到脚本。快照是 owner 所属的瞬时观察值,不是第二种文件格式,也不进入 History、Workspace 或资源元数据。比较失败时不发布新快照,旧快照保持有效。快照捕获期间,Converter 借用的 Writer 只属于其同步回调;不要在回调返回后继续写入。 + +内部 Writer 对同序、同类型且 converter 选择不变的标量复用一次位置查询,不重复查找相同节点;重复 member 仍明确失败。对象 keys 的现有二进制规范顺序保持不变,因此 Converter 调整写入次序不会改变规范 bytes,数组顺序仍属于数据。所有优化共用当前编码器与 generation lease,不缓存业务对象或绕过自定义 Converter。 + +`SerializationWriter.WritePropertiesData(name, value, context)` 将同一 generation 的独立属性快照写为现有 binary member,供 Scene 等需要保留独立恢复边界的 Converter 使用。它与 `CapturePropertiesData` 的 bytes 一致,并在重复捕获时复用未变化的属性节点和编码结果;传入的 context 必须保留 owner 的引用解析能力。普通序列化、保存与 Missing 恢复继续使用同一 pipeline,不建立领域专用 serializer。 + +### 显式完整捕获 key + +`SerializationConverter.TryCaptureKey(value, key)` 是可选 protected 扩展点,默认返回 false。只有当前注册的 Core Identity 根对象参与。回调每次捕获都会执行,必须纯读取并覆盖全部输出、上下文依赖与顺序;不能只报告 UI 编辑通知。Core 自动限定当前 runtime identity/domain、metadata 声明和 Converter generation。回调返回 true 且 key 完全相同时才省去 `Write`;变化时执行原 writer,随后再次验证 key 与所有借用 identity,失败不发布新快照。 + +| API | 责任与限制 | +| --- | --- | +| `SerializationCaptureKeyBuilder.context` | 当前同步回调的完整 owner context;回调结束后失效。 | +| `EnsureCapacity(int)` | 按已冻结输入的实际条目预留容量,不改变 key。 | +| `TryAddOwner(IdentityObject, ulong?)` | 通过现有 Core Identity 记录 owner 与完整 revision;null、失效绑定或任何失败使整个 key 不可复用。 | +| `TryAddProperties(ISerializable)` | 仅接受 metadata 显式证明的完整属性 revision,以及权威 Converter resolver 已确认没有自定义 Converter 的纯值成员;不在查询时构造 Converter。 | +| `AddValue(ulong)` / `AddValue(Guid)` / `AddText(string?)` | 追加精确值、完整标识符和有序文本,区分 null 与空串;不使用有碰撞风险的标识符 hash。 | +| `SerializationRevisionAttribute(string memberName)` / `memberName` | 选择 sealed serializable identity 对象所属的非持久化 ulong/nullable ulong 字段或属性;每次相关写入前递增,不能循环复用计数。 | +| `SerializationTypeMetadata.WithCaptureRevision(Func)` | Metadata provider 显式声明上述完整 revision 读取器;null 结果拒绝复用。 | +| `SerializationTypeMetadata.WithPureFieldAccess()` | Provider 证明值类型的持久成员仅为直接读取的 primitive 字段;任意 getter 不具备该资格。 | + +这些公开入口是 Converter 与 metadata provider 组合所需的最小能力。Builder 和 attribute 导出为逻辑脚本契约;metadata provider 的声明能力仍只由 Host/编译器 owner 使用。不得把 builder、metadata、访问委托或类型长期存入插件状态。Builder 只属于同步 owner 线程,跨线程、退休后访问明确失败;即使扩展错误地保留 builder,其退休也会清空 context、identity 检查与临时存储。 + +不完整 revision、计数耗尽、可变引用、Missing、任意 getter、stateful Converter 和未证明的 metadata 继续执行原 capture。新返回的等价 metadata 可以正常捕获,但不会获得跨声明复用。Key 不进入二进制数据、History、Workspace 或 Asset metadata,不是第二套序列化或对象管理系统。静态 metadata provider 可以声明同样能力;未声明时保持完整捕获,不要求 Player 使用动态反射来源。 + +`SerializationWriter.WriteObjectArray(name, values, writeElement, captureKey)` 将同一证明用于结构化数组元素。可选回调必须声明当前 Core identity owner,以及完整数据、顺序、布局和上下文输入。普通序列化逐次执行原 `writeElement`;Snapshot 只复用已经完整证明的相同元素,变化元素仍使用相同 writer。回调抛错、identity 失效或写入中输入变化均拒绝发布。未证明的元素继续完整捕获;初次获得 hint 不能单独改变数据未变化的既有节点。 + +Core 跟踪当前同步写入范围中实际执行的 Converter,并从独立属性 payload 向外传播中立标记。嵌套 Converter 的状态不能由父元素的局部 revision 隐式证明,因此其执行会阻止父节点发布可复用 key;既有 Converter 本身仍遵守自己的捕获契约。标记不编码、不持有类型或回调,也不是另一个依赖解析或缓存服务。 + +已证明的不可变对象节点可按需保存最多 64 KiB 的规范编码块。编码和流式写入复用同一编码器;只保留中立 bytes,并以并发安全方式发布完整块。更大的节点仍走原流式编码,不分配完整输出缓冲。未变化的对象可重复写出已有块,变化对象重新编码;没有第二套编码格式或全局块缓存。`ToArray()` 仍返回独立 caller-owned bytes,目标 Stream 失败不会破坏快照。Guid 写入直接使用借用 Span,规范字节顺序保持一致。 + +Scene 的实际消费者在 Scene owner 中声明每个对象的元数据计数、精确 source/parent ID、sibling 位置及全部 Component 属性证明。结构、对象顺序、根属性、别名和 System 仍由现有 writer 处理;单个对象改变不强迫其他已证明对象重新编码。普通插件字段、自定义 Converter、Missing 和 Prefab 不会因这一优化失去变化检测。源状态必须在 owner 同步操作内稳定;此能力不是并发读写锁。 + ## 初始化 Shader 等 Editor 创作回调可以借用宿主的 `SerializationRegistry`,脚本侧只导出数据编解码及属性读取。 @@ -154,7 +191,7 @@ public sealed class OrderedState : ISerializable ### 跨 generation 的独立成员恢复 -逐成员快照用于热重载和其他需要容忍 schema 演进的流程,不会改变普通 `Serialize` / `Deserialize` / `Restore` 的严格行为: +逐成员快照用于当前会话中的扩展重载、成员暂时 Missing 及恢复;普通 `Serialize` / `Deserialize` / `Restore` 仍保持严格的当前格式: ```csharp using System.Collections.Generic; @@ -175,7 +212,7 @@ static SerializationPropertyRestoreResult RestoreMatchingProperties( `SerializationPropertyRestoreMode.Strict` 在第一个匹配但不可解码的成员处抛异常。`Compatible` 使用独立的 operation checkpoint 跳过该成员,撤销它注册的 completion callback,保留目标对象构造后的默认值,并在 `failures` 中记录旧类型、新类型和错误信息。 -Editor History 等需要跨 assembly generation 保留数据的系统应使用 `CapturePropertyData` / `RestorePropertiesData`。该格式包含 property key 与原声明类型信息,但不包含 owning object 引用;调用方必须用 persistent ID 在当前 generation 解析目标。格式严格检查当前 magic、长度与 trailing bytes,损坏数据不会被部分接受。 +Editor History 等需要跨 assembly generation 保留数据的系统应使用 `CapturePropertyData` / `RestorePropertiesData`。该格式保存 property key 与独立编码的值节点,不保存 CLR Type 或 owning object;恢复时由当前目标的 metadata 决定声明类型。内存中的 `SerializationPropertySnapshot.propertyType` 只属于其创建代际,不能据此长期持有 collectible 类型。调用方必须用 persistent ID 在当前 generation 解析目标。格式严格检查当前 magic、长度与 trailing bytes,损坏数据不会被部分接受。 已删除成员计入 `ignoredCount`,新增成员自然保持默认值。所有兼容成员完成后,目标的 `[OnSerializableRestored]` 仍只执行一次;对象级 restore hook 失败属于严重错误,不会被 compatible 模式吞掉。 @@ -247,7 +284,7 @@ Converter 应为无状态 class,并提供无参构造函数。选择优先级 | `Write(name, value)` | 经统一 value pipeline 写命名值;名称必须非空且唯一。 | | `Write(name, object?, Type declaredType)` | 按明确声明写值;和泛型入口共用格式、Converter 与空值规则,不构造运行时泛型方法。 | | `WriteObject(name, Action)` | 写一个结构化子对象。 | -| `WriteObjectArray(name, values, writeElement)` | 写有序结构化对象数组。 | +| `WriteObjectArray(name, values, writeElement, captureKey = null)` | 写有序结构化对象数组。 | | `WriteProperties(ISerializable)` | 把对象上标注的成员写入当前 object。 | ## SerializationReader @@ -299,6 +336,12 @@ static (float x, float y)[] RoundTripPoints( - Compatible property restore 只跳过单个成员的数据/类型不兼容;对象结构或 restore hook 错误仍由上层事务处理。 - 序列化层只实现当前二进制契约,不包含旧 schema 的兼容读取或迁移分支。不兼容数据会直接失败。 +单属性快照 CapturePropertyData 只读取指定的持久属性;其他 getter 不会因捕获该属性而执行。二进制节点编码先计算确定大小,再用标准库 BinaryWriter 写入独立的结果数组,避免增长缓冲区及最终复制。当前 magic、字段顺序、UTF-8 长度编码和调用方拥有的 bytes 语义保持一致;这是非公开实现,不增加格式或缓存协议。 + +已解析 Converter 的查找复用当前 generation 自有的并发缓存;首次解析及实例创建仍受同一个 owner 协调,最后一个 lease 释放后才清空缓存并退休实例。冻结 generation 的并行调用仍要求 Converter 和被读写数据自身满足相应线程约束,调用方应在所有操作完成后释放 lease。这些内部查找改良不改变 Registry、Context 或业务对象的所有权。 + +内部序列化操作仅在需要时创建回调、恢复去重与循环检测集合;不使用跨操作对象池。已有上下文只有在 TypeCatalog、Registry 和 metadata source 均属于当前 owner 时才复用,否则重新绑定这些依赖并保留调用方服务。`SerializationContext.With` 仍返回新的不可变上下文,确定性键排序和当前二进制格式保持不变。 + ## 当前源码公开 API 清单 按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 @@ -603,13 +646,15 @@ static (float x, float y)[] RoundTripPoints( + + | 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | | --- | --- | --- | --- | | [`public Inno.Core.Serialization.SerializationRegistry.SerializationRegistry(Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.ISerializationMetadataSource metadata)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L32) | Creates a serialization registry derived from one isolated type catalog.
`types`:The type catalog that owns converter extension generations.
`metadata`:The declaration access provider selected by the same composition root. | — | `System.ArgumentNullException`:Thrown when types is null. | | [`public Inno.Core.Serialization.SerializationGeneration Inno.Core.Serialization.SerializationRegistry.CaptureGeneration()`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L65) | Captures the currently active converter generation for deterministic work that may cross asynchronous continuations.
返回:An immutable serialization generation owned by the caller. | — | `System.InvalidOperationException`:Thrown when serialization services have not been initialized. | | [`public System.Collections.Generic.IReadOnlyList Inno.Core.Serialization.SerializationRegistry.CaptureProperties(Inno.Core.Serialization.ISerializable value, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L146) | Captures each persistent property independently for reload-safe state restoration.
返回:Ordered independent property snapshots.
`value`:The object whose serializable properties should be captured.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when value is null.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | | [`public byte[] Inno.Core.Serialization.SerializationRegistry.CapturePropertiesData(Inno.Core.Serialization.ISerializable value, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L214) | Captures every persistent property as neutral bytes without serializing the owning object graph.
返回:Strictly validated bytes containing independently encoded property values.
`value`:The serializable object whose persistent properties should be captured.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when value is null.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | -| [`public byte[] Inno.Core.Serialization.SerializationRegistry.CapturePropertyData(Inno.Core.Serialization.ISerializable value, string propertyName, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L180) | Captures one persistent property as independently restorable neutral bytes.
返回:Neutral bytes containing the property name and encoded value.
`value`:The serializable object containing the requested property.
`propertyName`:The exact serialized member key to capture.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when value is null.
`System.ArgumentException`:Thrown when propertyName is empty or does not identify a persistent property.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | +| [`public byte[] Inno.Core.Serialization.SerializationRegistry.CapturePropertyData(Inno.Core.Serialization.ISerializable value, string propertyName, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L180) | Captures one persistent property as independently restorable neutral bytes without reading unrelated members.
返回:Neutral bytes containing the property name and encoded value.
`value`:The serializable object containing the requested property.
`propertyName`:The exact serialized member key to capture.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when value is null.
`System.ArgumentException`:Thrown when propertyName is empty or does not identify a persistent property.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | | [`public TResult Inno.Core.Serialization.SerializationRegistry.Decode(System.ReadOnlySpan bytes, System.Func read, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L566) | Decodes a manually defined structured schema from deterministic binary data.
返回:The callback result after all completion callbacks succeed.
`bytes`:The encoded bytes.
`read`:The callback that reads the root object.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when read is null.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | | [`public T Inno.Core.Serialization.SerializationRegistry.Deserialize(System.ReadOnlySpan bytes, Inno.Core.Serialization.SerializationContext? context = null) where T : class, Inno.Core.Serialization.ISerializable`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L415) | Deserializes a new serializable root object from deterministic binary data.
返回:The restored object.
`bytes`:The encoded bytes.
`context`:Optional immutable converter context. | — | `System.InvalidOperationException`:Thrown when the manager is not initialized. | | [`public void Inno.Core.Serialization.SerializationRegistry.Dispose()`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L45) | Releases every converter generation owned by this registry. | — | — | @@ -622,6 +667,19 @@ static (float x, float y)[] RoundTripPoints( | [`public Inno.Core.Serialization.SerializationPropertyRestoreResult Inno.Core.Serialization.SerializationRegistry.RestorePropertiesData(Inno.Core.Serialization.ISerializable target, System.ReadOnlySpan data, Inno.Core.Serialization.SerializationPropertyRestoreMode mode = Inno.Core.Serialization.SerializationPropertyRestoreMode.Strict, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L324) | Restores one or more independently encoded persistent properties from neutral bytes.
返回:A summary containing restored, ignored, and skipped properties.
`target`:The existing object receiving the captured values.
`data`:Strict property snapshot bytes created by Inno.Core.Serialization.SerializationRegistry.CapturePropertyData(Inno.Core.Serialization.ISerializable,System.String,Inno.Core.Serialization.SerializationContext) or Inno.Core.Serialization.SerializationRegistry.CapturePropertiesData(Inno.Core.Serialization.ISerializable,Inno.Core.Serialization.SerializationContext).
`mode`:The failure policy for matching but incompatible current properties.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when target is null.
`System.ArgumentOutOfRangeException`:Thrown when mode is unknown.
`System.IO.InvalidDataException`:Thrown when data is malformed or unsupported.
`System.InvalidOperationException`:Thrown when strict restoration or an object-level callback fails. | | [`public byte[] Inno.Core.Serialization.SerializationRegistry.Serialize(T value, Inno.Core.Serialization.SerializationContext? context = null) where T : class, Inno.Core.Serialization.ISerializable`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L364) | Serializes a complete serializable root object into deterministic binary data.
返回:The encoded bytes.
`value`:The root object to serialize.
`context`:Optional immutable converter context. | — | `System.ArgumentNullException`:Thrown when value is null.
`System.InvalidOperationException`:Thrown when the manager is not initialized. | | [`public sealed class Inno.Core.Serialization.SerializationRegistry : System.IDisposable`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L12) | Owns one generation-aware converter registry and provides deterministic root serialization operations. | — | — | +| [`public Inno.Core.Serialization.SerializationSnapshot Inno.Core.Serialization.SerializationRegistry.CaptureSnapshot(T value, Inno.Core.Serialization.SerializationContext? context = null, Inno.Core.Serialization.SerializationSnapshot? previous = null) where T : class, Inno.Core.Serialization.ISerializable`](../../src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs#L395) | Captures complete serialized state while sharing unchanged neutral values with a previous capture.
The exact concrete root type, interpreted by the current converter generation.
`value`:The non-null root captured through its current converter and declared complete inputs.
`context`:The complete owner context, including required reference resolvers.
`previous`:An optional immutable capture to reuse; it is never changed or retained by this registry.
返回:The previous instance when all captured values can be reused, otherwise a new immutable snapshot. Reference equality proves unchanged bytes; different instances may still encode identically. Ordinary serialization and snapshot encoding produce identical bytes. Capture failure publishes no snapshot.
Writers borrowed by a snapshot converter belong to its synchronous callback and must not be used after it returns. Every capture still evaluates getters and converters; this is not a mutation-notification cache.
System.ArgumentNullException:The source value is null.
System.ObjectDisposedException:The registry has been disposed.
System.InvalidOperationException:A converter, unsupported value or reference context prevents capture. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | + +### `Inno.Core.Serialization.SerializationSnapshot` + + + + + +| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| --- | --- | --- | --- | +| [`public sealed class Inno.Core.Serialization.SerializationSnapshot`](../../src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs#L14) | Holds an immutable, neutral capture of serialized values without retaining the source object or its generation.
Capture again through to reuse unchanged values. Readers may share a snapshot across threads. It owns only scalar values, strings and copied binary data, never source instances, metadata, converters or delegates. It is transient and is not a persistence format. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | +| [`public byte[] Inno.Core.Serialization.SerializationSnapshot.ToArray()`](../../src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs#L24) | Encodes this capture using the same deterministic binary format as ordinary serialization.
返回:A new caller-owned byte array; changing it cannot modify this snapshot. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | +| [`public void Inno.Core.Serialization.SerializationSnapshot.WriteTo(System.IO.Stream destination)`](../../src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs#L33) | Streams this capture in the ordinary deterministic binary format without requiring a contiguous complete-output buffer.
`destination`:The writable caller-owned stream, which remains open.
System.ArgumentNullException:The destination is null.
System.ArgumentException:The destination cannot be written.
System.IO.IOException:The destination rejects a write; its partial bytes remain the caller's responsibility. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | ### `Inno.Core.Serialization.SerializationTypeMetadata` @@ -654,20 +712,25 @@ static (float x, float y)[] RoundTripPoints( - + + + + | 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | | --- | --- | --- | --- | | [`public void Inno.Core.Serialization.SerializationWriter.Write(string name, object? value, System.Type declaredType)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L85) | Writes a value using its declared metadata type without constructing a generic method at runtime.
`name`:The unique non-empty member name.
`value`:The value to encode, including null when permitted by the declared type.
`declaredType`:The exact declaration whose converter and value semantics apply. | — | `System.ArgumentNullException`:The declared type is null.
`System.ArgumentException`:The member name is empty.
`System.InvalidOperationException`:The operation has ended, the name is duplicated, or the value is unsupported. | | [`public void Inno.Core.Serialization.SerializationWriter.Write(string name, TValue value)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L59) | Writes a named value through the unified value pipeline.
`name`:The unique non-empty member name.
`value`:The value to write. | — | `System.ArgumentException`:Thrown when name is empty.
`System.InvalidOperationException`:Thrown when the name is duplicated or the value is unsupported. | | [`public void Inno.Core.Serialization.SerializationWriter.WriteObject(string name, System.Action write)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L107) | Writes a named structured object.
`name`:The unique non-empty member name.
`write`:The callback that fills the child object. | — | `System.ArgumentNullException`:Thrown when write is null. | -| [`public void Inno.Core.Serialization.SerializationWriter.WriteObjectArray(string name, System.Collections.Generic.IEnumerable values, System.Action writeElement)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L136) | Writes an ordered array of structured objects.
`name`:The unique non-empty member name.
`values`:The elements to write.
`writeElement`:The callback that fills each element object. | — | `System.ArgumentNullException`:Thrown when an argument is null. | +| [`public void Inno.Core.Serialization.SerializationWriter.WriteObjectArray(string name, System.Collections.Generic.IEnumerable values, System.Action writeElement, System.Func? captureKey = null)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L204) | 写有序结构化对象数组。`name` 是唯一非空成员;`values` 是当前输入;`writeElement` 填充普通对象;可选 `captureKey` 只在 Snapshot 中声明完整的中立输入证明。 | Builder 仅借用于同步回调;嵌套 Converter 阻止父节点复用;失败不修改旧快照。 | `ArgumentNullException`:values/writeElement 为 null;`ArgumentException`:名称无效;`InvalidOperationException`:借用失效、重复成员或捕获中输入变化。 | | [`public void Inno.Core.Serialization.SerializationWriter.WriteProperties(Inno.Core.Serialization.ISerializable value)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L168) | Writes annotated properties from a serializable object into the current structured object.
`value`:The object whose annotated properties should be written. | — | `System.ArgumentNullException`:Thrown when value is null. | | [`public Inno.Core.Serialization.SerializationContext Inno.Core.Serialization.SerializationWriter.context { get; }`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L29) | Gets the immutable context supplied to the current operation. | — | — | | [`public string Inno.Core.Serialization.SerializationWriter.path { get; }`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L34) | Gets the current diagnostic path. | — | — | | [`public System.Type Inno.Core.Serialization.SerializationWriter.valueType { get; }`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L39) | Gets the declared value type represented by this writer. | — | — | | [`public sealed class Inno.Core.Serialization.SerializationWriter`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L9) | Writes a structured object during the active serialization operation. | — | — | +| [`public Inno.Extensibility.Types.TypeCacheSnapshot Inno.Core.Serialization.SerializationWriter.typeSnapshot { get; }`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L53) | Borrows the immutable type snapshot pinned by this operation's converter generation.
Use this snapshot for consistent type identities during a complete capture. It must not be retained beyond the writer's operation; keeping it would retain its collectible types after the converter lease is released.
System.InvalidOperationException:The writer no longer belongs to an active capture. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | +| [`public void Inno.Core.Serialization.SerializationWriter.WritePropertiesData(string name, Inno.Core.Serialization.ISerializable value, Inno.Core.Serialization.SerializationContext? context = null)`](../../src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs#L241) | Writes independently restorable property bytes through the current converter generation.
`name`:The unique member receiving the binary property snapshot.
`value`:The object whose persistent properties are captured once.
`context`:The complete owner context, or null to use this writer's context.
System.ArgumentNullException:The source object is null.
System.InvalidOperationException:The writer has ended, a key is duplicated, or capture fails.
System.ArgumentException:The member name is empty. | 操作内借用;快照仅含中立值。 | 失败不发布部分捕获。 | ### `Inno.Core.Serialization.SerializedIdentityRemapper` @@ -715,6 +778,43 @@ static (float x, float y)[] RoundTripPoints( ## 项目依赖 +- [Inno.Core.Identity](Inno.Core.Identity.md):公开 key builder 使用现有 identity owner;不创建第二个对象路由表。 - [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 - [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 - [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 + +## 捕获 key 源码 API 清单 + +本节对应当前源码声明;完整线程、所有权、失败和脚本边界见前文。签名仍由正式 Wiki 门禁复核。 + + + + + + + + + + + + + + + + +| 源码声明 | 语义与约束 | +| --- | --- | +| [`public sealed class Inno.Core.Serialization.SerializationCaptureKeyBuilder`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L16) | 同步借用的完整 key builder;没有公开构造与释放权限。 | +| [`public Inno.Core.Serialization.SerializationContext Inno.Core.Serialization.SerializationCaptureKeyBuilder.context { get; }`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L64) | 借用 owner context;退休或跨线程读取抛 InvalidOperationException。 | +| [`public void Inno.Core.Serialization.SerializationCaptureKeyBuilder.EnsureCapacity(int capacity)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L78) | capacity 是实际输入容量,负数拒绝;不改变 key。 | +| [`public bool Inno.Core.Serialization.SerializationCaptureKeyBuilder.TryAddOwner(Inno.Core.Identity.IdentityObject source, ulong? revision)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L105) | source 为当前 Core owner;revision 覆盖全部所属状态,null/无效 identity 返回 false 并拒绝整个 key。 | +| [`public bool Inno.Core.Serialization.SerializationCaptureKeyBuilder.TryAddProperties(Inno.Core.Serialization.ISerializable source)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L134) | source 为当前属性 owner;只有完整 revision、纯值与无自定义 Converter 证明时返回 true。 | +| [`public void Inno.Core.Serialization.SerializationCaptureKeyBuilder.AddValue(ulong value)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L170) | value 作为有序精确中立输入;不解析或保存 live object。 | +| [`public void Inno.Core.Serialization.SerializationCaptureKeyBuilder.AddValue(System.Guid value)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L181) | value 作为完整中立标识符比较,不编码为有碰撞风险的 hash。 | +| [`public void Inno.Core.Serialization.SerializationCaptureKeyBuilder.AddText(string? value)`](../../src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs#L192) | value 使用 ordinal 比较,null 与空串不同;不编码到持久数据。 | +| [`public sealed class Inno.Core.Serialization.SerializationRevisionAttribute : System.Attribute`](../../src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs#L13) | 声明 sealed identity source 的完整非持久化 revision 成员。 | +| [`public Inno.Core.Serialization.SerializationRevisionAttribute.SerializationRevisionAttribute(string memberName)`](../../src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs#L24) | memberName 选择可读 ulong/nullable ulong 成员;空名抛 ArgumentException。 | +| [`public string Inno.Core.Serialization.SerializationRevisionAttribute.memberName { get; }`](../../src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs#L33) | 取得唯一声明的 revision 成员名称。 | +| [`public Inno.Core.Serialization.SerializationTypeMetadata Inno.Core.Serialization.SerializationTypeMetadata.WithPureFieldAccess()`](../../src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs#L108) | 返回声明纯 primitive 字段读取的不可变 metadata;非默认值类型抛 InvalidOperationException。 | +| [`public Inno.Core.Serialization.SerializationTypeMetadata Inno.Core.Serialization.SerializationTypeMetadata.WithCaptureRevision(System.Func readRevision)`](../../src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs#L134) | readRevision 是 provider 所属的完整计数读取器;null 读取器拒绝,返回新 metadata,不改变原声明。 | +| [`protected virtual bool Inno.Core.Serialization.Converters.SerializationConverter.TryCaptureKey(T value, Inno.Core.Serialization.SerializationCaptureKeyBuilder key)`](../../src/foundation/core/Inno.Core.Serialization/Converters/SerializationConverter.cs#L81) | value 是当前根对象,key 只借用于本次同步回调;仅全部输出与上下文输入已覆盖时返回 true,默认 false。 | diff --git a/docs/editor/Inno.Editor.Annotations.md b/docs/editor/Inno.Editor.Annotations.md index 023bbcfc8..be12b0f9f 100644 --- a/docs/editor/Inno.Editor.Annotations.md +++ b/docs/editor/Inno.Editor.Annotations.md @@ -53,68 +53,33 @@ public sealed class Mover : GameBehavior ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Annotations.HeaderAttribute` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.HeaderAttribute.HeaderAttribute(string title, string description = "")`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L38) | Creates a section header with optional explanatory text.
`title`:Visible section title.
`description`:Optional explanatory text displayed when the title is hovered. | — | — | -| [`public string Inno.Editor.Annotations.HeaderAttribute.description { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L55) | Gets the optional explanatory text. | — | — | -| [`public string Inno.Editor.Annotations.HeaderAttribute.title { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L50) | Gets the visible section title. | — | — | -| [`public sealed class Inno.Editor.Annotations.HeaderAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L27) | Starts a visually separated Inspector section before the annotated property. | — | — | - -### `Inno.Editor.Annotations.HelpBoxAttribute` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L438) | Creates an Inspector help message.
`text`:Non-empty message text.
`messageType`:Visual severity. | — | — | -| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, string conditionMember, Inno.Editor.Annotations.InspectorCondition condition, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L461) | Creates an Inspector help message shown when a sibling satisfies a named condition.
`text`:Non-empty message text.
`conditionMember`:Sibling field or property name.
`condition`:Comparison applied to the sibling value.
`messageType`:Visual severity. | — | — | -| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, string conditionMember, object? expectedValue, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L486) | Creates an Inspector help message shown when a sibling equals a compile-time value.
`text`:Non-empty message text.
`conditionMember`:Sibling field or property name.
`expectedValue`:The expected value consumed by help box attribute; ownership remains with the caller unless explicitly stated otherwise.
`messageType`:Visual severity. | — | — | -| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.HelpBoxAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L529) | Gets the optional sibling comparison. | — | — | -| [`public string Inno.Editor.Annotations.HelpBoxAttribute.conditionMember { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L524) | Gets the optional sibling member controlling message visibility. | — | — | -| [`public object? Inno.Editor.Annotations.HelpBoxAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L534) | Gets the optional expected constant. | — | — | -| [`public Inno.Editor.Annotations.InspectorMessageType Inno.Editor.Annotations.HelpBoxAttribute.messageType { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L519) | Gets the visual severity. | — | — | -| [`public string Inno.Editor.Annotations.HelpBoxAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L514) | Gets the message text. | — | — | -| [`public sealed class Inno.Editor.Annotations.HelpBoxAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L427) | Draws a wrapped explanatory message before the annotated Inspector property. | — | — | - -### `Inno.Editor.Annotations.HideIfAttribute` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName, Inno.Editor.Annotations.InspectorCondition condition)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L367) | Creates a named condition that does not require an expected value.
`memberName`:Sibling field or property name.
`condition`:Comparison applied to the sibling value. | — | — | -| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName, object? expectedValue)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L350) | Creates an equality condition.
`memberName`:Sibling field or property name.
`expectedValue`:Compile-time value that hides the property. | — | — | -| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L336) | Creates a truthiness condition.
`memberName`:Sibling field or property name. | — | — | -| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.HideIfAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L394) | Gets the comparison. | — | — | -| [`public object? Inno.Editor.Annotations.HideIfAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L399) | Gets the optional expected constant. | — | — | -| [`public string Inno.Editor.Annotations.HideIfAttribute.memberName { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L389) | Gets the sibling member name. | — | — | -| [`public sealed class Inno.Editor.Annotations.HideIfAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L328) | Hides the annotated Inspector property when a sibling member satisfies a condition. | — | — | - -### `Inno.Editor.Annotations.InspectorCondition` - + + + + + + + + + + + + + + + + + + @@ -124,83 +89,6 @@ public sealed class Mover : GameBehavior - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Annotations.InspectorCondition.Assigned = 6`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L238) | Requires a reference-like value to expose an assigned resource. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.Equal = 2`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L218) | Compares the value with the supplied expected constant. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.Falsy = 1`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L213) | Shows a value when it evaluates as false, null, or empty. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.NotAssigned = 7`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L243) | Requires a reference-like value not to expose an assigned resource. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.NotEqual = 3`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L223) | Requires the value to differ from the supplied expected constant. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.NotNull = 5`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L233) | Requires a non-null value. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.Null = 4`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L228) | Requires a null value. | — | — | -| [`Inno.Editor.Annotations.InspectorCondition.Truthy = 0`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L208) | Shows a value when it evaluates as true or non-empty. | — | — | -| [`public enum Inno.Editor.Annotations.InspectorCondition : int`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L203) | Defines the comparison used by conditional Inspector presentation. | — | — | - -### `Inno.Editor.Annotations.InspectorMessageType` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Annotations.InspectorMessageType.Error = 2`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L420) | An invalid or unusable configuration. | — | — | -| [`Inno.Editor.Annotations.InspectorMessageType.Info = 0`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L410) | Neutral explanatory information. | — | — | -| [`Inno.Editor.Annotations.InspectorMessageType.Warning = 1`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L415) | A non-blocking configuration warning. | — | — | -| [`public enum Inno.Editor.Annotations.InspectorMessageType : int`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L405) | Selects the visual severity of an Inspector help message. | — | — | - -### `Inno.Editor.Annotations.InspectorNameAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.InspectorNameAttribute.InspectorNameAttribute(string name)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L142) | Creates a label override.
`name`:Non-empty visible label. | — | — | -| [`public string Inno.Editor.Annotations.InspectorNameAttribute.name { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L151) | Gets the visible label. | — | — | -| [`public sealed class Inno.Editor.Annotations.InspectorNameAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L134) | Replaces the generated Inspector label for one property. | — | — | - -### `Inno.Editor.Annotations.InspectorPresentationAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Annotations.InspectorPresentationAttribute.InspectorPresentationAttribute()`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L15) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public int Inno.Editor.Annotations.InspectorPresentationAttribute.order { get; set; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L20) | Gets or sets the relative order among presentation attributes declared on the same member. | — | — | -| [`public abstract class Inno.Editor.Annotations.InspectorPresentationAttribute : System.Attribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L15) | Provides editor-only presentation metadata for one serialized field or property. | Presentation attributes never participate in serialization. Concrete attributes are conditional on INNO_EDITOR, so their applications are omitted from Player script assemblies. | — | - -### `Inno.Editor.Annotations.InspectorReadOnlyAttribute` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.InspectorReadOnlyAttribute.InspectorReadOnlyAttribute()`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L196) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public sealed class Inno.Editor.Annotations.InspectorReadOnlyAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L196) | Makes an otherwise writable serialized property read-only in the Inspector. | — | — | - -### `Inno.Editor.Annotations.RangeAttribute` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.RangeAttribute.RangeAttribute(double minimum, double maximum)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L169) | Creates an inclusive numeric range.
`minimum`:Inclusive lower bound.
`maximum`:Inclusive upper bound. | — | — | -| [`public double Inno.Editor.Annotations.RangeAttribute.maximum { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L189) | Gets the inclusive upper bound. | — | — | -| [`public double Inno.Editor.Annotations.RangeAttribute.minimum { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L184) | Gets the inclusive lower bound. | — | — | -| [`public sealed class Inno.Editor.Annotations.RangeAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L158) | Constrains a numeric Inspector control to an inclusive range. | — | — | - -### `Inno.Editor.Annotations.ShowIfAttribute` - @@ -208,54 +96,94 @@ public sealed class Mover : GameBehavior + + + + + + + + + + + + + + + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName, Inno.Editor.Annotations.InspectorCondition condition)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L289) | Creates a named condition that does not require an expected value.
`memberName`:Sibling field or property name.
`condition`:Comparison applied to the sibling value. | — | — | -| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName, object? expectedValue)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L272) | Creates an equality condition.
`memberName`:Sibling field or property name.
`expectedValue`:Compile-time value required for visibility. | — | — | -| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L258) | Creates a truthiness condition.
`memberName`:Sibling field or property name. | — | — | -| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.ShowIfAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L316) | Gets the comparison. | — | — | -| [`public object? Inno.Editor.Annotations.ShowIfAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L321) | Gets the optional expected constant. | — | — | -| [`public string Inno.Editor.Annotations.ShowIfAttribute.memberName { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L311) | Gets the sibling member name. | — | — | -| [`public sealed class Inno.Editor.Annotations.ShowIfAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L250) | Shows the annotated Inspector property only when a sibling member satisfies a condition. | — | — | - -### `Inno.Editor.Annotations.SpaceAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.SpaceAttribute.SpaceAttribute(float height = 8)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L94) | Creates a spacing decorator.
`height`:Non-negative logical pixel height. | — | — | -| [`public float Inno.Editor.Annotations.SpaceAttribute.height { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L103) | Gets the logical pixel height. | — | — | -| [`public sealed class Inno.Editor.Annotations.SpaceAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L86) | Inserts vertical spacing before the annotated Inspector property. | — | — | - -### `Inno.Editor.Annotations.TextAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.TextAttribute.TextAttribute(string text)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L70) | Creates persistent Inspector text.
`text`:Non-empty text displayed in the Inspector layout. | — | — | -| [`public string Inno.Editor.Annotations.TextAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L79) | Gets the persistent Inspector text. | — | — | +| [`public abstract class Inno.Editor.Annotations.InspectorPresentationAttribute : System.Attribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L15) | Provides editor-only presentation metadata for one serialized field or property. | — | — | +| [`public int Inno.Editor.Annotations.InspectorPresentationAttribute.order { get; set; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L20) | Gets or sets the relative order among presentation attributes declared on the same member. | — | — | +| [`protected Inno.Editor.Annotations.InspectorPresentationAttribute.InspectorPresentationAttribute()`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Annotations.HeaderAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L27) | Starts a visually separated Inspector section before the annotated property. | — | — | +| [`public Inno.Editor.Annotations.HeaderAttribute.HeaderAttribute(string title, string description = "")`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L38) | Creates a section header with optional explanatory text.
`title`: Visible section title.
`description`: Optional explanatory text displayed when the title is hovered. | — | — | +| [`public string Inno.Editor.Annotations.HeaderAttribute.title { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L50) | Gets the visible section title. | — | — | +| [`public string Inno.Editor.Annotations.HeaderAttribute.description { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L55) | Gets the optional explanatory text. | — | — | | [`public sealed class Inno.Editor.Annotations.TextAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L62) | Draws persistent wrapped explanatory text before the annotated Inspector property. | — | — | - -### `Inno.Editor.Annotations.TooltipAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Annotations.TooltipAttribute.TooltipAttribute(string text)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L118) | Creates property hover help.
`text`:Non-empty tooltip text. | — | — | -| [`public string Inno.Editor.Annotations.TooltipAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L127) | Gets the tooltip text. | — | — | +| [`public Inno.Editor.Annotations.TextAttribute.TextAttribute(string text)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L70) | Creates persistent Inspector text.
`text`: Non-empty text displayed in the Inspector layout. | — | — | +| [`public string Inno.Editor.Annotations.TextAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L79) | Gets the persistent Inspector text. | — | — | +| [`public sealed class Inno.Editor.Annotations.SpaceAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L86) | Inserts vertical spacing before the annotated Inspector property. | — | — | +| [`public Inno.Editor.Annotations.SpaceAttribute.SpaceAttribute(float height = 8)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L94) | Creates a spacing decorator.
`height`: Non-negative logical pixel height. | — | — | +| [`public float Inno.Editor.Annotations.SpaceAttribute.height { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L103) | Gets the logical pixel height. | — | — | | [`public sealed class Inno.Editor.Annotations.TooltipAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L110) | Provides hover help for one Inspector property label. | — | — | +| [`public Inno.Editor.Annotations.TooltipAttribute.TooltipAttribute(string text)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L118) | Creates property hover help.
`text`: Non-empty tooltip text. | — | — | +| [`public string Inno.Editor.Annotations.TooltipAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L127) | Gets the tooltip text. | — | — | +| [`public sealed class Inno.Editor.Annotations.InspectorNameAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L134) | Replaces the generated Inspector label for one property. | — | — | +| [`public Inno.Editor.Annotations.InspectorNameAttribute.InspectorNameAttribute(string name)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L142) | Creates a label override.
`name`: Non-empty visible label. | — | — | +| [`public string Inno.Editor.Annotations.InspectorNameAttribute.name { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L151) | Gets the visible label. | — | — | +| [`public sealed class Inno.Editor.Annotations.RangeAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L158) | Constrains a numeric Inspector control to an inclusive range. | — | — | +| [`public Inno.Editor.Annotations.RangeAttribute.RangeAttribute(double minimum, double maximum)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L169) | Creates an inclusive numeric range.
`minimum`: Inclusive lower bound.
`maximum`: Inclusive upper bound. | — | — | +| [`public double Inno.Editor.Annotations.RangeAttribute.minimum { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L184) | Gets the inclusive lower bound. | — | — | +| [`public double Inno.Editor.Annotations.RangeAttribute.maximum { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L189) | Gets the inclusive upper bound. | — | — | +| [`public sealed class Inno.Editor.Annotations.InspectorReadOnlyAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L196) | Makes an otherwise writable serialized property read-only in the Inspector. | — | — | +| [`public Inno.Editor.Annotations.InspectorReadOnlyAttribute.InspectorReadOnlyAttribute()`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L196) | Inherited or compiler-provided contract. | — | — | +| [`public enum Inno.Editor.Annotations.InspectorCondition : int`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L203) | Defines the comparison used by conditional Inspector presentation. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.Truthy = 0`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L208) | Shows a value when it evaluates as true or non-empty. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.Falsy = 1`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L213) | Shows a value when it evaluates as false, null, or empty. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.Equal = 2`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L218) | Compares the value with the supplied expected constant. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.NotEqual = 3`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L223) | Requires the value to differ from the supplied expected constant. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.Null = 4`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L228) | Requires a null value. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.NotNull = 5`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L233) | Requires a non-null value. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.Assigned = 6`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L238) | Requires a reference-like value to expose an assigned resource. | — | — | +| [`Inno.Editor.Annotations.InspectorCondition.NotAssigned = 7`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L243) | Requires a reference-like value not to expose an assigned resource. | — | — | +| [`public sealed class Inno.Editor.Annotations.ShowIfAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L250) | Shows the annotated Inspector property only when a sibling member satisfies a condition. | — | — | +| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L258) | Creates a truthiness condition.
`memberName`: Sibling field or property name. | — | — | +| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName, object? expectedValue)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L272) | Creates an equality condition.
`memberName`: Sibling field or property name.
`expectedValue`: Compile-time value required for visibility. | — | — | +| [`public Inno.Editor.Annotations.ShowIfAttribute.ShowIfAttribute(string memberName, Inno.Editor.Annotations.InspectorCondition condition)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L289) | Creates a named condition that does not require an expected value.
`memberName`: Sibling field or property name.
`condition`: Comparison applied to the sibling value. | — | — | +| [`public string Inno.Editor.Annotations.ShowIfAttribute.memberName { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L311) | Gets the sibling member name. | — | — | +| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.ShowIfAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L316) | Gets the comparison. | — | — | +| [`public object? Inno.Editor.Annotations.ShowIfAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L321) | Gets the optional expected constant. | — | — | +| [`public sealed class Inno.Editor.Annotations.HideIfAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L328) | Hides the annotated Inspector property when a sibling member satisfies a condition. | — | — | +| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L336) | Creates a truthiness condition.
`memberName`: Sibling field or property name. | — | — | +| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName, object? expectedValue)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L350) | Creates an equality condition.
`memberName`: Sibling field or property name.
`expectedValue`: Compile-time value that hides the property. | — | — | +| [`public Inno.Editor.Annotations.HideIfAttribute.HideIfAttribute(string memberName, Inno.Editor.Annotations.InspectorCondition condition)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L367) | Creates a named condition that does not require an expected value.
`memberName`: Sibling field or property name.
`condition`: Comparison applied to the sibling value. | — | — | +| [`public string Inno.Editor.Annotations.HideIfAttribute.memberName { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L389) | Gets the sibling member name. | — | — | +| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.HideIfAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L394) | Gets the comparison. | — | — | +| [`public object? Inno.Editor.Annotations.HideIfAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L399) | Gets the optional expected constant. | — | — | +| [`public enum Inno.Editor.Annotations.InspectorMessageType : int`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L405) | Selects the visual severity of an Inspector help message. | — | — | +| [`Inno.Editor.Annotations.InspectorMessageType.Info = 0`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L410) | Neutral explanatory information. | — | — | +| [`Inno.Editor.Annotations.InspectorMessageType.Warning = 1`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L415) | A non-blocking configuration warning. | — | — | +| [`Inno.Editor.Annotations.InspectorMessageType.Error = 2`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L420) | An invalid or unusable configuration. | — | — | +| [`public sealed class Inno.Editor.Annotations.HelpBoxAttribute : Inno.Editor.Annotations.InspectorPresentationAttribute`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L427) | Draws a wrapped explanatory message before the annotated Inspector property. | — | — | +| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L438) | Creates an Inspector help message.
`text`: Non-empty message text.
`messageType`: Visual severity. | — | — | +| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, string conditionMember, Inno.Editor.Annotations.InspectorCondition condition, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L461) | Creates an Inspector help message shown when a sibling satisfies a named condition.
`text`: Non-empty message text.
`conditionMember`: Sibling field or property name.
`condition`: Comparison applied to the sibling value.
`messageType`: Visual severity. | — | — | +| [`public Inno.Editor.Annotations.HelpBoxAttribute.HelpBoxAttribute(string text, string conditionMember, object? expectedValue, Inno.Editor.Annotations.InspectorMessageType messageType = Inno.Editor.Annotations.InspectorMessageType.Info)`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L486) | Creates an Inspector help message shown when a sibling equals a compile-time value.
`text`: Non-empty message text.
`conditionMember`: Sibling field or property name.
`expectedValue`: The expected value consumed by help box attribute; ownership remains with the caller unless explicitly stated otherwise.
`messageType`: Visual severity. | — | — | +| [`public string Inno.Editor.Annotations.HelpBoxAttribute.text { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L514) | Gets the message text. | — | — | +| [`public Inno.Editor.Annotations.InspectorMessageType Inno.Editor.Annotations.HelpBoxAttribute.messageType { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L519) | Gets the visual severity. | — | — | +| [`public string Inno.Editor.Annotations.HelpBoxAttribute.conditionMember { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L524) | Gets the optional sibling member controlling message visibility. | — | — | +| [`public Inno.Editor.Annotations.InspectorCondition Inno.Editor.Annotations.HelpBoxAttribute.condition { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L529) | Gets the optional sibling comparison. | — | — | +| [`public object? Inno.Editor.Annotations.HelpBoxAttribute.expectedValue { get; }`](../../src/composition/editor/contracts/Inno.Editor.Annotations/InspectorPresentationAttributes.cs#L534) | Gets the optional expected constant. | — | — | ## 项目依赖 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Assets.ImGui.md b/docs/editor/Inno.Editor.Assets.ImGui.md new file mode 100644 index 000000000..f1ecc0e5b --- /dev/null +++ b/docs/editor/Inno.Editor.Assets.ImGui.md @@ -0,0 +1,171 @@ +# Inno.Editor.Assets.ImGui + +[Editor 索引](README.md) · [Wiki 首页](../README.md) · [UI 架构](../architecture/EDITOR_UI_ARCHITECTURE.md) + +## 职责与边界 + +Asset 浏览呈现与图标发现。依赖 Assets 编辑 feature、Inspection、Settings 和 ImGui;File Browser 消费此呈现模块。编辑操作、文件归档及 History 不属于此程序集。 + +## 依赖与初始化 + +产品通过 `EditorProduct.props` 显式部署此程序集。由当前 TypeRegistry/Editor Registry 发现声明并在候选成功后切换;不扫描目录、不建立独立注册表或全局状态。 + +标准产品组合 Workbench 的默认外观设置贡献。嵌入 Host 也必须组合所需的图标设置贡献;缺少 `Editor/Appearance/Icons/Folder` 等必需设置时明确失败,不从另一个 owner 隐式补齐配置。 + +## API 与扩展点 + +AssetBrowserState、AssetBrowserRoot、AssetIconAttribute 和 AssetIconRegistry 提供当前浏览位置、来源、图标声明与查询。AssetBrowserModule 组合图标及无界面的 AssetEditingModule;BasicAssetInspectionDrawer 只做显示。 + +`AssetBrowserStyle(EditorStyleMetrics)` 在一次绘制前冻结当前用户 Zoom,不保留 Session,也不再次乘上 DPI。默认值使用 100% Zoom。它包含 Browser 图标、网格、面包屑及列比例;通用 pane/splitter/toolbar 指标仍由共享 metrics 提供。尺度在帧边界更新后,调用方重新取得值快照。 + +## 工作流 + +使用逻辑 namespace `InnoEditor.Assets` 声明 AssetIconAttribute;修改源内容时调用 Assets feature,不能直接写安装包。 + +## 错误、生命周期与热重载 + +控件只在当前 EditorUiSession 的 owner thread、作用域与原生上下文中使用。跨帧只保留稳定 ID 和中立状态;Missing、只读和未赋值状态保持区别。手势失败保留 History 所有权和诊断;扩展在所属 generation 退休,不保留插件 Type、delegate 或实例。 + +所有公开签名的当前映射见本页 API 清单;内部实现不构成稳定扩展契约。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public sealed class Inno.Editor.Assets.ImGui.AssetIconAttribute : System.Attribute`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L14) | Declares the icon used by the Asset Browser for an imported asset type or source extension. | — | — | +| [`public Inno.Editor.Assets.ImGui.AssetIconAttribute.AssetIconAttribute(System.Type assetType, string icon, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L37) | Creates an icon declaration using a glyph from the Editor icon catalog.
`assetType`: The imported asset type represented by the icon.
`icon`: The Editor icon glyph to render.
`useForChildren`: Whether the declaration may also represent derived asset types.
`priority`: The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Assets.ImGui.AssetIconAttribute.AssetIconAttribute(string extension, string icon, int priority = 0)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L65) | Creates an icon declaration for files ending with a source extension.
`extension`: The simple or compound file extension. A leading period is optional and matching is case-insensitive.
`icon`: The Editor icon glyph to render.
`priority`: The tie-breaking priority after extension specificity. | — | `System.ArgumentException`: Thrown when is empty or contains a path separator, or when is empty. | +| [`public System.Type? Inno.Editor.Assets.ImGui.AssetIconAttribute.assetType { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L79) | Gets the imported asset type represented by this declaration, or for an extension declaration. | — | — | +| [`public string? Inno.Editor.Assets.ImGui.AssetIconAttribute.extension { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L84) | Gets the normalized source extension represented by this declaration, or for a type declaration. | — | — | +| [`public string Inno.Editor.Assets.ImGui.AssetIconAttribute.icon { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L88) | Gets the Editor icon glyph to render. | — | — | +| [`public bool Inno.Editor.Assets.ImGui.AssetIconAttribute.useForChildren { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L93) | Gets whether a type declaration may also represent derived asset types. Extension declarations always return . | — | — | +| [`public int Inno.Editor.Assets.ImGui.AssetIconAttribute.priority { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs#L97) | Gets the tie-breaking priority after target specificity. | — | — | +| [`public sealed class Inno.Editor.Assets.ImGui.AssetBrowserModule : Inno.Editor.Core.EditorModule, Inno.Editor.Inspection.IInspectionIconProvider`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L17) | Owns asset navigation and icon presentation independently from source mutations. | — | — | +| [`public Inno.Editor.Assets.ImGui.AssetBrowserModule.AssetBrowserModule(Inno.Editor.Assets.AssetEditingModule edits, Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Settings.EditorSettings settings, Inno.Extensibility.Types.TypeCatalog types)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L40) | Creates the asset presentation module for the current editor generation.
`edits`: The independent authoritative asset editing service.
`interactions`: The editor selection and action owner.
`settings`: The editor settings that supply semantic icon choices.
`types`: The active extension type catalog. | — | `System.ArgumentNullException`: A required owner is null. | +| [`public Inno.Editor.Assets.AssetEditingModule Inno.Editor.Assets.ImGui.AssetBrowserModule.edits { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L55) | Gets the source editing service; this module does not implement source transactions. | — | — | +| [`public Inno.Editor.Assets.ImGui.AssetBrowserState Inno.Editor.Assets.ImGui.AssetBrowserModule.browser { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L59) | Gets navigation state; object selection belongs to the shared interaction owner. | — | — | +| [`public string Inno.Editor.Assets.ImGui.AssetBrowserModule.folderIcon { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L63) | Gets the configured folder glyph used by asset navigation. | — | — | +| [`protected override void Inno.Editor.Assets.ImGui.AssetBrowserModule.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L71) | Captures an immutable snapshot of the current observable state.
`state`: The lifecycle or domain state applied by this operation. | — | — | +| [`protected override void Inno.Editor.Assets.ImGui.AssetBrowserModule.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L84) | Restores the supplied snapshot while preserving current invariants.
`state`: The lifecycle or domain state applied by this operation. | — | — | +| [`public string Inno.Editor.Assets.ImGui.AssetBrowserModule.GetIcon(Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L127) | Resolves the presentation icon registered for an asset type or source extension.
`entry`: The source entry whose presentation icon should be resolved. | The most specific registered icon, or the built-in directory or file icon when no registration matches the entry. | `System.ArgumentNullException`: Thrown when is . | +| [`protected override void Inno.Editor.Assets.ImGui.AssetBrowserModule.OnDispose()`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs#L142) | Releases resources owned by this module after it has stopped and left the active extension generation. | — | — | +| [`public enum Inno.Editor.Assets.ImGui.AssetBrowserRoot : int`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L14) | Identifies the authoring or installed-content root displayed by the Asset Browser. | — | — | +| [`Inno.Editor.Assets.ImGui.AssetBrowserRoot.Assets = 0`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L19) | The writable project Assets authoring root. | — | — | +| [`Inno.Editor.Assets.ImGui.AssetBrowserRoot.Plugins = 1`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L23) | The read-only Plugins installation root. | — | — | +| [`public sealed class Inno.Editor.Assets.ImGui.AssetBrowserState`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L29) | Stores asset browser navigation independently from global object selection. | — | — | +| [`public Inno.Editor.Assets.ImGui.AssetBrowserRoot Inno.Editor.Assets.ImGui.AssetBrowserState.root { get; private set; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L58) | Gets the root currently displayed by the Asset Browser. | — | — | +| [`public string Inno.Editor.Assets.ImGui.AssetBrowserState.currentDirectory { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L62) | Gets the current directory inside . An empty value identifies that root's overview. | — | — | +| [`public string Inno.Editor.Assets.ImGui.AssetBrowserState.projectDirectory { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L66) | Gets the most recently visited writable project directory, independently of the displayed root. | — | — | +| [`public string? Inno.Editor.Assets.ImGui.AssetBrowserState.GetSelectedPath(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L80) | Gets the selected asset path when the editor-wide target belongs to the Asset Browser.
`context`: The shared editor context containing the global selection. | The normalized source-relative path, or when another target type is selected. | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.Assets.ImGui.AssetBrowserState.IsSelected(Inno.Editor.Core.EditorContext context, Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L98) | Checks whether the source entry belongs to the current asset selection.
`context`: The current editor callback context.
`entry`: The indexed source identity to inspect. | True when the entry is selected; otherwise false. | — | +| [`public void Inno.Editor.Assets.ImGui.AssetBrowserState.ToggleSelection(Inno.Editor.Core.EditorContext context, Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L111) | Adds or removes an asset from the current selection without retaining runtime asset objects.
`context`: The current editor callback context.
`entry`: The indexed source identity to toggle. | — | — | +| [`public void Inno.Editor.Assets.ImGui.AssetBrowserState.SetRoot(Inno.Editor.Assets.ImGui.AssetBrowserRoot value)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L124) | Switches the displayed root while preserving the last directory visited in each root.
`value`: The authoring or installed-content root to display. | — | — | +| [`public void Inno.Editor.Assets.ImGui.AssetBrowserState.SetCurrentDirectory(string relativePath)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L131) | Sets the current Asset Browser directory and infers its root from the isolated source identity.
`relativePath`: The isolated source-relative directory path, or an empty value for the currently displayed root. | — | — | +| [`public void Inno.Editor.Assets.ImGui.AssetBrowserState.Select(Inno.Editor.Core.EditorContext context, string? relativePath)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L158) | Selects an asset path through the editor-wide selection state, or clears Asset selection.
`context`: The shared editor context whose selection should be updated.
`relativePath`: The source-relative path to select, or an empty value to clear selection. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public string Inno.Editor.Assets.ImGui.AssetBrowserState.GetDirectory(Inno.Editor.Assets.ImGui.AssetBrowserRoot targetRoot)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L184) | Gets the last visited isolated directory for a navigation root.
`targetRoot`: The root whose independent navigation position is requested. | The source-relative directory, or an empty string for the root. | — | +| [`public void Inno.Editor.Assets.ImGui.AssetBrowserState.SetLocation(Inno.Editor.Assets.ImGui.AssetBrowserRoot targetRoot, string directory)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs#L207) | Changes the displayed source root and its isolated navigation directory.
`targetRoot`: The navigation root that owns the requested directory.
`directory`: The isolated source-relative directory; empty selects the root. | — | `System.ArgumentException`: The directory belongs to a different source root. | +| [`public readonly struct Inno.Editor.Assets.ImGui.AssetBrowserStyle`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L11) | Freezes Browser-specific layout values from the shared UI scale without retaining a session. The default value uses the standard 100 percent scale. | — | — | +| [`public Inno.Editor.Assets.ImGui.AssetBrowserStyle.AssetBrowserStyle(Inno.Editor.ImGui.EditorStyleMetrics metrics)`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L24) | Captures the current shared scale for one Browser presentation pass.
`metrics`: The existing session metrics; later mutations do not change this value. | — | `System.ArgumentNullException`: The metrics are null. | +| [`public System.Numerics.Vector2 Inno.Editor.Assets.ImGui.AssetBrowserStyle.windowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L33) | Gets asset browser window padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Assets.ImGui.AssetBrowserStyle.cellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L38) | Gets asset browser cell padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Assets.ImGui.AssetBrowserStyle.itemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L43) | Gets asset browser item spacing. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.frameRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L48) | Gets asset browser frame rounding. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.breadcrumbHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L53) | Gets asset breadcrumb bar height. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridMinimumCellSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L58) | Gets minimum asset grid cell size. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridCellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L63) | Gets asset grid cell padding. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridDefaultScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L68) | Gets default asset grid scale. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridMinimumScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L73) | Gets minimum asset grid scale. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridMaximumScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L78) | Gets maximum asset grid scale. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listRowSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L83) | Gets spacing between asset list rows. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listNameSeparatorPosition { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L88) | Gets the default normalized position of the asset list name/type separator. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listTypeSeparatorPosition { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L93) | Gets the default normalized position of the asset list type/source separator. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listMinimumColumnRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L98) | Gets the minimum normalized width reserved for each asset list column. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listSeparatorHitWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L103) | Gets the horizontal hit width of an asset list column separator. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.listContentHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L108) | Gets the horizontal inset between an asset list separator and column content. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridLabelHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L113) | Gets horizontal padding for asset grid labels. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridLabelBottomPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L118) | Gets bottom padding for asset grid labels. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridLabelLineSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L123) | Gets the additional vertical spacing between asset grid label lines. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridFixedCellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L128) | Gets fixed padding added to calculated asset grid cells. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridScaleBias { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L133) | Gets the scale bias added to asset grid icons. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridIconTopPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L138) | Gets the top inset reserved above an asset grid icon. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridIconHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L143) | Gets the horizontal inset that constrains an asset grid icon inside its card. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.gridIconLabelSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L148) | Gets the vertical spacing between an asset grid icon and its label. | — | — | +| [`public float Inno.Editor.Assets.ImGui.AssetBrowserStyle.breadcrumbSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs#L153) | Gets spacing around breadcrumb separators. | — | — | + +## 项目依赖 + +| 工程 | 边界 | +| --- | --- | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Editor.Assets` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Inspection` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.Editor.Settings` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Assets.md b/docs/editor/Inno.Editor.Assets.md index 315e4ce0f..8d22acb06 100644 --- a/docs/editor/Inno.Editor.Assets.md +++ b/docs/editor/Inno.Editor.Assets.md @@ -10,7 +10,7 @@ ## Asset 创建扩展 -`AssetCreationTemplate` 的派生关系是发现协议;`AssetCreationMenuAttribute` 只携带无法从 `AssetObject` 推导的稳定模板 ID、菜单路径、扩展名、默认文件名和排序。File Browser Registry 构造并持有全部模板,因此模板本身不重复声明实例 ID。一个插件新增资产类型时不需要修改 File Browser 中央分支: +`AssetCreationTemplate` 的派生关系是发现协议;`AssetCreationMenuAttribute` 只携带无法从 `AssetObject` 推导的稳定模板 ID、菜单路径、扩展名、默认文件名和排序。Assets feature Registry 构造并持有全部模板,因此模板本身不重复声明实例 ID。一个插件新增资产类型时不需要修改 File Browser 中央分支: ```csharp using InnoEditor.Assets; @@ -50,9 +50,9 @@ Feature 必须提供相同 historyKind 的 EditorHistoryHandler;不能保存 | `Start()` | 注册共享文档 Provider;重复启动明确失败 | | `Open(path)` | 打开来源与身份,不要求导入成功;不显示第二个 Inspector | | `Read(assetId)` | 解码独立值;引用的 canonical 资产仍是只读输入 | -| `Replace(assetId, candidate, finishGesture)` | 修改草稿;手势结束记录一次 History,不写源 | +| `Replace(assetId, candidate, mergeKey)` | 修改草稿;手势结束记录一次 History,不写源 | | `Commit(assetId)` | 结束手势,不保存 | -| `ReplaceMany(candidates, finishGesture)` / `CommitMany(ids)` | 多文档共用一次 History 事务;不是跨文件原子保存 | +| `ReplaceMany(candidates, mergeKey)` / `ReplaceMany(ids)` | 多文档共用一次 History 事务;不是跨文件原子保存 | | `GetDraft(assetId)` | 获取不含 Asset/Type/delegate 的中立状态对象 | | `TouchInspection(assetId)` | 标记当前帧仍参与编辑;离开 Inspector 后完成遗留手势 | | `ValidateHistory(change, direction)` / `ApplyHistory(change, direction)` | Feature 的注册 Handler 路由公共 History 协议,不独立移动历史栈 | @@ -103,23 +103,19 @@ Store 不保留插件资产实例、设置实例或 Type;消费者在每次绘 模板使用默认 `Encode`,经所属 source 的完整 SerializationContext 保存结构化资产。对应扩展还须提供使用同一格式的 importer;天然文本源才应覆盖编码,不能用自定义文本或 JSON 旁路保存结构化资产。 -## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 -### `Inno.Editor.Assets.AssetCreationContext` +## 共享编辑入口 - - - +AssetEditingModule 拥有创建、删除、重命名、移动、只读校验与 source History;AssetImportSettingsEdits 拥有导入设置;AssetSampleImportModule 复用正式 Sample import。File Browser 只发出交互。 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Assets.AssetCreationContext.AssetCreationContext(Inno.Assets.Pipeline.AssetSourceStore sources)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L180) | Creates a context over the source serializer owned by the active Asset pipeline.
`sources`:Source serializer carrying the active reference context. | — | — | -| [`public byte[] Inno.Editor.Assets.AssetCreationContext.EncodeNative(Inno.Assets.AssetObject asset)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L191) | Encodes an Asset value through the current native structured source serializer.
返回:Complete native source bytes, including the concrete serialized type identity.
`asset`:Detached Asset value to encode. | — | — | -| [`public sealed class Inno.Editor.Assets.AssetCreationContext`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L169) | Provides source encoding services to one detached Asset creation template invocation. | — | — | +AssetDraftDocuments 的 Replace/ReplaceMany 立即记录中立 bytes,不延迟到下一帧或失焦。连续输入由 EditorEditGesture 开启事务;mergeKey 只标识当前逻辑值手势。Cancel 通过同一个 History 回滚。Save 不再承担补记 History 的职责。 -### `Inno.Editor.Assets.AssetCreationMenuAttribute` +批量删除和移动先验证整个选择,再将被选父目录覆盖的子条目归一化,最后用现有 History transaction 原子执行。后续失败自动恢复先前步骤。安装源始终只读。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -130,89 +126,32 @@ Store 不保留插件资产实例、设置实例或 Type;消费者在每次绘 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Assets.AssetCreationMenuAttribute.AssetCreationMenuAttribute(string id, string menuPath, string extension, string defaultName, int groupOrder = 0, int itemOrder = 0, bool separatorBeforeGroup = false)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L42) | Creates one asset creation menu declaration.
`id`:Globally stable template identifier.
`menuPath`:Slash-delimited path below the File Browser Create menu.
`extension`:Native source extension, including its leading dot.
`defaultName`:Default source name without an extension or numeric suffix.
`groupOrder`:Stable order of the top-level category below Create.
`itemOrder`:Stable order of the leaf within its immediate category.
`separatorBeforeGroup`:Whether the top-level category starts after a separator. | — | `System.ArgumentException`:Thrown when an identifier, path, source name, or native extension is invalid. | -| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.defaultName { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L99) | Gets the default source name without an extension or numeric suffix. | — | — | -| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.extension { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L94) | Gets the native source extension, including its leading dot. | — | — | -| [`public int Inno.Editor.Assets.AssetCreationMenuAttribute.groupOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L104) | Gets the stable order of the top-level category below Create. | — | — | -| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L84) | Gets the globally stable template identifier. | — | — | -| [`public int Inno.Editor.Assets.AssetCreationMenuAttribute.itemOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L109) | Gets the stable order of the leaf within its immediate category. | — | — | -| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.menuPath { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L89) | Gets the slash-delimited path below the File Browser Create menu. | — | — | -| [`public bool Inno.Editor.Assets.AssetCreationMenuAttribute.separatorBeforeGroup { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L114) | Gets whether the top-level category starts after a separator. | — | — | -| [`public sealed class Inno.Editor.Assets.AssetCreationMenuAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L13) | Declares the source name, extension, and menu placement that cannot be inferred from an Inno.Assets.AssetObject type alone. | — | — | - -### `Inno.Editor.Assets.AssetCreationTemplate` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Assets.AssetCreationTemplate.AssetCreationTemplate()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L124) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract Inno.Assets.AssetObject Inno.Editor.Assets.AssetCreationTemplate.Create()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L137) | Creates a detached default value that has not been imported or published.
返回:A new asset value owned by the caller. | — | — | -| [`public virtual byte[] Inno.Editor.Assets.AssetCreationTemplate.Encode(Inno.Editor.Assets.AssetCreationContext context, Inno.Assets.AssetObject asset)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L156) | Encodes the detached initial value into the source representation owned by this template.
返回:Complete source bytes ready for an atomic File Browser create operation.
`context`:Creation services scoped to the current Asset source pipeline.
`asset`:The exact value returned by Inno.Editor.Assets.AssetCreationTemplate.Create. | The default implementation uses native structured Asset serialization. Templates paired with a textual or custom binary importer can override this method without changing the File Browser or registering a second creation protocol. | — | -| [`public abstract System.Type Inno.Editor.Assets.AssetCreationTemplate.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L129) | Gets the concrete asset type produced by this template. | — | — | -| [`public abstract class Inno.Editor.Assets.AssetCreationTemplate`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L124) | Produces the detached initial value for one authorable native asset source. | Derivation is the discovery contract. Inno.Editor.Assets.AssetCreationMenuAttribute supplies only source-format and presentation data that the asset inheritance hierarchy cannot express. | — | - -### `Inno.Editor.Assets.AssetCreationTemplate`1` - + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Assets.AssetCreationTemplate.AssetCreationTemplate()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L204) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public override Inno.Assets.AssetObject Inno.Editor.Assets.AssetCreationTemplate.Create()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L218) | Creates a value using this implementation's validated inputs.
返回:The validated asset object that represents the completed operation. | — | — | -| [`protected virtual TAsset Inno.Editor.Assets.AssetCreationTemplate.CreateAsset()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L226) | Creates the initial detached value, allowing specialized deterministic defaults.
返回:A new asset value owned by the caller. | — | — | -| [`public override sealed System.Type Inno.Editor.Assets.AssetCreationTemplate.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L210) | Gets the asset type created by this Editor template. | — | — | -| [`public abstract class Inno.Editor.Assets.AssetCreationTemplate where TAsset : Inno.Assets.AssetObject, new() : Inno.Editor.Assets.AssetCreationTemplate`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L204) | Creates a default-constructed native source for an authorable asset type. | — | — | - -### `Inno.Editor.Assets.AssetDraftDocuments`1` - - - - - - + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Assets.AssetDraftDocuments.AssetDraftDocuments(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Editor.Interactions.EditorInteractions interactions, string providerId, string historyKind, string extension, string label)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L61) | Creates a feature-owned draft store; call Start after document services become available.
`assets`:Owner of source identities, serialization and import.
`serialization`:Host registry for neutral History and recovery records.
`interactions`:Shared documents and History services.
`providerId`:Stable unique document provider identifier.
`historyKind`:Stable protocol handled by the feature's registered History handler.
`extension`:Native source extension including its leading dot.
`label`:Human-readable singular asset kind. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.ApplyHistory(Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L280) | Applies a previously validated neutral record; failed recovery leaves the draft unchanged.
`change`:Record owned by the shared History service.
`direction`:Requested traversal direction. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.Commit(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L158) | Finishes the active edit gesture without saving or applying its draft.
`assetId`:Open asset identity. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.CommitMany(System.Collections.Generic.IEnumerable assetIds)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L204) | Commits all selected draft samples in a single shared history transaction.
`assetIds`:Open asset identities belonging to this gesture. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.Dispose()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L481) | Preserves unsaved recovery and unregisters the current feature provider. | — | — | -| [`public Inno.Editor.Assets.AssetDraftDocuments.Draft Inno.Editor.Assets.AssetDraftDocuments.GetDraft(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L94) | Gets host-owned neutral presentation state for an open document.
返回:Read-only presentation state; it contains no live asset or extension objects.
`assetId`:Persistent source identity. | — | — | -| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L113) | Opens a native asset source in the shared document service without revealing a second Inspector.
返回:The persistent asset identity used for subsequent draft operations.
`path`:Source path, including sources with failed imports. | — | — | -| [`public TAsset Inno.Editor.Assets.AssetDraftDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L132) | Reads a detached current-generation value; callers must never mutate its referenced canonical assets.
返回:A detached editable asset whose changes have not been published.
`assetId`:Open asset identity. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.Replace(System.Guid assetId, TAsset candidate, bool finishGesture = true)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L146) | Updates the open draft through shared History without saving or publishing a runtime asset.
`assetId`:Open asset identity.
`candidate`:Detached edited asset.
`finishGesture`:True completes one history transaction; false continues the same active gesture. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.ReplaceMany(System.Collections.Generic.IReadOnlyDictionary candidates, bool finishGesture = true)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L169) | Edits compatible selected drafts as one gesture without saving any source.
`candidates`:Detached candidate assets indexed by their open asset identities.
`finishGesture`:Whether this sample completes the shared gesture. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.Start()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L407) | Registers the feature provider with the headless document ownership service. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.TouchInspection(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L102) | Marks a document as inspected in this frame so a lost gesture can be completed.
`assetId`:Persistent source identity. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.Update()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L417) | Completes abandoned gestures, imports explicit saves and reconciles source changes. | — | — | -| [`public void Inno.Editor.Assets.AssetDraftDocuments.ValidateHistory(Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L264) | Validates a feature's neutral History record without changing the draft.
`change`:Record owned by the shared History service.
`direction`:Requested traversal direction. | — | — | -| [`public sealed class Inno.Editor.Assets.AssetDraftDocuments where TAsset : Inno.Assets.AssetObject : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L20) | Owns detached native asset drafts with shared document, History, recovery and explicit-save semantics. | — | — | - -### `Inno.Editor.Assets.AssetDraftDocuments`1.Draft` - @@ -220,26 +159,221 @@ Store 不保留插件资产实例、设置实例或 Type;消费者在每次绘 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Draft.documentId { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L595) | Gets the shared document identity used for Save/Revert and close confirmation. | — | — | -| [`public string Inno.Editor.Assets.AssetDraftDocuments.Draft.error { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L607) | Gets the latest source, save or import diagnostic. | — | — | -| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Draft.id { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L591) | Gets the persistent source identity. | — | — | -| [`public bool Inno.Editor.Assets.AssetDraftDocuments.Draft.isDirty { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L611) | Gets whether detached bytes differ from the saved baseline. | — | — | -| [`public Inno.Assets.AssetPath Inno.Editor.Assets.AssetDraftDocuments.Draft.path { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L599) | Gets the current resolved source path. | — | — | -| [`public bool Inno.Editor.Assets.AssetDraftDocuments.Draft.readOnly { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L603) | Gets whether this source is installed read-only content. | — | — | -| [`public sealed class Inno.Editor.Assets.AssetDraftDocuments.Draft`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L564) | Contains neutral draft status; only the owning store can change it. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetCreationMenuAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L13) | Declares the source name, extension, and menu placement that cannot be inferred from an type alone. | — | — | +| [`public Inno.Editor.Assets.AssetCreationMenuAttribute.AssetCreationMenuAttribute(string id, string menuPath, string extension, string defaultName, int groupOrder = 0, int itemOrder = 0, bool separatorBeforeGroup = false)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L42) | Creates one asset creation menu declaration.
`id`: Globally stable template identifier.
`menuPath`: Slash-delimited path below the File Browser Create menu.
`extension`: Native source extension, including its leading dot.
`defaultName`: Default source name without an extension or numeric suffix.
`groupOrder`: Stable order of the top-level category below Create.
`itemOrder`: Stable order of the leaf within its immediate category.
`separatorBeforeGroup`: Whether the top-level category starts after a separator. | — | `System.ArgumentException`: Thrown when an identifier, path, source name, or native extension is invalid. | +| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L84) | Gets the globally stable template identifier. | — | — | +| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.menuPath { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L89) | Gets the slash-delimited path below the File Browser Create menu. | — | — | +| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.extension { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L94) | Gets the native source extension, including its leading dot. | — | — | +| [`public string Inno.Editor.Assets.AssetCreationMenuAttribute.defaultName { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L99) | Gets the default source name without an extension or numeric suffix. | — | — | +| [`public int Inno.Editor.Assets.AssetCreationMenuAttribute.groupOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L104) | Gets the stable order of the top-level category below Create. | — | — | +| [`public int Inno.Editor.Assets.AssetCreationMenuAttribute.itemOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L109) | Gets the stable order of the leaf within its immediate category. | — | — | +| [`public bool Inno.Editor.Assets.AssetCreationMenuAttribute.separatorBeforeGroup { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L114) | Gets whether the top-level category starts after a separator. | — | — | +| [`public abstract class Inno.Editor.Assets.AssetCreationTemplate`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L124) | Produces the detached initial value for one authorable native asset source. | — | — | +| [`public abstract System.Type Inno.Editor.Assets.AssetCreationTemplate.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L129) | Gets the concrete asset type produced by this template. | — | — | +| [`public abstract Inno.Assets.AssetObject Inno.Editor.Assets.AssetCreationTemplate.Create()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L137) | Creates a detached default value that has not been imported or published. | A new asset value owned by the caller. | — | +| [`public virtual byte[] Inno.Editor.Assets.AssetCreationTemplate.Encode(Inno.Editor.Assets.AssetCreationContext context, Inno.Assets.AssetObject asset)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L156) | Encodes the detached initial value into the source representation owned by this template.
`context`: Creation services scoped to the current Asset source pipeline.
`asset`: The exact value returned by . | Complete source bytes ready for an atomic File Browser create operation. | — | +| [`protected Inno.Editor.Assets.AssetCreationTemplate.AssetCreationTemplate()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L124) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetCreationContext`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L169) | Provides source encoding services to one detached Asset creation template invocation. | — | — | +| [`public Inno.Editor.Assets.AssetCreationContext.AssetCreationContext(Inno.Assets.Pipeline.AssetSourceStore sources)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L180) | Creates a context over the source serializer owned by the active Asset pipeline.
`sources`: Source serializer carrying the active reference context. | — | — | +| [`public byte[] Inno.Editor.Assets.AssetCreationContext.EncodeNative(Inno.Assets.AssetObject asset)`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L191) | Encodes an Asset value through the current native structured source serializer.
`asset`: Detached Asset value to encode. | Complete native source bytes, including the concrete serialized type identity. | — | +| [`public abstract class Inno.Editor.Assets.AssetCreationTemplate where TAsset : Inno.Assets.AssetObject, new() : Inno.Editor.Assets.AssetCreationTemplate`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L204) | Creates a default-constructed native source for an authorable asset type.
`TAsset`: Concrete serializable asset type. | — | — | +| [`public override sealed System.Type Inno.Editor.Assets.AssetCreationTemplate.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L210) | Gets the asset type created by this Editor template. | — | — | +| [`public override Inno.Assets.AssetObject Inno.Editor.Assets.AssetCreationTemplate.Create()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L218) | Creates a value using this implementation's validated inputs. | The validated asset object that represents the completed operation. | — | +| [`protected virtual TAsset Inno.Editor.Assets.AssetCreationTemplate.CreateAsset()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L226) | Creates the initial detached value, allowing specialized deterministic defaults. | A new asset value owned by the caller. | — | +| [`protected Inno.Editor.Assets.AssetCreationTemplate.AssetCreationTemplate()`](../../src/composition/editor/features/Inno.Editor.Assets/Creation/AssetCreationTemplate.cs#L204) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetDraftDocuments where TAsset : Inno.Assets.AssetObject : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L19) | Owns detached native asset drafts with shared document, History, recovery and explicit-save semantics.
`TAsset`: Native source type edited by the consuming feature. | — | — | +| [`public Inno.Editor.Assets.AssetDraftDocuments.AssetDraftDocuments(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Editor.Interactions.EditorInteractions interactions, string providerId, string historyKind, string extension, string label)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L57) | Creates a feature-owned draft store; call Start after document services become available.
`assets`: Owner of source identities, serialization and import.
`serialization`: Host registry for neutral History and recovery records.
`interactions`: Shared documents and History services.
`providerId`: Stable unique document provider identifier.
`historyKind`: Stable protocol handled by the feature's registered History handler.
`extension`: Native source extension including its leading dot.
`label`: Human-readable singular asset kind. | — | — | +| [`public Inno.Editor.Assets.AssetDraftDocuments.Draft Inno.Editor.Assets.AssetDraftDocuments.GetDraft(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L95) | Gets host-owned neutral presentation state for an open document.
`assetId`: Persistent source identity. | Read-only presentation state; it contains no live asset or extension objects. | — | +| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L105) | Opens a native asset source in the shared document service without revealing a second Inspector.
`path`: Source path, including sources with failed imports. | The persistent asset identity used for subsequent draft operations. | — | +| [`public TAsset Inno.Editor.Assets.AssetDraftDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L124) | Reads a detached current-generation value; callers must never mutate its referenced canonical assets.
`assetId`: Open asset identity. | A detached editable asset whose changes have not been published. | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.Replace(System.Guid assetId, TAsset candidate, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L137) | Updates the open draft through shared History without saving or publishing a runtime asset.
`assetId`: Open asset identity.
`candidate`: Detached edited asset.
`mergeKey`: The current gesture key, or null for an independent discrete edit. | — | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.ReplaceMany(System.Collections.Generic.IReadOnlyDictionary candidates, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L157) | Records a fully validated selection sample atomically in the shared History.
`candidates`: Detached candidates keyed by their persistent source identity.
`mergeKey`: The owning gesture key, or null for a discrete edit. | — | — | +| [`public bool Inno.Editor.Assets.AssetDraftDocuments.TryMergeHistory(Inno.Editor.Interactions.EditorHistoryChange older, Inno.Editor.Interactions.EditorHistoryChange newer, out Inno.Editor.Interactions.EditorHistoryChange? merged)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L221) | Combines adjacent samples of the same document and gesture without retaining live assets.
`older`: The preceding owned record.
`newer`: The subsequent owned record.
`merged`: Receives a new independent payload when the records form a contiguous edit. | True when both records describe the same continuous value change. | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.ValidateHistory(Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L252) | Validates a feature's neutral History record without changing the draft.
`change`: Record owned by the shared History service.
`direction`: Requested traversal direction. | — | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.ApplyHistory(Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L270) | Applies a previously validated neutral record; failed recovery leaves the draft unchanged.
`change`: Record owned by the shared History service.
`direction`: Requested traversal direction. | — | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.Start()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L402) | Registers the feature provider with the headless document ownership service. | — | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.Update()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L412) | Imports explicit saves and reconciles source changes without owning UI gesture lifetimes. | — | — | +| [`public void Inno.Editor.Assets.AssetDraftDocuments.Dispose()`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L478) | Preserves unsaved recovery and unregisters the current feature provider. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetDraftDocuments.Draft`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L560) | Contains neutral draft status; only the owning store can change it. | — | — | +| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Draft.id { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L586) | Gets the persistent source identity. | — | — | +| [`public System.Guid Inno.Editor.Assets.AssetDraftDocuments.Draft.documentId { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L590) | Gets the shared document identity used for Save/Revert and close confirmation. | — | — | +| [`public Inno.Assets.AssetPath Inno.Editor.Assets.AssetDraftDocuments.Draft.path { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L594) | Gets the current resolved source path. | — | — | +| [`public bool Inno.Editor.Assets.AssetDraftDocuments.Draft.readOnly { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L598) | Gets whether this source is installed read-only content. | — | — | +| [`public string Inno.Editor.Assets.AssetDraftDocuments.Draft.error { get; internal set; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L602) | Gets the latest source, save or import diagnostic. | — | — | +| [`public bool Inno.Editor.Assets.AssetDraftDocuments.Draft.isDirty { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs#L606) | Gets whether detached bytes differ from the saved baseline. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetEditingModule : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs#L13) | Handles asset creation actions in the Editor file browser. | — | — | +| [`public Inno.Assets.Pipeline.AssetFileEntry Inno.Editor.Assets.AssetEditingModule.CreateDirectory(Inno.Assets.AssetPath parent)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs#L27) | Creates a uniquely named directory and records its removal and restoration in shared History.
`parent`: An existing writable source directory. | The indexed identity of the newly created directory. | `System.InvalidOperationException`: The source is read-only, unavailable, or cannot retain the History entry. | +| [`public Inno.Assets.Pipeline.AssetFileEntry Inno.Editor.Assets.AssetEditingModule.CreateSource(Inno.Assets.AssetPath path, System.ReadOnlySpan bytes)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs#L75) | Creates a native or ordinary-text asset source as one recoverable shared-history operation.
`path`: A new file in an existing writable asset directory.
`bytes`: Complete source bytes supplied by the feature's native writer. | The indexed source identity, even when its first import reports a source error. | `System.IO.IOException`: The source already exists, has no existing parent, or cannot be installed.
`System.InvalidOperationException`: The source mount is unavailable/read-only or history cannot retain the change. | +| [`public Inno.Editor.Assets.AssetEditingModule.AssetEditingModule(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Assets.Pipeline.AssetPipeline pipeline, Inno.Plugins.Authoring.PluginEnvironment plugins, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L50) | Creates the asset editing service for one authoring generation.
`interactions`: The active editor interaction entry point.
`pipeline`: The isolated authoring asset pipeline used by browser operations.
`plugins`: The active Plugin catalog that authoritatively identifies Plugin-owned source mounts.
`types`: The active type catalog used to build extension registry snapshots.
`logs`: The application log router used for Asset Browser operation diagnostics. | — | `System.ArgumentNullException`: Thrown when any required service is . | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Assets.AssetEditingModule.interactions { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L71) | Gets the editor interaction owner used by source operations. | — | — | +| [`public Inno.Assets.Pipeline.AssetPipeline Inno.Editor.Assets.AssetEditingModule.pipeline { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L77) | Gets the authoritative authoring pipeline; mutations must use its existing ownership protocol. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Assets.AssetEditingModule.creationTemplates { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L81) | Gets the current immutable creation menu declarations without retaining template instances. | — | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.CanCreateAsset(string directory, string templateId)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L96) | Checks that a creation template and writable destination are available.
`directory`: The source-relative writable destination directory.
`templateId`: The stable identity of a discovered creation template. | True when the request is accepted; otherwise false. | — | +| [`public Inno.Assets.Pipeline.AssetFileEntry Inno.Editor.Assets.AssetEditingModule.CreateAsset(string directory, string templateId)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L120) | Creates and imports a source using a discovered template and records the committed change.
`directory`: The source-relative writable destination directory.
`templateId`: The stable identity of a discovered creation template. | The committed source identity, or a validation result describing why the operation is unavailable. | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.IsPluginSource(Inno.Assets.AssetSourceId source)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L170) | Determines whether the active Plugin catalog owns a source mount.
`source`: The source mount identity to query. | True when the request is accepted; otherwise false. | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.IsPluginRoot(Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L181) | Determines whether an indexed entry is the root of an installed Plugin.
`entry`: The currently indexed source identity. | True when the request is accepted; otherwise false. | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.TryCreateContext(Inno.Editor.Core.EditorContext editor, string relativePath, out Inno.Editor.Assets.AssetEditorContext? context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L203) | Captures the currently indexed source and extension metadata for one immediate operation.
`editor`: The current editor context used only during this invocation.
`relativePath`: The isolated source-relative path to resolve.
`context`: Receives the current source snapshot, or null when the source is unavailable. | True when the request is accepted; otherwise false. | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.Open(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L230) | Opens a source using its current asset editor extension and reports rejected or failed opens.
`context`: The immutable metadata captured for this immediate source operation. | True when the request is accepted; otherwise false. | — | +| [`public Inno.Editor.Interactions.EditorValidationResult Inno.Editor.Assets.AssetEditingModule.ValidateRename(Inno.Editor.Assets.AssetEditorContext context, string newName)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L260) | Validates a requested name without modifying the source.
`context`: The immutable metadata captured for this immediate source operation.
`newName`: The requested entry name without its protected file extension. | The committed source identity, or a validation result describing why the operation is unavailable. | — | +| [`public void Inno.Editor.Assets.AssetEditingModule.Rename(Inno.Editor.Assets.AssetEditorContext context, string newName)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L278) | Renames a source atomically and records its reversible history payload.
`context`: The immutable metadata captured for this immediate source operation.
`newName`: The requested entry name without its protected file extension. | — | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.CanMoveToDirectory(string sourcePath, string targetDirectory)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L314) | Checks whether a source can move into the destination without a cycle or name collision.
`sourcePath`: The current isolated source-relative path.
`targetDirectory`: The isolated destination directory. | True when the request is accepted; otherwise false. | — | +| [`public Inno.Assets.Pipeline.AssetFileEntry Inno.Editor.Assets.AssetEditingModule.MoveToDirectoryWithHistory(string sourcePath, string targetDirectory, Inno.Editor.Interactions.IEditorHistory history)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L334) | Moves a source and records the committed mutation in the supplied history.
`sourcePath`: The current isolated source-relative path.
`targetDirectory`: The isolated destination directory.
`history`: The authoritative history that receives the committed mutation. | The committed source identity, or a validation result describing why the operation is unavailable. | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.DeleteWithHistory(Inno.Editor.Assets.AssetEditorContext asset, Inno.Editor.Interactions.IEditorHistory history)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L411) | Deletes a source with a recoverable archive and records the committed mutation.
`asset`: The captured source metadata to validate and delete.
`history`: The authoritative History receiving this mutation. | True when the request is accepted; otherwise false. | — | +| [`public void Inno.Editor.Assets.AssetEditingModule.SelectPath(string relativePath)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L462) | Selects the current source identity without retaining an indexed entry across generations.
`relativePath`: The isolated source-relative path to resolve. | — | — | +| [`public bool Inno.Editor.Assets.AssetEditingModule.TryCreateDragData(Inno.Editor.Assets.AssetEditorContext context, out Inno.Editor.Interactions.EditorDragData? data)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L481) | Creates identity-based drag data when the current extension permits dragging.
`context`: The immutable metadata captured for this immediate source operation.
`data`: Receives identity-based drag data, or null when dragging is unavailable. | True when the request is accepted; otherwise false. | — | +| [`protected override void Inno.Editor.Assets.AssetEditingModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L502) | Initializes this feature when its owning runtime becomes active.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Assets.AssetEditingModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L513) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Assets.AssetEditingModule.OnDispose()`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs#L521) | Releases resources retained by this feature after it has stopped. | — | — | +| [`public Inno.Editor.Assets.AssetOperationValidation Inno.Editor.Assets.AssetEditingModule.ValidateDeleteSelection(Inno.Editor.Core.EditorContext editor, System.Collections.Generic.IReadOnlyList targets)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs#L28) | Checks the entire source selection before a batch mutation, including extension validation and read-only ownership.
`editor`: The immediate Editor context.
`targets`: Fresh source entries in selection order. | The first rejection, or a valid result when every source can be deleted. | — | +| [`public void Inno.Editor.Assets.AssetEditingModule.DeleteSelection(Inno.Editor.Core.EditorContext editor, System.Collections.Generic.IReadOnlyList targets)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs#L68) | Deletes minimal selected source roots in one History transaction, restoring earlier deletions if a later one fails.
`editor`: The immediate Editor context.
`targets`: Fresh selected source entries; nested entries are covered by their selected directory. | — | `System.InvalidOperationException`: Validation or a mutation fails; rollback failures remain explicit. | +| [`public bool Inno.Editor.Assets.AssetEditingModule.CanMoveSelection(System.Collections.Generic.IReadOnlyList targets, string targetDirectory)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs#L99) | Validates every minimal selected source and rejects destination collisions before a batch move.
`targets`: Live source entries in selection order.
`targetDirectory`: The writable project destination. | True when every source can move to a distinct destination. | — | +| [`public void Inno.Editor.Assets.AssetEditingModule.MoveSelection(System.Collections.Generic.IReadOnlyList targets, string targetDirectory)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs#L126) | Moves selected source roots in one transaction; a later failure restores earlier moves.
`targets`: Live entries, including descendants covered by a selected directory.
`targetDirectory`: The writable project destination. | — | `System.InvalidOperationException`: Any source or destination rejects the operation. | +| [`public abstract class Inno.Editor.Assets.AssetEditor`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L7) | Customizes editor interactions for one imported asset type. | — | — | +| [`public virtual bool Inno.Editor.Assets.AssetEditor.CanOpen(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L18) | Gets whether the fallback asset editor can open the supplied source entry.
`context`: The immutable source and catalog snapshot for the operation. | when can handle the entry; otherwise, . | — | +| [`public virtual void Inno.Editor.Assets.AssetEditor.Open(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L25) | Opens the supplied asset entry when no more specific typed open action handled it.
`context`: The immutable source and catalog snapshot for the operation. | — | — | +| [`public virtual Inno.Editor.Assets.AssetOperationValidation Inno.Editor.Assets.AssetEditor.ValidateRename(Inno.Editor.Assets.AssetEditorContext context, string targetPath)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L41) | Validates a requested asset move before the AssetPipeline transaction begins.
`context`: The immutable snapshot of the source entry before the move.
`targetPath`: The normalized source-relative destination path. | A valid result when the transaction may proceed, or a diagnostic describing the rejection. | — | +| [`public virtual void Inno.Editor.Assets.AssetEditor.OnRenamed(Inno.Editor.Assets.AssetEditorContext context, string oldPath, string newPath)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L57) | Runs after an asset move transaction commits successfully.
`context`: The immutable snapshot captured before the move.
`oldPath`: The previous source-relative path.
`newPath`: The committed source-relative destination path. | — | — | +| [`public virtual Inno.Editor.Assets.AssetOperationValidation Inno.Editor.Assets.AssetEditor.ValidateDelete(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L73) | Validates a requested asset deletion before the AssetPipeline transaction begins.
`context`: The immutable snapshot of the entry that would be deleted. | A valid result when the transaction may proceed, or a diagnostic describing the rejection. | — | +| [`public virtual void Inno.Editor.Assets.AssetEditor.OnDeleted(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L80) | Runs after an asset deletion transaction commits successfully.
`context`: The immutable snapshot captured before the entry was deleted. | — | — | +| [`public virtual bool Inno.Editor.Assets.AssetEditor.CanStartDrag(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L93) | Gets whether the supplied entry can begin a managed editor drag operation.
`context`: The immutable source and catalog snapshot for the entry. | when drag data can be created; otherwise, . | — | +| [`public virtual Inno.Editor.Interactions.EditorDragData Inno.Editor.Assets.AssetEditor.CreateDragData(Inno.Editor.Assets.AssetEditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L103) | Creates the managed source, preview label, and validity predicate for an asset drag.
`context`: The immutable source and catalog snapshot for the entry. | The managed drag data published by the Asset Browser. | — | +| [`protected Inno.Editor.Assets.AssetEditor.AssetEditor()`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs#L7) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetEditorAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs#L8) | Associates an asset editor with an imported asset type. | — | — | +| [`public Inno.Editor.Assets.AssetEditorAttribute.AssetEditorAttribute(System.Type assetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs#L25) | Creates an asset-editor registration for an imported runtime asset type.
`assetType`: The imported asset type handled by the editor.
`useForChildren`: Whether the editor may also handle assignable derived asset types.
`priority`: The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public System.Type Inno.Editor.Assets.AssetEditorAttribute.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs#L38) | Gets the imported asset type handled by the editor. | — | — | +| [`public bool Inno.Editor.Assets.AssetEditorAttribute.useForChildren { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs#L42) | Gets whether derived asset types are accepted. | — | — | +| [`public int Inno.Editor.Assets.AssetEditorAttribute.priority { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs#L46) | Gets the tie-breaking priority. | — | — | +| [`public sealed class Inno.Editor.Assets.AssetEditorContext`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L10) | Provides an immutable snapshot for an asset editor operation. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Assets.AssetEditorContext.editorContext { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L66) | Gets the shared editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Assets.AssetEditorContext.interactions { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L70) | Gets the active editor interaction entry point. | — | — | +| [`public string Inno.Editor.Assets.AssetEditorContext.relativePath { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L74) | Gets the source-relative path. | — | — | +| [`public string Inno.Editor.Assets.AssetEditorContext.name { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L78) | Gets the final source path segment. | — | — | +| [`public bool Inno.Editor.Assets.AssetEditorContext.isDirectory { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L82) | Gets whether the source represents a directory. | — | — | +| [`public Inno.Assets.AssetInfo? Inno.Editor.Assets.AssetEditorContext.info { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L86) | Gets the cataloged asset information when available. | — | — | +| [`public System.Type? Inno.Editor.Assets.AssetEditorContext.assetType { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs#L90) | Gets the resolved imported asset type when available. | — | — | +| [`public readonly record struct Inno.Editor.Assets.AssetOperationValidation : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Describes whether an editor asset operation may proceed. | — | — | +| [`public Inno.Editor.Assets.AssetOperationValidation.AssetOperationValidation(bool isValid, string message)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L16) | Creates the validation result returned before an asset transaction begins.
`isValid`: Whether the requested asset operation may proceed.
`message`: The user-facing rejection diagnostic, or an empty string for a valid result. | — | — | +| [`public bool Inno.Editor.Assets.AssetOperationValidation.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L27) | Gets whether the operation may proceed. | — | — | +| [`public string Inno.Editor.Assets.AssetOperationValidation.message { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L31) | Gets the validation diagnostic. | — | — | +| [`public static Inno.Editor.Assets.AssetOperationValidation Inno.Editor.Assets.AssetOperationValidation.valid { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L35) | Gets a successful validation result. | — | — | +| [`public static Inno.Editor.Assets.AssetOperationValidation Inno.Editor.Assets.AssetOperationValidation.Invalid(string message)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L46) | Creates a failed asset-operation validation result.
`message`: The user-facing diagnostic explaining why the operation was rejected. | An invalid validation result containing the supplied diagnostic. | — | +| [`public override string Inno.Editor.Assets.AssetOperationValidation.ToString()`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Assets.AssetOperationValidation.operator !=(Inno.Editor.Assets.AssetOperationValidation left, Inno.Editor.Assets.AssetOperationValidation right)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Assets.AssetOperationValidation.operator ==(Inno.Editor.Assets.AssetOperationValidation left, Inno.Editor.Assets.AssetOperationValidation right)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Assets.AssetOperationValidation.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Assets.AssetOperationValidation.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Assets.AssetOperationValidation.Equals(Inno.Editor.Assets.AssetOperationValidation other)`](../../src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs#L5) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Assets.AssetImportSettingsEdits : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs#L15) | Applies importer configuration through the common sidecar pipeline and stable-identity Editor history. | — | — | +| [`public Inno.Editor.Assets.AssetImportSettingsEdits.AssetImportSettingsEdits(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Types.TypeCatalog types, Inno.Editor.Interactions.EditorInteractions interactions)`](../../src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs#L38) | Uses the authoring owners responsible for source identity, converter generations and shared undo.
`assets`: Authoritative asset pipeline.
`serialization`: Current owner converter registry.
`types`: Current type generation owner.
`interactions`: Shared Editor history owner. | — | — | +| [`public bool Inno.Editor.Assets.AssetImportSettingsEdits.Apply(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable settings, string expectedFingerprint)`](../../src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs#L65) | Saves one settings gesture, recording neutral before/after properties even when reimport reports an error.
`path`: Writable source asset path.
`settings`: Detached current-generation settings value.
`expectedFingerprint`: Sidecar fingerprint captured when editing began. | Whether the saved configuration reimported successfully; false does not mean the settings were discarded. | — | +| [`public sealed class Inno.Editor.Assets.AssetSampleImportModule : Inno.Editor.Core.EditorModule, System.IProgress`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L20) | Owns sample import validation, publication and rollback independently of its progress window. | — | — | +| [`public override bool Inno.Editor.Assets.AssetSampleImportModule.blocksFollowingUpdates { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L50) | Defers later modules while the import pins their generation or requires a Host restart. | — | — | +| [`public bool Inno.Editor.Assets.AssetSampleImportModule.isBusy { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L54) | Gets whether an import still owns validation or publication work. | — | — | +| [`public bool Inno.Editor.Assets.AssetSampleImportModule.canImport { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L58) | Gets whether the current authoring generation admits a new sample import. | — | — | +| [`public string Inno.Editor.Assets.AssetSampleImportModule.status { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L63) | Gets an immutable progress message captured under the progress owner lock. | — | — | +| [`public float Inno.Editor.Assets.AssetSampleImportModule.progress { get; }`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L75) | Gets the most recent validation progress fraction. | — | — | +| [`public void Inno.Editor.Assets.AssetSampleImportModule.Request(Inno.Assets.AssetPath source)`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L93) | Begins validating an installed sample before its source copy can be committed.
`source`: The installed sample root to validate and import. | — | `System.InvalidOperationException`: The authoring generation is busy or faulted. | +| [`public void Inno.Editor.Assets.AssetSampleImportModule.Cancel()`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L105) | Requests cancellation without releasing work that has not retired. | — | — | +| [`public void Inno.Editor.Assets.AssetSampleImportModule.Report(Inno.Scripting.Compiler.ScriptCompilationProgress value)`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L118) | Receives asynchronous compilation progress without accessing live Editor state.
`value`: The compiler stage and completion fraction. | — | — | +| [`protected override void Inno.Editor.Assets.AssetSampleImportModule.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L125) | Advances completed background phases and publishes the validated import at a frame safe point.
`context`: The current Editor frame and interaction services. | — | — | +| [`protected override void Inno.Editor.Assets.AssetSampleImportModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L200) | Cancels and retires pending work before the scripting and asset dependencies stop.
`context`: The stopping Editor's interaction services. | — | — | +| [`protected override void Inno.Editor.Assets.AssetSampleImportModule.OnDispose()`](../../src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs#L209) | Retries retirement without releasing dependencies still used by background work. | — | — | ## 项目依赖 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.Scripting` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Plugins.Authoring` | 公开签名依赖 | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Audio.md b/docs/editor/Inno.Editor.Audio.md index 3923ae8b9..631640b34 100644 --- a/docs/editor/Inno.Editor.Audio.md +++ b/docs/editor/Inno.Editor.Audio.md @@ -33,9 +33,7 @@ Audio 的 scope、update 与逆序释放全部由 Session Subsystem Pipeline 管 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Audio.EditorAudioHost` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -44,42 +42,38 @@ Audio 的 scope、update 与逆序释放全部由 Session Subsystem Pipeline 管 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Audio.EditorAudioHost.EditorAudioHost(Inno.Extensibility.Types.TypeCatalog types, Inno.Assets.IAssetArtifactLookup artifacts, Inno.Core.Diagnostics.DiagnosticHub diagnostics, System.Func deviceFactory, System.Func? settingsProvider = null)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L47) | Creates the Editor audio host over the active authoring artifact lookup and settings source.
`types`:Active type catalog used for provider and mixer extension generations.
`artifacts`:Authoring artifact lookup shared by Edit and isolated Play sessions.
`diagnostics`:Shared Editor diagnostic hub that owns current audio issues.
`deviceFactory`:Required host-owned backend-neutral device factory.
`settingsProvider`:Optional host-owned callback that returns the latest isolated audio project settings snapshot. | — | — | -| [`public Inno.Runtime.Contracts.IRuntimeSubsystemFactory Inno.Editor.Audio.EditorAudioHost.CreateRuntimeSubsystemFactory(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L70) | Creates the reusable feature factory used by Edit and Play runtime sessions.
返回:A factory that creates and releases one isolated audio generation per session.
`session`:The explicit session owner; it is never exposed through subsystem contracts. | — | — | -| [`public void Inno.Editor.Audio.EditorAudioHost.Dispose()`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L201) | Releases every active audio generation before their owning Editor sessions are torn down. | — | — | -| [`public System.IDisposable Inno.Editor.Audio.EditorAudioHost.EnterExecutionScope(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L147) | Binds the script-facing audio façade to one session's runtime.
返回:A strict last-in-first-out execution scope.
`session`:Session whose feature pipeline owns an active Editor audio generation. | — | — | -| [`public Inno.Audio.AudioVoiceHandle Inno.Editor.Audio.EditorAudioHost.PlayPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioClipAsset clip, Inno.Audio.AudioPlayOptions? options = null)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L164) | Starts an Editor-owned preview voice through an active Edit Mode session.
返回:The preview voice handle, initially in the preparing state.
`session`:Active Edit Mode session.
`clip`:Imported audio clip to preview.
`options`:Optional playback parameters; omitted values use engine defaults. | — | — | -| [`public bool Inno.Editor.Audio.EditorAudioHost.StopPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioVoiceHandle voice)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L188) | Stops one preview voice owned by an active Edit Mode session.
返回:true when a live preview was stopped.
`session`:Active Edit Mode session.
`voice`:Preview voice to stop. | — | — | -| [`public sealed class Inno.Editor.Audio.EditorAudioHost : Inno.Editor.Audio.IEditorAudioHost`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L19) | Owns Editor audio devices, previews, diagnostics, and isolated Play Mode audio generations. | — | — | - -### `Inno.Editor.Audio.IEditorAudioHost` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`Inno.Runtime.Contracts.IRuntimeSubsystemFactory Inno.Editor.Audio.IEditorAudioHost.CreateRuntimeSubsystemFactory(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L22) | Creates the reusable factory that contributes isolated audio runtimes to Editor sessions.
返回:A factory whose features are owned and released by their runtime sessions.
`session`:The explicit session being composed. | — | — | -| [`System.IDisposable Inno.Editor.Audio.IEditorAudioHost.EnterExecutionScope(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L33) | Binds the script-facing Inno.Editor.Audio façade to one session's audio runtime.
返回:A strict last-in-first-out execution scope.
`session`:Session whose feature pipeline owns an active Editor audio generation. | — | — | -| [`Inno.Audio.AudioVoiceHandle Inno.Editor.Audio.IEditorAudioHost.PlayPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioClipAsset clip, Inno.Audio.AudioPlayOptions? options = null)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L50) | Starts an Editor-owned preview voice through an active Edit session.
返回:The preview voice handle, initially in the preparing state.
`session`:Active Edit Mode session.
`clip`:Imported audio clip to preview.
`options`:Optional playback parameters; omitted values use engine defaults. | — | — | -| [`bool Inno.Editor.Audio.IEditorAudioHost.StopPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioVoiceHandle voice)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L68) | Stops one preview voice owned by an active Edit session.
返回:true when a live preview was stopped.
`session`:Active Edit Mode session.
`voice`:Preview voice to stop. | — | — | +| [`public sealed class Inno.Editor.Audio.EditorAudioHost : Inno.Editor.Audio.IEditorAudioHost`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L19) | Owns Editor audio devices, previews, diagnostics, and isolated Play Mode audio generations. | — | — | +| [`public Inno.Editor.Audio.EditorAudioHost.EditorAudioHost(Inno.Extensibility.Types.TypeCatalog types, Inno.Assets.IAssetArtifactLookup artifacts, Inno.Core.Diagnostics.DiagnosticHub diagnostics, System.Func deviceFactory, System.Func? settingsProvider = null)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L47) | Creates the Editor audio host over the active authoring artifact lookup and settings source.
`types`: Active type catalog used for provider and mixer extension generations.
`artifacts`: Authoring artifact lookup shared by Edit and isolated Play sessions.
`diagnostics`: Shared Editor diagnostic hub that owns current audio issues.
`deviceFactory`: Required host-owned backend-neutral device factory.
`settingsProvider`: Optional host-owned callback that returns the latest isolated audio project settings snapshot. | — | — | +| [`public Inno.Runtime.Contracts.IRuntimeSubsystemFactory Inno.Editor.Audio.EditorAudioHost.CreateRuntimeSubsystemFactory(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L70) | Creates the reusable feature factory used by Edit and Play runtime sessions.
`session`: The explicit session owner; it is never exposed through subsystem contracts. | A factory that creates and releases one isolated audio generation per session. | — | +| [`public System.IDisposable Inno.Editor.Audio.EditorAudioHost.EnterExecutionScope(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L147) | Binds the script-facing audio façade to one session's runtime.
`session`: Session whose feature pipeline owns an active Editor audio generation. | A strict last-in-first-out execution scope. | — | +| [`public Inno.Audio.AudioVoiceHandle Inno.Editor.Audio.EditorAudioHost.PlayPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioClipAsset clip, Inno.Audio.AudioPlayOptions? options = null)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L164) | Starts an Editor-owned preview voice through an active Edit Mode session.
`session`: Active Edit Mode session.
`clip`: Imported audio clip to preview.
`options`: Optional playback parameters; omitted values use engine defaults. | The preview voice handle, initially in the preparing state. | — | +| [`public bool Inno.Editor.Audio.EditorAudioHost.StopPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioVoiceHandle voice)`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L188) | Stops one preview voice owned by an active Edit Mode session.
`session`: Active Edit Mode session.
`voice`: Preview voice to stop. | when a live preview was stopped. | — | +| [`public void Inno.Editor.Audio.EditorAudioHost.Dispose()`](../../src/composition/editor/features/Inno.Editor.Audio/EditorAudioHost.cs#L201) | Releases every active audio generation before their owning Editor sessions are torn down. | — | — | | [`public interface Inno.Editor.Audio.IEditorAudioHost : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L11) | Coordinates isolated audio runtime generations for Edit and Play Mode sessions. | — | — | +| [`Inno.Runtime.Contracts.IRuntimeSubsystemFactory Inno.Editor.Audio.IEditorAudioHost.CreateRuntimeSubsystemFactory(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L22) | Creates the reusable factory that contributes isolated audio runtimes to Editor sessions.
`session`: The explicit session being composed. | A factory whose features are owned and released by their runtime sessions. | — | +| [`System.IDisposable Inno.Editor.Audio.IEditorAudioHost.EnterExecutionScope(Inno.Runtime.RuntimeSession session)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L33) | Binds the script-facing façade to one session's audio runtime.
`session`: Session whose feature pipeline owns an active Editor audio generation. | A strict last-in-first-out execution scope. | — | +| [`Inno.Audio.AudioVoiceHandle Inno.Editor.Audio.IEditorAudioHost.PlayPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioClipAsset clip, Inno.Audio.AudioPlayOptions? options = null)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L50) | Starts an Editor-owned preview voice through an active Edit session.
`session`: Active Edit Mode session.
`clip`: Imported audio clip to preview.
`options`: Optional playback parameters; omitted values use engine defaults. | The preview voice handle, initially in the preparing state. | — | +| [`bool Inno.Editor.Audio.IEditorAudioHost.StopPreview(Inno.Runtime.RuntimeSession session, Inno.Audio.AudioVoiceHandle voice)`](../../src/composition/editor/features/Inno.Editor.Audio/IEditorAudioHost.cs#L68) | Stops one preview voice owned by an active Edit session.
`session`: Active Edit Mode session.
`voice`: Preview voice to stop. | when a live preview was stopped. | — | ## 项目依赖 -- [Inno.Audio.Runtime](../audio/Inno.Audio.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Audio](../audio/Inno.Audio.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Runtime.Contracts](../runtime/Inno.Runtime.Contracts.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Audio.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Audio` | 公开签名依赖 | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Runtime` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Runtime.Contracts` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Core.md b/docs/editor/Inno.Editor.Core.md index 3da450ebd..cbedc027f 100644 --- a/docs/editor/Inno.Editor.Core.md +++ b/docs/editor/Inno.Editor.Core.md @@ -45,8 +45,14 @@ Commands、Menus、DragDrop、Selection 和 Rename 状态属于 `Inno.Editor.Int | `EditorStatistics` | 当前帧发布、同 ID 替换和上一完成帧 handoff 的顺序无关交换。 | | `EditorRuntime` | 表现无关的 `Start`、`Update(EditorFrame)`、`Dispose` 抽象。 | +`EditorStatistics.GetSnapshot()` 返回按稳定 group/item 顺序排列的不可变快照。内容和排序不变时复用同一实例,旧快照不会被后续发布修改。重复发布相同值不会迫使消费 Panel 重建分组;当前帧与上一完成帧的 handoff 仍保持,停止发布的项按原帧生命周期退出。发布和快照操作由同一内部同步边界保护,不保存扩展类型或回调。 + `EditorContext` 是由 Application host 创建并注入扩展的中立数据,不承担路由: +Host 构造入口为 `EditorContext(string projectDirectory, EditorKeyboardPolicy keyboard, string? initialLayoutDocument = null)`;该构造函数不导出给脚本。可选初始文档由组合层提供,Core 不内置标准面板、停靠比例或任何项目内容。仅当 `editor.ini` 不存在时通过 Core IO `AtomicFile` 创建,已有文件(包括空文件)保持原始内容及时间戳;并发创建不覆盖先完成者。读取、权限或创建失败明确传播。未提供初始文档时,Context 保持被动,不在构造时创建文件。 + +普通保存继续使用同一份可读文档和原子替换;没有第二套 Writer、模板 Reader 或格式兼容分支。 + ```csharp using System; using Inno.Editor.Core; @@ -253,93 +259,15 @@ Playing、Preparing、Stopping 禁止手动 Reload;自动变化保留在现有 Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对象操作进入所属 Play Session。Scene 事务每个阶段独立进入 owner scope,Identity 替换不依赖 UI 当前 ambient domain。 -## 当前源码公开 API 清单 - -签名来自当前 SDK 求值与语义符号;按类型列出 public/protected 契约。工作流与生命周期见本页前文。 -### `Inno.Editor.Core.EditorContext` - - - - - - - - - - - - - - - - +## 不依赖绘制的名称格式化 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorContext.EditorContext(string projectDirectory, Inno.Editor.Core.EditorKeyboardPolicy keyboard)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L31) | Creates a passive editor context for one project.
`projectDirectory`:The project root containing Assets and Library.
`keyboard`:The explicit immutable conventions selected by product composition. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:The keyboard policy is null.
`System.ArgumentException`:Thrown when projectDirectory is empty. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Core.EditorContext.GetLayoutSectionNames(string prefix = "")`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L86) | Gets layout section names matching an ordinal prefix.
返回:A stable sorted snapshot of matching section names.
`prefix`:The prefix to match, or an empty string for every section. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when prefix is null. | -| [`public bool Inno.Editor.Core.EditorContext.RemoveLayoutSection(string sectionName)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L145) | Removes one editor layout section from the in-memory document.
返回:true when an existing section was removed.
`sectionName`:The section name without its INI header syntax. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when sectionName is empty or cannot be represented in the layout. | -| [`public void Inno.Editor.Core.EditorContext.SaveLayout()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L181) | Atomically rewrites the complete project editor layout document. | 约束由所属类型与本页生命周期定义。 | `System.IO.IOException`:Thrown when the layout document cannot be written atomically.
`System.UnauthorizedAccessException`:Thrown when the layout document is inaccessible. | -| [`public bool Inno.Editor.Core.EditorContext.SaveLayoutIfChanged()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L169) | Atomically saves the project editor layout when it changed.
返回:true when a changed document was written. | 约束由所属类型与本页生命周期定义。 | `System.IO.IOException`:Thrown when the layout document cannot be written atomically.
`System.UnauthorizedAccessException`:Thrown when the layout document is inaccessible. | -| [`public void Inno.Editor.Core.EditorContext.SetImGuiLayout(string? value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L154) | Replaces the Dear ImGui layout while retaining editor module and panel state sections.
`value`:The complete layout text returned by Dear ImGui. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorContext.SetLayoutSection(string sectionName, System.Collections.Generic.IEnumerable> values)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L126) | Adds or replaces one human-readable editor layout section in memory.
`sectionName`:The section name without its INI header syntax.
`values`:The complete scalar or JSON-formatted values. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when values is null.
`System.ArgumentException`:Thrown when a section name, key, or value cannot be represented in the layout. | -| [`public bool Inno.Editor.Core.EditorContext.TryGetLayoutSection(string sectionName, out System.Collections.Generic.IReadOnlyDictionary values)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L104) | Tries to read one independent editor layout section snapshot.
返回:true when the requested section exists.
`sectionName`:The section name without its INI header syntax.
`values`:The copied values when the section exists. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when sectionName is empty or cannot be represented in the layout. | -| [`public Inno.Editor.Core.EditorFrame Inno.Editor.Core.EditorContext.frame { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L186) | Gets the latest immutable editor frame snapshot. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorContext.imguiLayout { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L69) | Gets the Dear ImGui layout text without editor module or panel state sections. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorContext.isFocused { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L191) | Gets whether any editor viewport currently owns application focus. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.Core.EditorKeyboardPolicy Inno.Editor.Core.EditorContext.keyboard { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L52) | Gets the immutable keyboard conventions shared by discovery, dispatch and presentation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorContext.layoutPath { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L63) | Gets the absolute path of the project editor layout document. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorContext.projectDirectory { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L47) | Gets the normalized project root directory. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.Core.EditorStatistics Inno.Editor.Core.EditorContext.statistics { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L57) | Gets the frame-scoped statistics exchange used by independent editor features and viewers. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorContext`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L13) | Provides passive project and frame state shared by editor extensions. Interaction routing is supplied separately by Inno.Editor.Interactions. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorFrame` +`EditorDisplayNames.Nicify` 为菜单、Inspector 与插件控件格式化标识符,不需要 ImGui session 或原生 context。空白输入得到空标签;下划线、连字符及小写到大写的边界产生空格。 - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorFrame.EditorFrame(float deltaTime, float totalTime, bool isFocused)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Describes one immutable editor frame.
`deltaTime`:The elapsed frame time in seconds.
`totalTime`:The absolute editor runtime in seconds.
`isFocused`:Whether an editor viewport owns application focus. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorFrame.Deconstruct(out float deltaTime, out float totalTime, out bool isFocused)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorFrame.Equals(Inno.Editor.Core.EditorFrame other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override bool Inno.Editor.Core.EditorFrame.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override int Inno.Editor.Core.EditorFrame.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override string Inno.Editor.Core.EditorFrame.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorFrame.operator ==(Inno.Editor.Core.EditorFrame left, Inno.Editor.Core.EditorFrame right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorFrame.operator !=(Inno.Editor.Core.EditorFrame left, Inno.Editor.Core.EditorFrame right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public float Inno.Editor.Core.EditorFrame.deltaTime { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L16) | The elapsed frame time in seconds. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorFrame.isFocused { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L18) | Whether an editor viewport owns application focus. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public float Inno.Editor.Core.EditorFrame.totalTime { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L17) | The absolute editor runtime in seconds. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public readonly record struct Inno.Editor.Core.EditorFrame : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Describes one immutable editor frame.
`deltaTime`:The elapsed frame time in seconds.
`totalTime`:The absolute editor runtime in seconds.
`isFocused`:Whether an editor viewport owns application focus. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorKeyboardPolicy` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorKeyboardPolicy.EditorKeyboardPolicy(Inno.Core.Input.KeyModifier primaryModifier, string superModifierLabel)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L23) | Defines the primary shortcut modifier and the display name of the Super modifier.
`primaryModifier`:One Control, Super or Alt modifier used by symbolic primary shortcuts.
`superModifierLabel`:The nonempty product-specific name displayed for the Super modifier. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:The modifier is not one supported modifier or the label is empty. | -| [`public Inno.Core.Input.KeyModifier Inno.Editor.Core.EditorKeyboardPolicy.primaryModifier { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L37) | Gets the modifier used when resolving symbolic primary shortcuts. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorKeyboardPolicy.superModifierLabel { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L42) | Gets the product-selected display name of the Super modifier. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorKeyboardPolicy`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L9) | Supplies immutable keyboard conventions selected by the Editor product composition. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | +## 当前源码公开 API 清单 -### `Inno.Editor.Core.EditorModal` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -352,39 +280,11 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Core.EditorModal.EditorModal()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L10) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorModal.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L60) | Draws the modal body inside the runtime-managed centered window.
`context`:The shared editor context containing current project and frame state. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null. | -| [`protected abstract void Inno.Editor.Core.EditorModal.OnDraw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L72) | Draws the modal body inside the runtime-managed centered window.
`context`:The shared editor context containing current project and frame state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorModal.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L36) | Gets whether the modal window owns scrolling when its content overflows. Disable this when a child region owns all scrolling. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorModal.blocksInteraction { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L20) | Gets whether the modal prevents interaction with regular editor views. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorModal.canMove { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L25) | Gets whether the modal window can be moved inside the main viewport. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorModal.canResize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L30) | Gets whether the modal window can be resized by the user. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorModal.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L42) | Gets the initial modal size in unscaled editor units, or P:System.Numerics.Vector2.Zero when the runtime should size the modal from its content. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract bool Inno.Editor.Core.EditorModal.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L15) | Gets whether the modal should currently be visible. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorModal.minimumSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L48) | Gets the minimum modal size in unscaled editor units, or P:System.Numerics.Vector2.Zero when no explicit minimum is required. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract class Inno.Editor.Core.EditorModal`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L10) | Defines non-dockable modal editor content. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorModalAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorModalAttribute.EditorModalAttribute(string id, string title, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L26) | Creates a centered modal registration with a stable identity and draw order.
`id`:The stable identity used to retain modal transition state across refreshes.
`title`:The visible title rendered by the modal host.
`order`:The stable ordering value when multiple modals are visible. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when id or title is empty. | -| [`public string Inno.Editor.Core.EditorModalAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L43) | Gets the stable modal identifier. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public int Inno.Editor.Core.EditorModalAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L53) | Gets the stable draw order. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorModalAttribute.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L48) | Gets the visible modal title. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorModalAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L9) | Registers a centered editor modal for automatic discovery. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorModule` - @@ -397,38 +297,10 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Core.EditorModule.EditorModule()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L13) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L172) | Captures readable project state owned by this module.
`state`:The writable parameter that receives the complete readable state for this module. | Overriding this method opts the module into project-state IO. Modules that keep the base implementation are never registered with the persistence coordinator and therefore perform no state reads or writes. | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L195) | Releases resources owned by this module after it has stopped and left the active extension generation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L133) | Runs after the module generation becomes active and before its first update.
`context`:The shared editor context for the active runtime. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L157) | Runs before the module generation is released and its disposable instances are destroyed.
`context`:The shared editor context for the runtime being stopped. | This includes partial startup compensation. Pending retirement must throw RetirementPendingException and preserve unfinished ownership; completed steps must be idempotent across retries. | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L143) | Runs once per editor frame before views are drawn.
`context`:The shared editor context containing the current frame state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorModule.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L187) | Restores readable project state owned by this module.
`state`:The read-only state parameter for this module. Missing or incompatible values return the fallback supplied to M:Inno.Editor.Core.EditorState.Get``1(System.String,``0). | This method is called only when M:Inno.Editor.Core.EditorModule.Capture(Inno.Editor.Core.EditorState) is overridden. It runs once after the module is started and before the module is allowed to capture replacement state. | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorModule.Start(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L46) | Starts the module after the containing extension generation becomes active.
`context`:The shared editor context for the active runtime. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null.
`System.ObjectDisposedException`:Thrown after the module has been disposed.
`System.InvalidOperationException`:Thrown when the module is already started. | -| [`public void Inno.Editor.Core.EditorModule.Stop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L102) | Stops the module before the containing extension generation is released.
`context`:The shared editor context for the runtime being stopped. | Also compensates a partially failed Start. Completed stops are idempotent; pending stops may be retried. | `System.ArgumentNullException`:Thrown when context is null.
`System.ObjectDisposedException`:Thrown after the module has been disposed.
`Inno.Core.Execution.RetirementPendingException`:Live work still depends on this module. Its owner must retain it and retry Stop before disposal. | -| [`public void Inno.Editor.Core.EditorModule.Update(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L74) | Updates the module once per editor frame before panels and modals are drawn.
`context`:The shared editor context containing the current frame state. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null.
`System.ObjectDisposedException`:Thrown after the module has been disposed.
`System.InvalidOperationException`:Thrown when the module is not started. | -| [`public virtual bool Inno.Editor.Core.EditorModule.blocksFollowingUpdates { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L28) | Gets whether modules ordered after this module must defer their updates for the current frame. | A module may return true while completing a project-wide bootstrap or atomic transition that later modules must not observe partially. Panels and modals remain drawable. | 具体失败按方法与类型契约处理。 | -| [`public abstract class Inno.Editor.Core.EditorModule : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L13) | Owns optional shared state and lifecycle for one editor feature. Simple panels and actions do not need a module. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorModuleAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorModuleAttribute.EditorModuleAttribute(string id, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L24) | Creates a module registration with stable identity and deterministic lifecycle order.
`id`:The globally unique module identifier used for discovery diagnostics and optional persisted module state.
`order`:The ascending order used to start and update the module. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when id is empty. | -| [`public string Inno.Editor.Core.EditorModuleAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L37) | Gets the stable module identifier. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public int Inno.Editor.Core.EditorModuleAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L42) | Gets the stable lifecycle order. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorModuleAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L9) | Registers an editor feature module with a stable identity for automatic discovery. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorPanel` - @@ -443,26 +315,6 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Core.EditorPanel.EditorPanel()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L13) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorPanel.Attach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L56) | Attaches the panel after its extension generation becomes active.
`context`:The shared editor context for the active runtime. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null.
`System.InvalidOperationException`:Thrown when the panel is already attached. | -| [`protected virtual void Inno.Editor.Core.EditorPanel.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L168) | Captures readable project state owned by this panel.
`state`:The writable parameter that receives the complete readable state for this panel. | Overriding this method opts the panel into project-state IO. Panels that keep the base implementation are never registered with the persistence coordinator and therefore perform no state reads or writes. Panel visibility is persisted separately for every registered panel. | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorPanel.Detach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L82) | Detaches the panel before its extension generation is released.
`context`:The shared editor context for the runtime being detached. | Also compensates partial attachment. Completed detachment is idempotent; a pending panel cannot draw. | `System.ArgumentNullException`:Thrown when context is null.
`Inno.Core.Execution.RetirementPendingException`:Panel work is still active. The owner must retain the panel and its dependencies and retry detachment. | -| [`public void Inno.Editor.Core.EditorPanel.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L117) | Draws the complete dockable contents of the panel for the current frame.
`context`:The shared editor context containing current selection, focus, and frame state. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null.
`System.InvalidOperationException`:Thrown when the panel is not attached. | -| [`protected virtual void Inno.Editor.Core.EditorPanel.OnAttach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L139) | Runs after the panel is attached to an active extension generation.
`context`:The shared editor context for the active runtime. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorPanel.OnDetach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L153) | Runs before the panel is detached from its active extension generation.
`context`:The shared editor context for the runtime being detached. | This includes failed attachment compensation. Retain unfinished resources when throwing RetirementPendingException and avoid repeating completed cleanup steps on the next attempt. | 具体失败按方法与类型契约处理。 | -| [`protected abstract void Inno.Editor.Core.EditorPanel.OnDraw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L131) | Draws the complete dockable contents of this panel for the current frame.
`context`:The shared editor context containing current selection, focus, and frame state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected virtual void Inno.Editor.Core.EditorPanel.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L183) | Restores readable project state owned by this panel.
`state`:The read-only state parameter for this panel. Missing or incompatible values return the fallback supplied to M:Inno.Editor.Core.EditorState.Get``1(System.String,``0). | This method is called only when M:Inno.Editor.Core.EditorPanel.Capture(Inno.Editor.Core.EditorState) is overridden. It runs once after the panel is attached and before the panel is allowed to capture replacement state. | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorPanel.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L31) | Gets whether the presentation backend may create a scroll range for this panel window. | Full-canvas panels should return false and manage navigation inside their own content instead of allowing the host window to scroll. | 具体失败按方法与类型契约处理。 | -| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorPanel.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L36) | Gets the initial floating size in UI coordinates; zero leaves native auto-sizing in control. Saved layouts take precedence. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorPanel.isOpen { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L41) | Gets or sets whether panel is visible. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public virtual bool Inno.Editor.Core.EditorPanel.useWindowPadding { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L22) | Gets whether the presentation backend should inset this panel body by its standard window padding. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract class Inno.Editor.Core.EditorPanel`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L13) | Base class for editor panel implementations. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorPanelAttribute` - @@ -471,20 +323,13 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorPanelAttribute.EditorPanelAttribute(string id, string title, int order = 0, bool defaultOpen = true, string menuPath = "", bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L36) | Creates a dockable panel registration with stable identity and presentation metadata.
`id`:The stable identity used for Panel-menu routing and reload-state retention.
`title`:The visible dockable-window title.
`order`:The stable panel and generated Panel-menu ordering value.
`defaultOpen`:Whether a newly discovered panel is visible before retained state is available.
`menuPath`:The optional slash-delimited category path under the generated Panel menu.
`separatorBefore`:Whether the generated toggle is preceded by a separator within its final menu category. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when id or title is empty, or when menuPath contains an empty segment. | -| [`public bool Inno.Editor.Core.EditorPanelAttribute.defaultOpen { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L74) | Gets whether a newly discovered panel is open by default. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorPanelAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L59) | Gets the stable panel identifier. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorPanelAttribute.menuPath { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L79) | Gets the optional category path under the generated Panel menu. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public int Inno.Editor.Core.EditorPanelAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L69) | Gets the stable panel ordering value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorPanelAttribute.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L84) | Gets whether the generated panel toggle begins a new visual group. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorPanelAttribute.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L64) | Gets the visible panel title. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorPanelAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L9) | Registers an editor panel for automatic discovery. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorReloadCoordinator` - + + + + + + + @@ -493,20 +338,45 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorReloadCoordinator.EditorReloadCoordinator()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L16) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | 请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public bool Inno.Editor.Core.EditorReloadCoordinator.CanActivateReload(out string? reason)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L66) | Queries whether compilation and activation can proceed without interrupting a running session.
返回:True when all live guards permit generation work.
`reason`:The first blocking reason, or null when activation is allowed. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | 请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public bool Inno.Editor.Core.EditorReloadCoordinator.CanRequestReload(out string? reason)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L55) | Queries whether every registered owner accepts an explicit reload request.
返回:True when all live guards permit a manual request.
`reason`:The first blocking reason, or null when the request is allowed. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | 请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public Inno.Extensibility.Reload.IAssemblyUnloadProbe Inno.Editor.Core.EditorReloadCoordinator.Execute(Inno.Extensibility.Modules.AssemblyReloadSession reload, Inno.Extensibility.Reload.IGenerationChange? externalChange = null)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L113) | Applies one prepared assembly reload together with every registered editor feature migration.
返回:A monitor that observes cooperative unloading of assemblies retired by the committed reload.
`reload`:The prepared assembly reload session to activate and complete.
`externalChange`:Content recovery prepared before type publication, applied before Editor recovery, and restored before old values. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | `System.ArgumentNullException`:Thrown when reload is null.
`System.AggregateException`:Thrown when activation fails and one or more feature or assembly rollback stages also fail.
请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public void Inno.Editor.Core.EditorReloadCoordinator.RefreshDiagnostics()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L128) | Requests every live participant to republish diagnostics derived from its current state. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | 请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public System.IDisposable Inno.Editor.Core.EditorReloadCoordinator.Register(Inno.Editor.Core.IEditorReloadParticipant participant)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L81) | Registers an editor feature that owns generation-bound live state.
返回:A registration lease that strongly retains the participant until the lease is disposed. The coordinator itself retains only a weak reference.
`participant`:The feature participant. The coordinator retains only a weak reference to this instance. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | `System.ArgumentNullException`:Thrown when participant is null.
请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public System.IDisposable Inno.Editor.Core.EditorReloadCoordinator.RegisterGuard(Inno.Editor.Core.IEditorReloadGuard guard)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L34) | Registers a weak admission guard without stopping its owner or creating a generation transaction.
返回:A caller-owned lease retaining the guard until registration is released.
`guard`:The owner whose current state determines reload admission. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | `System.ArgumentNullException`:The guard is null.
请求和执行分别重新检查;事务失败逆序恢复。 | -| [`public sealed class Inno.Editor.Core.EditorReloadCoordinator : Inno.Scripting.Reload.IScriptReloadCoordinator`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L16) | Coordinates weakly registered editor feature migrations around atomic assembly reload sessions. | 注册 lease 由调用者释放;coordinator 仅弱引用参与者与 guard。 | 请求和执行分别重新检查;事务失败逆序恢复。 | - -### `Inno.Editor.Core.EditorRuntime` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -514,83 +384,10 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Core.EditorRuntime.EditorRuntime(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L19) | Creates a runtime for the supplied passive context.
`context`:The shared editor context. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when context is null. | -| [`public abstract void Inno.Editor.Core.EditorRuntime.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L57) | Stops the runtime and releases active extensions. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`protected void Inno.Editor.Core.EditorRuntime.SetFrame(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L48) | Advances frame-scoped data and publishes the latest immutable frame.
`frame`:The frame to publish. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract void Inno.Editor.Core.EditorRuntime.Start()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L32) | Starts the runtime and activates its initial extension generation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract void Inno.Editor.Core.EditorRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L40) | Updates the runtime for one editor frame.
`frame`:The immutable frame state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Core.EditorRuntime.context { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L27) | Gets the shared passive editor context. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract class Inno.Editor.Core.EditorRuntime : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L8) | Defines the presentation-independent lifecycle of an editor runtime. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorState` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Core.EditorState.EditorState()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L19) | Initializes the base contract for a runtime-owned editor state parameter. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public abstract T Inno.Editor.Core.EditorState.Get(string key, T fallback)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L42) | Reads a compatible value or returns the caller-provided fallback.
返回:The stored compatible value, or fallback.
`key`:The stable key local to the owning module or panel.
`fallback`:The value returned when the key is absent, malformed, or incompatible with T. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when key is empty. | -| [`public abstract void Inno.Editor.Core.EditorState.Set(string key, T value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L68) | Writes one neutral value under a stable extension-local key.
`key`:The stable key local to the owning module or panel.
`value`:The value to persist. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when key is empty.
`System.InvalidOperationException`:Thrown when the state parameter was supplied for restoration and is therefore read-only.
`System.NotSupportedException`:Thrown when value cannot be represented by the runtime state serializer. | -| [`public abstract class Inno.Editor.Core.EditorState`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L14) | Provides the serialization-neutral parameter used to capture or restore one editor extension's project state. | The editor supplies this object to the protected state hooks on Inno.Editor.Core.EditorModule and Inno.Editor.Core.EditorPanel. Extensions should write values during capture and read values during restore; storage format and persistence remain private runtime details. | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorStatistic` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorStatistic.EditorStatistic(Inno.Editor.Core.EditorStatisticId id, Inno.Editor.Core.EditorStatisticGroupId groupId, string groupName, string label, string value, int groupOrder = 0, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L117) | Creates one statistic contribution.
`id`:Globally stable statistic identity used for replacement within a frame.
`groupId`:Stable visual group identity.
`groupName`:User-facing group heading.
`label`:User-facing metric label.
`value`:Already formatted user-facing value.
`groupOrder`:Ascending group presentation order.
`order`:Ascending metric order within the group. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when an identifier, group name, label, or value is empty. | -| [`public Inno.Editor.Core.EditorStatisticGroupId Inno.Editor.Core.EditorStatistic.groupId { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L150) | Gets the stable visual group identity. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorStatistic.groupName { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L155) | Gets the user-facing group heading. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public int Inno.Editor.Core.EditorStatistic.groupOrder { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L170) | Gets ascending group presentation order. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.Core.EditorStatisticId Inno.Editor.Core.EditorStatistic.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L145) | Gets the globally stable statistic identity. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorStatistic.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L160) | Gets the user-facing metric label. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public int Inno.Editor.Core.EditorStatistic.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L175) | Gets ascending metric order within the group. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorStatistic.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L165) | Gets the presentation-ready value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.Core.EditorStatistic`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L88) | Describes one immutable, presentation-ready statistic without retaining provider instances or types. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorStatisticGroupId` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorStatisticGroupId.EditorStatisticGroupId(string value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L60) | Creates a globally stable statistic-group identifier.
`value`:Non-empty globally stable identity. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when value is empty. | -| [`public bool Inno.Editor.Core.EditorStatisticGroupId.Equals(Inno.Editor.Core.EditorStatisticGroupId other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override bool Inno.Editor.Core.EditorStatisticGroupId.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override int Inno.Editor.Core.EditorStatisticGroupId.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override string Inno.Editor.Core.EditorStatisticGroupId.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L82) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorStatisticGroupId.operator ==(Inno.Editor.Core.EditorStatisticGroupId left, Inno.Editor.Core.EditorStatisticGroupId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorStatisticGroupId.operator !=(Inno.Editor.Core.EditorStatisticGroupId left, Inno.Editor.Core.EditorStatisticGroupId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorStatisticGroupId.isValid { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L74) | Gets whether this identifier contains a usable value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorStatisticGroupId.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L69) | Gets the globally stable identity. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public readonly record struct Inno.Editor.Core.EditorStatisticGroupId : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Identifies one visual group in the editor statistics presentation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorStatisticId` - @@ -601,81 +398,196 @@ Editor 模块更新与 Reload 事务使用 Edit Session;Play Tick 和运行对 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorStatisticId.EditorStatisticId(string value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L21) | Creates a globally stable statistic identifier.
`value`:Non-empty globally stable identity. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentException`:Thrown when value is empty. | -| [`public bool Inno.Editor.Core.EditorStatisticId.Equals(Inno.Editor.Core.EditorStatisticId other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override bool Inno.Editor.Core.EditorStatisticId.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override int Inno.Editor.Core.EditorStatisticId.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public override string Inno.Editor.Core.EditorStatisticId.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L43) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorStatisticId.operator ==(Inno.Editor.Core.EditorStatisticId left, Inno.Editor.Core.EditorStatisticId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public static bool Inno.Editor.Core.EditorStatisticId.operator !=(Inno.Editor.Core.EditorStatisticId left, Inno.Editor.Core.EditorStatisticId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.Core.EditorStatisticId.isValid { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L35) | Gets whether this identifier contains a usable value. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string Inno.Editor.Core.EditorStatisticId.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L30) | Gets the globally stable identity. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public readonly record struct Inno.Editor.Core.EditorStatisticId : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Identifies one editor statistic across panels, modules, and reloadable extensions. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.EditorStatistics` - + + + + + + + + + + + + + + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorStatistics.EditorStatistics()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L185) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Core.EditorStatistics.GetSnapshot()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L230) | Gets a deterministic snapshot combining current contributions with the previous completed frame for providers that draw after their consumer.
返回:A detached, sorted snapshot containing at most one value for each stable statistic ID. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.Core.EditorStatistics.Publish(Inno.Editor.Core.EditorStatistic statistic)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L200) | Publishes or replaces one statistic for the current editor frame.
`statistic`:Immutable contribution containing no runtime provider references. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when statistic is null. | -| [`public void Inno.Editor.Core.EditorStatistics.Publish(System.Collections.Generic.IEnumerable statistics)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L216) | Publishes or replaces several statistics for the current editor frame.
`statistics`:Contributions to publish in enumeration order. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when statistics or one of its values is null. | -| [`public sealed class Inno.Editor.Core.EditorStatistics`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L185) | Exchanges frame-scoped, reload-safe statistics between independent editor features and viewers. | Contributions contain only stable identifiers and strings. A contribution is retained for the current frame and one completed-frame handoff so panel draw order cannot lose data. | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.IEditorPanelReloadState` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`System.ReadOnlyMemory Inno.Editor.Core.IEditorPanelReloadState.CaptureReloadState()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L16) | Captures presentation-neutral state without retaining runtime or plugin object references.
返回:An immutable byte payload that can be consumed by the replacement panel generation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`void Inno.Editor.Core.IEditorPanelReloadState.RestoreReloadState(System.ReadOnlyMemory state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L24) | Restores presentation-neutral state captured from the previous panel generation.
`state`:The immutable payload returned by the retiring panel generation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public interface Inno.Editor.Core.IEditorPanelReloadState`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L8) | Optionally migrates presentation-neutral panel state across assembly reloads. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.Core.IEditorReloadGuard` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`string? Inno.Editor.Core.IEditorReloadGuard.blockingReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L21) | Gets the reason admission is blocked, or null when the owner is ready for reload. | 借用不可变契约;状态由 owner 在其线程安全点提供。 | 阻止请求不停止 Play;仍须遵守 generation Pending/Faulted gate。 | -| [`bool Inno.Editor.Core.IEditorReloadGuard.canActivateReload { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L16) | Gets whether compilation and activation may proceed without a live isolated runtime session. | 借用不可变契约;状态由 owner 在其线程安全点提供。 | 阻止请求不停止 Play;仍须遵守 generation Pending/Faulted gate。 | -| [`bool Inno.Editor.Core.IEditorReloadGuard.canRequestReload { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L11) | Gets whether an explicit user reload request may be accepted in the current owner state. | 借用不可变契约;状态由 owner 在其线程安全点提供。 | 阻止请求不停止 Play;仍须遵守 generation Pending/Faulted gate。 | -| [`public interface Inno.Editor.Core.IEditorReloadGuard`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L6) | Describes whether an editor owner has released the state required for a generation change. | 借用不可变契约;状态由 owner 在其线程安全点提供。 | 阻止请求不停止 Play;仍须遵守 generation Pending/Faulted gate。 | - -### `Inno.Editor.Core.IEditorReloadParticipant` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Extensibility.Reload.IGenerationChange Inno.Editor.Core.IEditorReloadParticipant.Capture(Inno.Extensibility.Modules.AssemblyReloadContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L21) | Captures one isolated transaction for the prepared assembly generation.
返回:A transaction that has captured current state but has not modified live editor objects.
`context`:The prepared assembly reload context containing previous and candidate participant state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`void Inno.Editor.Core.IEditorReloadParticipant.RefreshDiagnostics()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L26) | Republishes diagnostics derived from the participant's current live state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public interface Inno.Editor.Core.IEditorReloadParticipant`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L10) | Contributes editor-owned live state to an assembly reload without coupling the reload requester to a feature domain. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | +| [`public abstract class Inno.Editor.Core.EditorModal`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L10) | Defines non-dockable modal editor content. | — | — | +| [`public abstract bool Inno.Editor.Core.EditorModal.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L15) | Gets whether the modal should currently be visible. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorModal.blocksInteraction { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L20) | Gets whether the modal prevents interaction with regular editor views. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorModal.canMove { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L25) | Gets whether the modal window can be moved inside the main viewport. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorModal.canResize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L30) | Gets whether the modal window can be resized by the user. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorModal.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L36) | Gets whether the modal window owns scrolling when its content overflows. Disable this when a child region owns all scrolling. | — | — | +| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorModal.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L42) | Gets the initial modal size in unscaled editor units, or when the runtime should size the modal from its content. | — | — | +| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorModal.minimumSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L48) | Gets the minimum modal size in unscaled editor units, or when no explicit minimum is required. | — | — | +| [`public void Inno.Editor.Core.EditorModal.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L60) | Draws the modal body inside the runtime-managed centered window.
`context`: The shared editor context containing current project and frame state. | — | `System.ArgumentNullException`: Thrown when is . | +| [`protected abstract void Inno.Editor.Core.EditorModal.OnDraw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L72) | Draws the modal body inside the runtime-managed centered window.
`context`: The shared editor context containing current project and frame state. | — | — | +| [`protected Inno.Editor.Core.EditorModal.EditorModal()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModal.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Core.EditorModalAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L9) | Registers a centered editor modal for automatic discovery. | — | — | +| [`public Inno.Editor.Core.EditorModalAttribute.EditorModalAttribute(string id, string title, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L26) | Creates a centered modal registration with a stable identity and draw order.
`id`: The stable identity used to retain modal transition state across refreshes.
`title`: The visible title rendered by the modal host.
`order`: The stable ordering value when multiple modals are visible. | — | `System.ArgumentException`: Thrown when or is empty. | +| [`public string Inno.Editor.Core.EditorModalAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L43) | Gets the stable modal identifier. | — | — | +| [`public string Inno.Editor.Core.EditorModalAttribute.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L48) | Gets the visible modal title. | — | — | +| [`public int Inno.Editor.Core.EditorModalAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModalAttribute.cs#L53) | Gets the stable draw order. | — | — | +| [`public abstract class Inno.Editor.Core.EditorModule : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L13) | Owns optional shared state and lifecycle for one editor feature. Simple panels and actions do not need a module. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorModule.blocksFollowingUpdates { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L28) | Gets whether modules ordered after this module must defer their updates for the current frame. | — | — | +| [`public void Inno.Editor.Core.EditorModule.Start(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L46) | Starts the module after the containing extension generation becomes active.
`context`: The shared editor context for the active runtime. | — | `System.ArgumentNullException`: Thrown when is .
`System.ObjectDisposedException`: Thrown after the module has been disposed.
`System.InvalidOperationException`: Thrown when the module is already started. | +| [`public void Inno.Editor.Core.EditorModule.Update(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L74) | Updates the module once per editor frame before panels and modals are drawn.
`context`: The shared editor context containing the current frame state. | — | `System.ArgumentNullException`: Thrown when is .
`System.ObjectDisposedException`: Thrown after the module has been disposed.
`System.InvalidOperationException`: Thrown when the module is not started. | +| [`public void Inno.Editor.Core.EditorModule.Stop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L102) | Stops the module before the containing extension generation is released.
`context`: The shared editor context for the runtime being stopped. | — | `System.ArgumentNullException`: Thrown when is .
`System.ObjectDisposedException`: Thrown after the module has been disposed.
`Inno.Core.Execution.RetirementPendingException`: Live work still depends on this module. Its owner must retain it and retry Stop before disposal. | +| [`protected virtual void Inno.Editor.Core.EditorModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L133) | Runs after the module generation becomes active and before its first update.
`context`: The shared editor context for the active runtime. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorModule.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L143) | Runs once per editor frame before views are drawn.
`context`: The shared editor context containing the current frame state. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L157) | Runs before the module generation is released and its disposable instances are destroyed.
`context`: The shared editor context for the runtime being stopped. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorModule.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L172) | Captures readable project state owned by this module.
`state`: The writable parameter that receives the complete readable state for this module. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorModule.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L187) | Restores readable project state owned by this module.
`state`: The read-only state parameter for this module. Missing or incompatible values return the fallback supplied to . | — | — | +| [`protected virtual void Inno.Editor.Core.EditorModule.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L195) | Releases resources owned by this module after it has stopped and left the active extension generation. | — | — | +| [`protected Inno.Editor.Core.EditorModule.EditorModule()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModule.cs#L13) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Core.EditorModuleAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L9) | Registers an editor feature module with a stable identity for automatic discovery. | — | — | +| [`public Inno.Editor.Core.EditorModuleAttribute.EditorModuleAttribute(string id, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L24) | Creates a module registration with stable identity and deterministic lifecycle order.
`id`: The globally unique module identifier used for discovery diagnostics and optional persisted module state.
`order`: The ascending order used to start and update the module. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Core.EditorModuleAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L37) | Gets the stable module identifier. | — | — | +| [`public int Inno.Editor.Core.EditorModuleAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorModuleAttribute.cs#L42) | Gets the stable lifecycle order. | — | — | +| [`public abstract class Inno.Editor.Core.EditorPanel`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L13) | Base class for editor panel implementations. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorPanel.useWindowPadding { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L22) | Gets whether the presentation backend should inset this panel body by its standard window padding. | — | — | +| [`public virtual bool Inno.Editor.Core.EditorPanel.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L31) | Gets whether the presentation backend may create a scroll range for this panel window. | — | — | +| [`public virtual System.Numerics.Vector2 Inno.Editor.Core.EditorPanel.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L36) | Gets the initial floating size in UI coordinates; zero leaves native auto-sizing in control. Saved layouts take precedence. | — | — | +| [`public bool Inno.Editor.Core.EditorPanel.isOpen { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L41) | Gets or sets whether panel is visible. | — | — | +| [`public void Inno.Editor.Core.EditorPanel.Attach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L56) | Attaches the panel after its extension generation becomes active.
`context`: The shared editor context for the active runtime. | — | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the panel is already attached. | +| [`public void Inno.Editor.Core.EditorPanel.Detach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L82) | Detaches the panel before its extension generation is released.
`context`: The shared editor context for the runtime being detached. | — | `System.ArgumentNullException`: Thrown when is .
`Inno.Core.Execution.RetirementPendingException`: Panel work is still active. The owner must retain the panel and its dependencies and retry detachment. | +| [`public void Inno.Editor.Core.EditorPanel.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L117) | Draws the complete dockable contents of the panel for the current frame.
`context`: The shared editor context containing current selection, focus, and frame state. | — | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the panel is not attached. | +| [`protected abstract void Inno.Editor.Core.EditorPanel.OnDraw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L131) | Draws the complete dockable contents of this panel for the current frame.
`context`: The shared editor context containing current selection, focus, and frame state. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorPanel.OnAttach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L139) | Runs after the panel is attached to an active extension generation.
`context`: The shared editor context for the active runtime. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorPanel.OnDetach(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L153) | Runs before the panel is detached from its active extension generation.
`context`: The shared editor context for the runtime being detached. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorPanel.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L168) | Captures readable project state owned by this panel.
`state`: The writable parameter that receives the complete readable state for this panel. | — | — | +| [`protected virtual void Inno.Editor.Core.EditorPanel.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L183) | Restores readable project state owned by this panel.
`state`: The read-only state parameter for this panel. Missing or incompatible values return the fallback supplied to . | — | — | +| [`protected Inno.Editor.Core.EditorPanel.EditorPanel()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanel.cs#L13) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Core.EditorPanelAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L9) | Registers an editor panel for automatic discovery. | — | — | +| [`public Inno.Editor.Core.EditorPanelAttribute.EditorPanelAttribute(string id, string title, int order = 0, bool defaultOpen = true, string menuPath = "", bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L36) | Creates a dockable panel registration with stable identity and presentation metadata.
`id`: The stable identity used for Panel-menu routing and reload-state retention.
`title`: The visible dockable-window title.
`order`: The stable panel and generated Panel-menu ordering value.
`defaultOpen`: Whether a newly discovered panel is visible before retained state is available.
`menuPath`: The optional slash-delimited category path under the generated Panel menu.
`separatorBefore`: Whether the generated toggle is preceded by a separator within its final menu category. | — | `System.ArgumentException`: Thrown when or is empty, or when contains an empty segment. | +| [`public string Inno.Editor.Core.EditorPanelAttribute.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L59) | Gets the stable panel identifier. | — | — | +| [`public string Inno.Editor.Core.EditorPanelAttribute.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L64) | Gets the visible panel title. | — | — | +| [`public int Inno.Editor.Core.EditorPanelAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L69) | Gets the stable panel ordering value. | — | — | +| [`public bool Inno.Editor.Core.EditorPanelAttribute.defaultOpen { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L74) | Gets whether a newly discovered panel is open by default. | — | — | +| [`public string Inno.Editor.Core.EditorPanelAttribute.menuPath { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L79) | Gets the optional category path under the generated Panel menu. | — | — | +| [`public bool Inno.Editor.Core.EditorPanelAttribute.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorPanelAttribute.cs#L84) | Gets whether the generated panel toggle begins a new visual group. | — | — | +| [`public static class Inno.Editor.Core.EditorTypeSpecificity`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorTypeSpecificity.cs#L7) | Ranks assignable editor extension targets by class ancestry, with interface registrations one step away. | — | — | +| [`public static bool Inno.Editor.Core.EditorTypeSpecificity.TryGet(System.Type concreteType, System.Type candidateType, out int distance)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorTypeSpecificity.cs#L27) | Determines whether a registration accepts a concrete target and returns its specificity distance.
`concreteType`: The target type supplied by the active extension generation.
`candidateType`: The registered class or interface accepted by the extension.
`distance`: Zero for an exact match, ancestry distance for classes, or one for an interface. | True when the registration accepts the target; otherwise false and the maximum distance. | `System.ArgumentNullException`: Either type is null. | +| [`public interface Inno.Editor.Core.IEditorPanelReloadState`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L8) | Optionally migrates presentation-neutral panel state across assembly reloads. | — | — | +| [`System.ReadOnlyMemory Inno.Editor.Core.IEditorPanelReloadState.CaptureReloadState()`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L16) | Captures presentation-neutral state without retaining runtime or plugin object references. | An immutable byte payload that can be consumed by the replacement panel generation. | — | +| [`void Inno.Editor.Core.IEditorPanelReloadState.RestoreReloadState(System.ReadOnlyMemory state)`](../../src/composition/editor/framework/Inno.Editor.Core/Extensions/IEditorPanelReloadState.cs#L24) | Restores presentation-neutral state captured from the previous panel generation.
`state`: The immutable payload returned by the retiring panel generation. | — | — | +| [`public static class Inno.Editor.Core.EditorDisplayNames`](../../src/composition/editor/framework/Inno.Editor.Core/Presentation/EditorDisplayNames.cs#L9) | Formats readable Editor labels independently from a native UI context or session. | — | — | +| [`public static string Inno.Editor.Core.EditorDisplayNames.Nicify(string name)`](../../src/composition/editor/framework/Inno.Editor.Core/Presentation/EditorDisplayNames.cs#L20) | Converts an identifier into a readable editor label.
`name`: Source identifier. | A label with identifier separators and lower-to-upper transitions spaced; empty when the input is blank. | — | +| [`public sealed class Inno.Editor.Core.EditorReloadCoordinator : Inno.Scripting.Reload.IScriptReloadCoordinator`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L16) | Coordinates weakly registered editor feature migrations around atomic assembly reload sessions. | — | — | +| [`public System.IDisposable Inno.Editor.Core.EditorReloadCoordinator.RegisterGuard(Inno.Editor.Core.IEditorReloadGuard guard)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L34) | Registers a weak admission guard without stopping its owner or creating a generation transaction.
`guard`: The owner whose current state determines reload admission. | A caller-owned lease retaining the guard until registration is released. | `System.ArgumentNullException`: The guard is null. | +| [`public bool Inno.Editor.Core.EditorReloadCoordinator.CanRequestReload(out string? reason)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L55) | Queries whether every registered owner accepts an explicit reload request.
`reason`: The first blocking reason, or null when the request is allowed. | True when all live guards permit a manual request. | — | +| [`public bool Inno.Editor.Core.EditorReloadCoordinator.CanActivateReload(out string? reason)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L66) | Queries whether compilation and activation can proceed without interrupting a running session.
`reason`: The first blocking reason, or null when activation is allowed. | True when all live guards permit generation work. | — | +| [`public System.IDisposable Inno.Editor.Core.EditorReloadCoordinator.Register(Inno.Editor.Core.IEditorReloadParticipant participant)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L81) | Registers an editor feature that owns generation-bound live state.
`participant`: The feature participant. The coordinator retains only a weak reference to this instance. | A registration lease that strongly retains the participant until the lease is disposed. The coordinator itself retains only a weak reference. | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Extensibility.Reload.IAssemblyUnloadProbe Inno.Editor.Core.EditorReloadCoordinator.Execute(Inno.Extensibility.Modules.AssemblyReloadSession reload, Inno.Extensibility.Reload.IGenerationChange? externalChange = null)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L113) | Applies one prepared assembly reload together with every registered editor feature migration.
`reload`: The prepared assembly reload session to activate and complete.
`externalChange`: Content recovery prepared before type publication, applied before Editor recovery, and restored before old values. | A monitor that observes cooperative unloading of assemblies retired by the committed reload. | `System.ArgumentNullException`: Thrown when is .
`System.AggregateException`: Thrown when activation fails and one or more feature or assembly rollback stages also fail. | +| [`public void Inno.Editor.Core.EditorReloadCoordinator.RefreshDiagnostics()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L128) | Requests every live participant to republish diagnostics derived from its current state. | — | — | +| [`public Inno.Editor.Core.EditorReloadCoordinator.EditorReloadCoordinator()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/EditorReloadCoordinator.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public interface Inno.Editor.Core.IEditorReloadGuard`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L6) | Describes whether an editor owner has released the state required for a generation change. | — | — | +| [`bool Inno.Editor.Core.IEditorReloadGuard.canRequestReload { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L11) | Gets whether an explicit user reload request may be accepted in the current owner state. | — | — | +| [`bool Inno.Editor.Core.IEditorReloadGuard.canActivateReload { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L16) | Gets whether compilation and activation may proceed without a live isolated runtime session. | — | — | +| [`string? Inno.Editor.Core.IEditorReloadGuard.blockingReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadGuard.cs#L21) | Gets the reason admission is blocked, or null when the owner is ready for reload. | — | — | +| [`public interface Inno.Editor.Core.IEditorReloadParticipant`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L10) | Contributes editor-owned live state to an assembly reload without coupling the reload requester to a feature domain. | — | — | +| [`Inno.Extensibility.Reload.IGenerationChange Inno.Editor.Core.IEditorReloadParticipant.Capture(Inno.Extensibility.Modules.AssemblyReloadContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L21) | Captures one isolated transaction for the prepared assembly generation.
`context`: The prepared assembly reload context containing previous and candidate participant state. | A transaction that has captured current state but has not modified live editor objects. | — | +| [`void Inno.Editor.Core.IEditorReloadParticipant.RefreshDiagnostics()`](../../src/composition/editor/framework/Inno.Editor.Core/Reloading/IEditorReloadParticipant.cs#L26) | Republishes diagnostics derived from the participant's current live state. | — | — | +| [`public sealed class Inno.Editor.Core.EditorContext`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L13) | Provides passive project and frame state shared by editor extensions. Interaction routing is supplied separately by Inno.Editor.Interactions. | — | — | +| [`public Inno.Editor.Core.EditorContext.EditorContext(string projectDirectory, Inno.Editor.Core.EditorKeyboardPolicy keyboard)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L31) | Creates a passive editor context for one project.
`projectDirectory`: The project root containing Assets and Library.
`keyboard`: The explicit immutable conventions selected by product composition. | — | `System.ArgumentNullException`: The keyboard policy is null.
`System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Core.EditorContext.projectDirectory { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L47) | Gets the normalized project root directory. | — | — | +| [`public Inno.Editor.Core.EditorKeyboardPolicy Inno.Editor.Core.EditorContext.keyboard { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L52) | Gets the immutable keyboard conventions shared by discovery, dispatch and presentation. | — | — | +| [`public Inno.Editor.Core.EditorStatistics Inno.Editor.Core.EditorContext.statistics { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L57) | Gets the frame-scoped statistics exchange used by independent editor features and viewers. | — | — | +| [`public string Inno.Editor.Core.EditorContext.layoutPath { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L63) | Gets the absolute path of the project editor layout document. | — | — | +| [`public string Inno.Editor.Core.EditorContext.imguiLayout { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L69) | Gets the Dear ImGui layout text without editor module or panel state sections. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Core.EditorContext.GetLayoutSectionNames(string prefix = "")`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L86) | Gets layout section names matching an ordinal prefix.
`prefix`: The prefix to match, or an empty string for every section. | A stable sorted snapshot of matching section names. | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.Core.EditorContext.TryGetLayoutSection(string sectionName, out System.Collections.Generic.IReadOnlyDictionary values)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L104) | Tries to read one independent editor layout section snapshot.
`sectionName`: The section name without its INI header syntax.
`values`: The copied values when the section exists. | when the requested section exists. | `System.ArgumentException`: Thrown when is empty or cannot be represented in the layout. | +| [`public void Inno.Editor.Core.EditorContext.SetLayoutSection(string sectionName, System.Collections.Generic.IEnumerable> values)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L126) | Adds or replaces one human-readable editor layout section in memory.
`sectionName`: The section name without its INI header syntax.
`values`: The complete scalar or JSON-formatted values. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when a section name, key, or value cannot be represented in the layout. | +| [`public bool Inno.Editor.Core.EditorContext.RemoveLayoutSection(string sectionName)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L145) | Removes one editor layout section from the in-memory document.
`sectionName`: The section name without its INI header syntax. | when an existing section was removed. | `System.ArgumentException`: Thrown when is empty or cannot be represented in the layout. | +| [`public void Inno.Editor.Core.EditorContext.SetImGuiLayout(string? value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L154) | Replaces the Dear ImGui layout while retaining editor module and panel state sections.
`value`: The complete layout text returned by Dear ImGui. | — | — | +| [`public bool Inno.Editor.Core.EditorContext.SaveLayoutIfChanged()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L169) | Atomically saves the project editor layout when it changed. | when a changed document was written. | `System.IO.IOException`: Thrown when the layout document cannot be written atomically.
`System.UnauthorizedAccessException`: Thrown when the layout document is inaccessible. | +| [`public void Inno.Editor.Core.EditorContext.SaveLayout()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L181) | Atomically rewrites the complete project editor layout document. | — | `System.IO.IOException`: Thrown when the layout document cannot be written atomically.
`System.UnauthorizedAccessException`: Thrown when the layout document is inaccessible. | +| [`public Inno.Editor.Core.EditorFrame Inno.Editor.Core.EditorContext.frame { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L186) | Gets the latest immutable editor frame snapshot. | — | — | +| [`public bool Inno.Editor.Core.EditorContext.isFocused { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs#L191) | Gets whether any editor viewport currently owns application focus. | — | — | +| [`public readonly record struct Inno.Editor.Core.EditorFrame : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Describes one immutable editor frame.
`deltaTime`: The elapsed frame time in seconds.
`totalTime`: The absolute editor runtime in seconds.
`isFocused`: Whether an editor viewport owns application focus. | — | — | +| [`public Inno.Editor.Core.EditorFrame.EditorFrame(float deltaTime, float totalTime, bool isFocused)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Describes one immutable editor frame.
`deltaTime`: The elapsed frame time in seconds.
`totalTime`: The absolute editor runtime in seconds.
`isFocused`: Whether an editor viewport owns application focus. | — | — | +| [`public float Inno.Editor.Core.EditorFrame.deltaTime { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L16) | The elapsed frame time in seconds. | — | — | +| [`public float Inno.Editor.Core.EditorFrame.totalTime { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L17) | The absolute editor runtime in seconds. | — | — | +| [`public bool Inno.Editor.Core.EditorFrame.isFocused { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L18) | Whether an editor viewport owns application focus. | — | — | +| [`public override string Inno.Editor.Core.EditorFrame.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Core.EditorFrame.operator !=(Inno.Editor.Core.EditorFrame left, Inno.Editor.Core.EditorFrame right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Core.EditorFrame.operator ==(Inno.Editor.Core.EditorFrame left, Inno.Editor.Core.EditorFrame right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Core.EditorFrame.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Core.EditorFrame.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Core.EditorFrame.Equals(Inno.Editor.Core.EditorFrame other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Core.EditorFrame.Deconstruct(out float deltaTime, out float totalTime, out bool isFocused)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorFrame.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Core.EditorKeyboardPolicy`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L9) | Supplies immutable keyboard conventions selected by the Editor product composition. | — | — | +| [`public Inno.Editor.Core.EditorKeyboardPolicy.EditorKeyboardPolicy(Inno.Core.Input.KeyModifier primaryModifier, string superModifierLabel)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L23) | Defines the primary shortcut modifier and the display name of the Super modifier.
`primaryModifier`: One Control, Super or Alt modifier used by symbolic primary shortcuts.
`superModifierLabel`: The nonempty product-specific name displayed for the Super modifier. | — | `System.ArgumentException`: The modifier is not one supported modifier or the label is empty. | +| [`public Inno.Core.Input.KeyModifier Inno.Editor.Core.EditorKeyboardPolicy.primaryModifier { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L37) | Gets the modifier used when resolving symbolic primary shortcuts. | — | — | +| [`public string Inno.Editor.Core.EditorKeyboardPolicy.superModifierLabel { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorKeyboardPolicy.cs#L42) | Gets the product-selected display name of the Super modifier. | — | — | +| [`public abstract class Inno.Editor.Core.EditorRuntime : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L8) | Defines the presentation-independent lifecycle of an editor runtime. | — | — | +| [`protected Inno.Editor.Core.EditorRuntime.EditorRuntime(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L19) | Creates a runtime for the supplied passive context.
`context`: The shared editor context. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Core.EditorRuntime.context { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L27) | Gets the shared passive editor context. | — | — | +| [`public abstract void Inno.Editor.Core.EditorRuntime.Start()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L32) | Starts the runtime and activates its initial extension generation. | — | — | +| [`public abstract void Inno.Editor.Core.EditorRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L40) | Updates the runtime for one editor frame.
`frame`: The immutable frame state. | — | — | +| [`protected void Inno.Editor.Core.EditorRuntime.SetFrame(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L48) | Advances frame-scoped data and publishes the latest immutable frame.
`frame`: The frame to publish. | — | — | +| [`public abstract void Inno.Editor.Core.EditorRuntime.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorRuntime.cs#L57) | Stops the runtime and releases active extensions. | — | — | +| [`public abstract class Inno.Editor.Core.EditorState`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L14) | Provides the serialization-neutral parameter used to capture or restore one editor extension's project state. | — | — | +| [`protected Inno.Editor.Core.EditorState.EditorState()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L19) | Initializes the base contract for a runtime-owned editor state parameter. | — | — | +| [`public abstract T Inno.Editor.Core.EditorState.Get(string key, T fallback)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L42) | Reads a compatible value or returns the caller-provided fallback.
`key`: The stable key local to the owning module or panel.
`fallback`: The value returned when the key is absent, malformed, or incompatible with .
`T`: The neutral value type expected by the caller. | The stored compatible value, or . | `System.ArgumentException`: Thrown when is empty. | +| [`public abstract void Inno.Editor.Core.EditorState.Set(string key, T value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorState.cs#L68) | Writes one neutral value under a stable extension-local key.
`key`: The stable key local to the owning module or panel.
`value`: The value to persist.
`T`: The neutral value type supplied by the caller. | — | `System.ArgumentException`: Thrown when is empty.
`System.InvalidOperationException`: Thrown when the state parameter was supplied for restoration and is therefore read-only.
`System.NotSupportedException`: Thrown when cannot be represented by the runtime state serializer. | +| [`public readonly record struct Inno.Editor.Core.EditorStatisticId : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Identifies one editor statistic across panels, modules, and reloadable extensions. | — | — | +| [`public Inno.Editor.Core.EditorStatisticId.EditorStatisticId(string value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L21) | Creates a globally stable statistic identifier.
`value`: Non-empty globally stable identity. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Core.EditorStatisticId.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L30) | Gets the globally stable identity. | — | — | +| [`public bool Inno.Editor.Core.EditorStatisticId.isValid { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L35) | Gets whether this identifier contains a usable value. | — | — | +| [`public override string Inno.Editor.Core.EditorStatisticId.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L43) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static bool Inno.Editor.Core.EditorStatisticId.operator !=(Inno.Editor.Core.EditorStatisticId left, Inno.Editor.Core.EditorStatisticId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Core.EditorStatisticId.operator ==(Inno.Editor.Core.EditorStatisticId left, Inno.Editor.Core.EditorStatisticId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Core.EditorStatisticId.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Core.EditorStatisticId.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Core.EditorStatisticId.Equals(Inno.Editor.Core.EditorStatisticId other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L10) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly record struct Inno.Editor.Core.EditorStatisticGroupId : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Identifies one visual group in the editor statistics presentation. | — | — | +| [`public Inno.Editor.Core.EditorStatisticGroupId.EditorStatisticGroupId(string value)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L60) | Creates a globally stable statistic-group identifier.
`value`: Non-empty globally stable identity. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Core.EditorStatisticGroupId.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L69) | Gets the globally stable identity. | — | — | +| [`public bool Inno.Editor.Core.EditorStatisticGroupId.isValid { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L74) | Gets whether this identifier contains a usable value. | — | — | +| [`public override string Inno.Editor.Core.EditorStatisticGroupId.ToString()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L82) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static bool Inno.Editor.Core.EditorStatisticGroupId.operator !=(Inno.Editor.Core.EditorStatisticGroupId left, Inno.Editor.Core.EditorStatisticGroupId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Core.EditorStatisticGroupId.operator ==(Inno.Editor.Core.EditorStatisticGroupId left, Inno.Editor.Core.EditorStatisticGroupId right)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Core.EditorStatisticGroupId.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Core.EditorStatisticGroupId.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Core.EditorStatisticGroupId.Equals(Inno.Editor.Core.EditorStatisticGroupId other)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L49) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Core.EditorStatistic`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L88) | Describes one immutable, presentation-ready statistic without retaining provider instances or types. | — | — | +| [`public Inno.Editor.Core.EditorStatistic.EditorStatistic(Inno.Editor.Core.EditorStatisticId id, Inno.Editor.Core.EditorStatisticGroupId groupId, string groupName, string label, string value, int groupOrder = 0, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L117) | Creates one statistic contribution.
`id`: Globally stable statistic identity used for replacement within a frame.
`groupId`: Stable visual group identity.
`groupName`: User-facing group heading.
`label`: User-facing metric label.
`value`: Already formatted user-facing value.
`groupOrder`: Ascending group presentation order.
`order`: Ascending metric order within the group. | — | `System.ArgumentException`: Thrown when an identifier, group name, label, or value is empty. | +| [`public Inno.Editor.Core.EditorStatisticId Inno.Editor.Core.EditorStatistic.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L145) | Gets the globally stable statistic identity. | — | — | +| [`public Inno.Editor.Core.EditorStatisticGroupId Inno.Editor.Core.EditorStatistic.groupId { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L150) | Gets the stable visual group identity. | — | — | +| [`public string Inno.Editor.Core.EditorStatistic.groupName { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L155) | Gets the user-facing group heading. | — | — | +| [`public string Inno.Editor.Core.EditorStatistic.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L160) | Gets the user-facing metric label. | — | — | +| [`public string Inno.Editor.Core.EditorStatistic.value { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L165) | Gets the presentation-ready value. | — | — | +| [`public int Inno.Editor.Core.EditorStatistic.groupOrder { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L170) | Gets ascending group presentation order. | — | — | +| [`public int Inno.Editor.Core.EditorStatistic.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L175) | Gets ascending metric order within the group. | — | — | +| [`public sealed class Inno.Editor.Core.EditorStatistics`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L185) | Exchanges frame-scoped, reload-safe statistics between independent editor features and viewers. | — | — | +| [`public void Inno.Editor.Core.EditorStatistics.Publish(Inno.Editor.Core.EditorStatistic statistic)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L202) | Publishes or replaces one statistic for the current editor frame.
`statistic`: Immutable contribution containing no runtime provider references. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.Core.EditorStatistics.Publish(System.Collections.Generic.IEnumerable statistics)`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L223) | Publishes or replaces several statistics for the current editor frame.
`statistics`: Contributions to publish in enumeration order. | — | `System.ArgumentNullException`: Thrown when or one of its values is null. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Core.EditorStatistics.GetSnapshot()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L238) | Gets a deterministic snapshot combining current contributions with the previous completed frame for providers that draw after their consumer. | An immutable, sorted snapshot containing at most one value for each stable statistic ID. Unchanged content reuses the same snapshot; previously returned snapshots remain valid. | — | +| [`public Inno.Editor.Core.EditorStatistics.EditorStatistics()`](../../src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs#L185) | Inherited or compiler-provided contract. | — | — | ## 项目依赖 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):公开引用边界由实际签名核对。 -- [Inno.Scripting.Reload](../scripting/Inno.Scripting.Reload.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Input](../core/Inno.Core.Input.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 公开签名依赖 | +| `Inno.Scripting.Reload` | 公开签名依赖 | +| `Inno.Extensibility.Reload` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Input` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Diagnostics.md b/docs/editor/Inno.Editor.Diagnostics.md index e62b82ef0..f18371fd4 100644 --- a/docs/editor/Inno.Editor.Diagnostics.md +++ b/docs/editor/Inno.Editor.Diagnostics.md @@ -20,9 +20,7 @@ Console timeline 与 collapse group 都按 occurrence sequence 正序排列: ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Diagnostics.EditorConsole` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -35,51 +33,14 @@ Console timeline 与 collapse group 都按 occurrence sequence 正序排列: - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Diagnostics.EditorConsole.EditorConsole(Inno.Core.Logging.LogRouter logRouter, Inno.Core.Diagnostics.DiagnosticHub diagnosticHub, Inno.Editor.PlayMode.IEditorPlayMode playMode)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L45) | Creates a Console service that observes one editor Play Mode controller.
`logRouter`:The host logging router whose entries are presented by this Console.
`diagnosticHub`:The host diagnostic hub whose current reports are presented by this Console.
`playMode`:The Play Mode source used to clear ordinary logs when a new simulation request begins. | — | `System.ArgumentNullException`:Thrown when playMode is null. | -| [`public Inno.Editor.Diagnostics.EditorConsoleSnapshot Inno.Editor.Diagnostics.EditorConsole.Capture()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L158) | Captures an immutable snapshot containing both individual occurrences and global groups.
返回:A consistent snapshot that remains valid while new entries arrive. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Clear()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L178) | Removes retained logs while keeping currently reported issues visible until their owners resolve them. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Clear(Inno.Core.Diagnostics.DiagnosticSource source)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L143) | Removes the current diagnostic report for one producer.
`source`:The producer whose report should be removed. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L192) | Detaches this service and releases its subscriptions. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Receive(Inno.Core.Logging.LogEntry entry)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L108) | Appends one asynchronously dispatched log entry.
`entry`:The immutable entry to append. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Replace(Inno.Core.Diagnostics.DiagnosticReport report)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L124) | Replaces the complete current diagnostic report for one producer.
`report`:The complete report to expose in subsequent snapshots. | — | — | -| [`public void Inno.Editor.Diagnostics.EditorConsole.Start()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L91) | Attaches this service to the process logging, diagnostic, and Play Mode sources. | — | `System.ObjectDisposedException`:Thrown after this service has been disposed. | -| [`public int Inno.Editor.Diagnostics.EditorConsole.capacity { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L58) | Gets or sets the maximum number of ordinary log occurrences retained in memory. | — | — | -| [`public bool Inno.Editor.Diagnostics.EditorConsole.clearOnPlay { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L83) | Gets or sets whether ordinary Console logs are cleared when a new Play Mode request begins. Current diagnostics remain visible because they represent active compiler and subsystem state. | — | — | -| [`public sealed class Inno.Editor.Diagnostics.EditorConsole : Inno.Editor.Diagnostics.IEditorConsole, Inno.Core.Logging.ILogSink, Inno.Core.Diagnostics.IDiagnosticSink, System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L15) | Owns one editor's bounded Console history, current diagnostics, grouping, and Play Mode clearing policy. | — | — | - -### `Inno.Editor.Diagnostics.EditorConsoleEntryKind` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Diagnostics.EditorConsoleEntryKind.Diagnostic = 1`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L16) | The occurrence represents current state from a diagnostic producer. | — | — | -| [`Inno.Editor.Diagnostics.EditorConsoleEntryKind.Log = 0`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L11) | The occurrence was emitted through the runtime logging pipeline. | — | — | -| [`public enum Inno.Editor.Diagnostics.EditorConsoleEntryKind : int`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L6) | Identifies the source protocol represented by one editor Console occurrence. | — | — | - -### `Inno.Editor.Diagnostics.EditorConsoleGroup` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public int Inno.Editor.Diagnostics.EditorConsoleGroup.count { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L38) | Gets the total number of matching occurrences. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleGroup.identity { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L23) | Gets the deterministic content fingerprint used as the presentation identity. | — | — | -| [`public Inno.Editor.Diagnostics.EditorConsoleOccurrence Inno.Editor.Diagnostics.EditorConsoleGroup.latest { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L33) | Gets the most recently received matching occurrence. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleGroup.occurrences { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L28) | Gets every matching occurrence in arrival order. | — | — | -| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleGroup`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L9) | Groups equivalent Console occurrences regardless of whether they arrived consecutively. | — | — | - -### `Inno.Editor.Diagnostics.EditorConsoleOccurrence` - @@ -96,60 +57,69 @@ Console timeline 与 collapse group 都按 occurrence sequence 正序排列: - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.category { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L77) | Gets the producer-defined category. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.code { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L72) | Gets the diagnostic code, or an empty string for uncoded entries. | — | — | -| [`public int Inno.Editor.Diagnostics.EditorConsoleOccurrence.column { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L107) | Gets the one-based source column, or zero when unavailable. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.displayMessage { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L87) | Gets the message text prefixed by its diagnostic code when one is present. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.file { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L97) | Gets the related source path, or an empty string when unavailable. | — | — | -| [`public Inno.Editor.Diagnostics.EditorConsoleEntryKind Inno.Editor.Diagnostics.EditorConsoleOccurrence.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L52) | Gets the source protocol represented by this occurrence. | — | — | -| [`public Inno.Core.Logging.LogLevel Inno.Editor.Diagnostics.EditorConsoleOccurrence.level { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L57) | Gets the normalized Console severity. | — | — | -| [`public int Inno.Editor.Diagnostics.EditorConsoleOccurrence.line { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L102) | Gets the one-based source line, or zero when unavailable. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L82) | Gets the original message text. | — | — | -| [`public long Inno.Editor.Diagnostics.EditorConsoleOccurrence.sequence { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L47) | Gets the process-local monotonic sequence assigned when this occurrence entered the Console. | — | — | -| [`public Inno.Core.Logging.LogSessionId Inno.Editor.Diagnostics.EditorConsoleOccurrence.sessionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L117) | Gets the isolated runtime session that emitted this occurrence. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L62) | Gets the human-readable producer name. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.sourceId { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L67) | Gets the stable diagnostic producer identifier, or an empty string for log entries. | — | — | -| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.stackTrace { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L112) | Gets the captured stack trace, or an empty string when unavailable. | — | — | -| [`public System.DateTime Inno.Editor.Diagnostics.EditorConsoleOccurrence.time { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L92) | Gets the occurrence timestamp. | — | — | -| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleOccurrence`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L10) | Describes one immutable log or diagnostic occurrence in the editor Console timeline. | — | — | - -### `Inno.Editor.Diagnostics.EditorConsoleSnapshot` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleSnapshot.groups { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L34) | Gets global fingerprint groups ordered by their latest occurrence. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleSnapshot.occurrences { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L29) | Gets every current occurrence in chronological arrival order. | — | — | -| [`public long Inno.Editor.Diagnostics.EditorConsoleSnapshot.revision { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L24) | Gets the monotonic revision of the captured Console state. | — | — | -| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleSnapshot`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L9) | Provides one immutable and internally consistent view of editor Console state. | — | — | - -### `Inno.Editor.Diagnostics.IEditorConsole` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`Inno.Editor.Diagnostics.EditorConsoleSnapshot Inno.Editor.Diagnostics.IEditorConsole.Capture()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L25) | Captures an immutable snapshot containing both individual occurrences and global groups.
返回:A consistent snapshot that remains valid while new entries arrive. | — | — | -| [`void Inno.Editor.Diagnostics.IEditorConsole.Clear()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L30) | Removes retained logs while keeping currently reported issues visible until their owners resolve them. | — | — | +| [`public sealed class Inno.Editor.Diagnostics.EditorConsole : Inno.Editor.Diagnostics.IEditorConsole, Inno.Core.Logging.ILogSink, Inno.Core.Diagnostics.IDiagnosticSink, System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L15) | Owns one editor's bounded Console history, current diagnostics, grouping, and Play Mode clearing policy. | — | — | +| [`public Inno.Editor.Diagnostics.EditorConsole.EditorConsole(Inno.Core.Logging.LogRouter logRouter, Inno.Core.Diagnostics.DiagnosticHub diagnosticHub, Inno.Editor.PlayMode.IEditorPlayMode playMode)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L46) | Creates a Console service that observes one editor Play Mode controller.
`logRouter`: The host logging router whose entries are presented by this Console.
`diagnosticHub`: The host diagnostic hub whose current reports are presented by this Console.
`playMode`: The Play Mode source used to clear ordinary logs when a new simulation request begins. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public int Inno.Editor.Diagnostics.EditorConsole.capacity { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L59) | Gets or sets the maximum number of ordinary log occurrences retained in memory. | — | — | +| [`public bool Inno.Editor.Diagnostics.EditorConsole.clearOnPlay { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L84) | Gets or sets whether ordinary Console logs are cleared when a new Play Mode request begins. Current diagnostics remain visible because they represent active compiler and subsystem state. | — | — | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Start()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L92) | Attaches this service to the process logging, diagnostic, and Play Mode sources. | — | `System.ObjectDisposedException`: Thrown after this service has been disposed. | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Receive(Inno.Core.Logging.LogEntry entry)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L120) | Appends one asynchronously dispatched log entry.
`entry`: The immutable entry to append. | — | — | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Replace(Inno.Core.Diagnostics.DiagnosticReport report)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L136) | Replaces the complete current diagnostic report for one producer.
`report`: The complete report to expose in subsequent snapshots. | — | — | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Clear(Inno.Core.Diagnostics.DiagnosticSource source)`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L155) | Removes the current diagnostic report for one producer.
`source`: The producer whose report should be removed. | — | — | +| [`public Inno.Editor.Diagnostics.EditorConsoleSnapshot Inno.Editor.Diagnostics.EditorConsole.Capture()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L170) | Captures an immutable snapshot containing both individual occurrences and global groups. | A consistent snapshot that remains valid while new entries arrive. | — | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Clear()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L190) | Removes retained logs while keeping currently reported issues visible until their owners resolve them. | — | — | +| [`public void Inno.Editor.Diagnostics.EditorConsole.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs#L204) | Detaches this service and releases its subscriptions. | — | — | +| [`public enum Inno.Editor.Diagnostics.EditorConsoleEntryKind : int`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L6) | Identifies the source protocol represented by one editor Console occurrence. | — | — | +| [`Inno.Editor.Diagnostics.EditorConsoleEntryKind.Log = 0`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L11) | The occurrence was emitted through the runtime logging pipeline. | — | — | +| [`Inno.Editor.Diagnostics.EditorConsoleEntryKind.Diagnostic = 1`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleEntryKind.cs#L16) | The occurrence represents current state from a diagnostic producer. | — | — | +| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleGroup`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L9) | Groups equivalent Console occurrences regardless of whether they arrived consecutively. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleGroup.identity { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L23) | Gets the deterministic content fingerprint used as the presentation identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleGroup.occurrences { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L28) | Gets every matching occurrence in arrival order. | — | — | +| [`public Inno.Editor.Diagnostics.EditorConsoleOccurrence Inno.Editor.Diagnostics.EditorConsoleGroup.latest { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L33) | Gets the most recently received matching occurrence. | — | — | +| [`public int Inno.Editor.Diagnostics.EditorConsoleGroup.count { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleGroup.cs#L38) | Gets the total number of matching occurrences. | — | — | +| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleOccurrence`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L10) | Describes one immutable log or diagnostic occurrence in the editor Console timeline. | — | — | +| [`public long Inno.Editor.Diagnostics.EditorConsoleOccurrence.sequence { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L47) | Gets the process-local monotonic sequence assigned when this occurrence entered the Console. | — | — | +| [`public Inno.Editor.Diagnostics.EditorConsoleEntryKind Inno.Editor.Diagnostics.EditorConsoleOccurrence.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L52) | Gets the source protocol represented by this occurrence. | — | — | +| [`public Inno.Core.Logging.LogLevel Inno.Editor.Diagnostics.EditorConsoleOccurrence.level { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L57) | Gets the normalized Console severity. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L62) | Gets the human-readable producer name. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.sourceId { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L67) | Gets the stable diagnostic producer identifier, or an empty string for log entries. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.code { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L72) | Gets the diagnostic code, or an empty string for uncoded entries. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.category { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L77) | Gets the producer-defined category. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L82) | Gets the original message text. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.displayMessage { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L87) | Gets the message text prefixed by its diagnostic code when one is present. | — | — | +| [`public System.DateTime Inno.Editor.Diagnostics.EditorConsoleOccurrence.time { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L92) | Gets the occurrence timestamp. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.file { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L97) | Gets the related source path, or an empty string when unavailable. | — | — | +| [`public int Inno.Editor.Diagnostics.EditorConsoleOccurrence.line { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L102) | Gets the one-based source line, or zero when unavailable. | — | — | +| [`public int Inno.Editor.Diagnostics.EditorConsoleOccurrence.column { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L107) | Gets the one-based source column, or zero when unavailable. | — | — | +| [`public string Inno.Editor.Diagnostics.EditorConsoleOccurrence.stackTrace { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L112) | Gets the captured stack trace, or an empty string when unavailable. | — | — | +| [`public Inno.Core.Logging.LogSessionId Inno.Editor.Diagnostics.EditorConsoleOccurrence.sessionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleOccurrence.cs#L117) | Gets the isolated runtime session that emitted this occurrence. | — | — | +| [`public sealed class Inno.Editor.Diagnostics.EditorConsoleSnapshot`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L9) | Provides one immutable and internally consistent view of editor Console state. | — | — | +| [`public long Inno.Editor.Diagnostics.EditorConsoleSnapshot.revision { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L24) | Gets the monotonic revision of the captured Console state. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleSnapshot.occurrences { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L29) | Gets every current occurrence in chronological arrival order. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Diagnostics.EditorConsoleSnapshot.groups { get; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsoleSnapshot.cs#L34) | Gets global fingerprint groups ordered by their latest occurrence. | — | — | +| [`public interface Inno.Editor.Diagnostics.IEditorConsole`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L6) | Exposes read-only editor Console snapshots and explicit retention controls to editor features. | — | — | | [`int Inno.Editor.Diagnostics.IEditorConsole.capacity { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L11) | Gets or sets the maximum number of ordinary log occurrences retained in memory. | — | — | | [`bool Inno.Editor.Diagnostics.IEditorConsole.clearOnPlay { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L17) | Gets or sets whether ordinary Console logs are cleared when a new Play Mode request begins. Current diagnostics remain visible because they represent active compiler and subsystem state. | — | — | -| [`public interface Inno.Editor.Diagnostics.IEditorConsole`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L6) | Exposes read-only editor Console snapshots and explicit retention controls to editor features. | — | — | +| [`Inno.Editor.Diagnostics.EditorConsoleSnapshot Inno.Editor.Diagnostics.IEditorConsole.Capture()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L25) | Captures an immutable snapshot containing both individual occurrences and global groups. | A consistent snapshot that remains valid while new entries arrive. | — | +| [`void Inno.Editor.Diagnostics.IEditorConsole.Clear()`](../../src/composition/editor/framework/Inno.Editor.Diagnostics/IEditorConsole.cs#L30) | Removes retained logs while keeping currently reported issues visible until their owners resolve them. | — | — | ## 项目依赖 -- [Inno.Editor.PlayMode](Inno.Editor.PlayMode.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.PlayMode` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Exporting.md b/docs/editor/Inno.Editor.Exporting.md index a5ecc6d76..ed4a3391b 100644 --- a/docs/editor/Inno.Editor.Exporting.md +++ b/docs/editor/Inno.Editor.Exporting.md @@ -32,21 +32,58 @@ Editor/CLI 已完成这个协调;源码工作区中的 Project 用户不需要 - [Inno.Build](../build/Inno.Build.md):公开构建请求、结果与目标边界。 - [Inno.Build.Cli](../build/Inno.Build.Cli.md):使用同一 Build Pipeline 的无界面入口。 -## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +## 异步服务与界面分离 + +EditorExportService 是无界面的导出 owner:StartGame/StartPlugin、Cancel、progress、错误和任务 drain 全部复用 Build pipeline 与 LifetimeScope。Workbench 拥有输入表单与阻塞 modal。服务不引用 ImGui、Panel 或 hosting;关闭时先取消并等待任务释放资源,不能让隐藏窗口丢弃活动导出。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public sealed class Inno.Editor.Exporting.EditorExportService : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L20) | Owns asynchronous export, progress, cancellation and retirement independently from presentation. | — | — | +| [`public bool Inno.Editor.Exporting.EditorExportService.isPluginBusy { get; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L42) | Gets whether a Plugin source export still owns build resources. | — | — | +| [`public bool Inno.Editor.Exporting.EditorExportService.isGameBusy { get; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L46) | Gets whether a game export or its Support Pack preparation is active. | — | — | +| [`public string Inno.Editor.Exporting.EditorExportService.status { get; private set; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L50) | Gets the latest owner-thread progress message. | — | — | +| [`public string Inno.Editor.Exporting.EditorExportService.error { get; private set; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L54) | Gets the last failure or cancellation diagnostic, or an empty string. | — | — | +| [`public float Inno.Editor.Exporting.EditorExportService.gameProgress { get; private set; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L58) | Gets monotonic progress for the current game build, from zero through one. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Exporting.EditorExportService.availableGameTargets { get; }`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L62) | Gets the targets registered by the active Build distribution. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Exporting.EditorExportService.GetManagedDeployments(Inno.Build.BuildTargetId target)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L73) | Gets deployment implementations registered for a target.
`target`: The explicitly chosen build target. | The registered deployment choices; no host fallback is introduced. | — | +| [`public string Inno.Editor.Exporting.EditorExportService.GetGameTargetDisplayName(Inno.Build.BuildTargetId target)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L83) | Gets a registered target label or a diagnostic for an unavailable target.
`target`: The target to describe. | A presentation label; never used as a routing identity. | — | +| [`public void Inno.Editor.Exporting.EditorExportService.StartPlugin(Inno.Build.PluginBuildRequest request)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L96) | Starts a source-package export using the existing Build pipeline.
`request`: The complete, explicitly selected package request. | — | `System.InvalidOperationException`: Another export still owns the service. | +| [`public void Inno.Editor.Exporting.EditorExportService.StartGame(Inno.Build.GameBuildRequest request)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L115) | Prepares the selected Support Pack before capturing and building the game.
`request`: The complete game request; no form or window state is retained. | — | `System.InvalidOperationException`: Another export still owns the service. | +| [`public void Inno.Editor.Exporting.EditorExportService.Cancel()`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L137) | Requests cancellation without releasing resources before their owned work drains. | — | — | +| [`protected override void Inno.Editor.Exporting.EditorExportService.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L157) | Advances compilation tickets, build progress, and completed export tasks.
`context`: The active Editor context for the current frame. | — | — | +| [`protected override void Inno.Editor.Exporting.EditorExportService.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L177) | Cancels and drains active export work before the Editor generation is stopped.
`context`: The active Editor context being stopped. | — | — | +| [`protected override void Inno.Editor.Exporting.EditorExportService.OnDispose()`](../../src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs#L185) | Drains export cleanup before releasing cancellation resources owned by this module. | — | — | ## 项目依赖 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build](../build/Inno.Build.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Managed](../build/Inno.Build.Managed.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Build.Managed` | 公开签名依赖 | +| `Inno.Build` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Graph.ImGui.md b/docs/editor/Inno.Editor.Graph.ImGui.md new file mode 100644 index 000000000..b24af3693 --- /dev/null +++ b/docs/editor/Inno.Editor.Graph.ImGui.md @@ -0,0 +1,182 @@ +# Inno.Editor.Graph.ImGui + +[Editor 索引](README.md) · [Wiki 首页](../README.md) · [UI 架构](../architecture/EDITOR_UI_ARCHITECTURE.md) + +## 职责与边界 + +通用 Graph 画布呈现。依赖中立 Graph/Graphs 与 ImGui;禁止引用 Rendering、Shader 或具体 Panel。 + +## 依赖与初始化 + +产品通过 `EditorProduct.props` 显式部署此程序集。由当前 TypeRegistry/Editor Registry 发现声明并在候选成功后切换;不扫描目录、不建立独立注册表或全局状态。 + +## API 与扩展点 + +`GraphCanvasView` 在构造时复制当前 pan、zoom、画布原点和尺寸;同一帧的投影不会因后续导航修改而漂移。它提供节点、端口、连线、命中与框选的通用呈现,不解释 Shader 或其他图领域语义。 + +`GraphCanvasLayout` 保留由稳定图 ID、位置、尺寸、文字与端口组成的中立快照。`SetNodes` 原子校验候选,按值复用未变化的节点;平移、缩放和拖动预览不要求重建端口几何。长期状态不能包含领域实例、插件 Type 或绘制 callback。 + +`GraphCanvasPointerController` 只管理选择与当前手势的中立预览。`Step` 接收已经完成焦点、遮挡和导航路由的输入,返回移动或连线意图;调用方负责校验、原子领域修改及既有 History。取消不修改文档,完成移动只有一个逻辑 Undo。Shader 消费方在保存、关闭、Play 或 reload 前通过现有 Core Events 操作准备通知完成尚未提交的预览;失败保留可重试输入。 + +## 工作流 + +每次绘制按当前 viewport 创建值对象;同一连接曲线用于绘制与命中测试。屏幕坐标只在当前帧有效。 + +`DrawNode` 和 `DrawConnection` 使用调用方当前窗口的帧内 `EditorDrawList` 借用;方法不保留或释放该借用,每次原生绘制仍验证窗口、帧、线程和 Context。`DrawNode` 的自定义内容只在节点与画布相交时借用执行,返回前恢复本次 clip;异常继续传播。调用方应在该次回调中配对自己的样式和其他作用域。没有原生 UI 帧时仍可调用纯几何、命中及指针契约;实际绘制要求有效的 owner thread、Session 和 ImGui Context。 + +## 错误、生命周期与热重载 + +`GraphCanvasView` 构造函数拒绝空的文档导航状态(`ArgumentNullException`)及非有限或负的画布几何(`ArgumentOutOfRangeException`);它不会修正调用方的非法输入。 + +控件只在当前 EditorUiSession 的 owner thread、作用域与原生上下文中使用。跨帧只保留稳定 ID 和中立状态;Missing、只读和未赋值状态保持区别。手势失败保留 History 所有权和诊断;扩展在所属 generation 退休,不保留插件 Type、delegate 或实例。 + +所有公开签名的当前映射见本页 API 清单;内部实现不构成稳定扩展契约。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public sealed class Inno.Editor.Graph.ImGui.GraphCanvasLayout`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs#L12) | Owns a document's neutral node display snapshot, independently of viewport transforms and domain instances. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Graph.ImGui.GraphCanvasLayout.nodes { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs#L20) | Gets nodes in draw order; reverse order determines overlapping-node hit priority. | — | — | +| [`public int Inno.Editor.Graph.ImGui.GraphCanvasLayout.SetNodes(System.Collections.Generic.IEnumerable nodes)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs#L37) | Atomically replaces a complete display snapshot, reusing unchanged node geometry by value.
`nodes`: The current neutral node descriptions in draw order. | The number of inserted, changed or removed nodes. Ordering changes do not rebuild geometry. | `System.ArgumentNullException`: The collection or one of its nodes is null.
`System.ArgumentException`: A node identity occurs more than once; the previous snapshot remains active. | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasLayout.TryGetNode(Inno.Core.Graphs.GraphNodeId id, out Inno.Editor.Graph.ImGui.GraphCanvasNode? node)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs#L74) | Resolves a node in this display snapshot without consulting domain or live-object registries.
`id`: The graph node identity to resolve.
`node`: The current neutral node, or null when it is absent. | True when the current snapshot contains the node. | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasLayout.GraphCanvasLayout()`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Graph.ImGui.GraphCanvasNode`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L12) | Freezes one node's geometric and textual presentation without owning its domain record. | — | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasNode.GraphCanvasNode(Inno.Core.Graphs.GraphNodeId id, Inno.Core.Graphs.GraphPosition position, System.Numerics.Vector2 size, float headerHeight, string title, System.Collections.Generic.IEnumerable ports)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L44) | Creates immutable display data that can safely outlive the extension that described it.
`id`: The existing graph node identity.
`position`: The node's document position in graph coordinates.
`size`: Finite positive width and height in graph coordinates.
`headerHeight`: The header extent, within the node height.
`title`: A display title already interpreted by its domain owner.
`ports`: Immutable neutral endpoint descriptions; the collection is copied. | — | `System.ArgumentNullException`: The title or port collection is null.
`System.ArgumentException`: A node or port identity is invalid, duplicated or owned by a different node.
`System.ArgumentOutOfRangeException`: The node's geometry is nonfinite or has an invalid extent. | +| [`public Inno.Core.Graphs.GraphNodeId Inno.Editor.Graph.ImGui.GraphCanvasNode.id { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L76) | Gets the stable graph identity represented by this display snapshot. | — | — | +| [`public Inno.Core.Graphs.GraphPosition Inno.Editor.Graph.ImGui.GraphCanvasNode.position { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L81) | Gets the document position, before transient gesture previews. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Graph.ImGui.GraphCanvasNode.size { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L86) | Gets the unscaled node extent. | — | — | +| [`public float Inno.Editor.Graph.ImGui.GraphCanvasNode.headerHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L91) | Gets the unscaled header extent used for node movement hit testing. | — | — | +| [`public string Inno.Editor.Graph.ImGui.GraphCanvasNode.title { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L96) | Gets the domain-supplied display title. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Graph.ImGui.GraphCanvasNode.ports { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs#L101) | Gets copied neutral port descriptions; no callback or domain object is retained. | — | — | +| [`public sealed class Inno.Editor.Graph.ImGui.GraphCanvasPointerController`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L14) | Coordinates neutral selection, movement previews, box selection and connection intents for one canvas. | — | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasPointerController.GraphCanvasPointerController(Inno.Editor.Graph.GraphCanvasState state)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L34) | Creates a pointer owner over existing document navigation and selection state.
`state`: The existing canvas state, which remains the selection and pending-connection authority. | — | `System.ArgumentNullException`: The state is null. | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Editor.Graph.ImGui.GraphCanvasPointerController.previewPositions { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L44) | Gets frame-local movement previews; callers must not persist this changing view. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerController.isMoving { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L49) | Gets whether a node movement preview is active. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerController.isBoxSelecting { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L54) | Gets whether a selection box is active. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Graph.ImGui.GraphCanvasPointerController.boxStart { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L59) | Gets the initial screen-space corner of the current selection box. | — | — | +| [`public void Inno.Editor.Graph.ImGui.GraphCanvasPointerController.Cancel()`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L64) | Cancels transient presentation state without applying or undoing document changes. | — | — | +| [`public void Inno.Editor.Graph.ImGui.GraphCanvasPointerController.BeginMove(Inno.Editor.Graph.ImGui.GraphCanvasLayout layout, System.Collections.Generic.IEnumerable nodes, System.Numerics.Vector2 pointer)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L85) | Starts movement for explicit node IDs, supporting domain-owned group-header composition.
`layout`: The neutral snapshot supplying initial positions.
`nodes`: Selected stable graph identities; absent nodes remain selected but are not moved.
`pointer`: The screen-space gesture start. | — | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasPointerResult Inno.Editor.Graph.ImGui.GraphCanvasPointerController.Step(Inno.Editor.Graph.ImGui.GraphCanvasView view, Inno.Editor.Graph.ImGui.GraphCanvasLayout layout, Inno.Editor.Graph.ImGui.GraphCanvasPointerInput input)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs#L121) | Consumes one routed frame and returns data-editing intents without invoking domain code.
`view`: The current canvas projection.
`layout`: The immutable neutral node snapshot used by drawing and hit testing.
`input`: Current pointer input after the caller's focus, navigation and overlay routing. | Completed neutral intents. The feature owns validation, atomic mutation and History recording. | `System.ArgumentOutOfRangeException`: The pointer position is nonfinite. | +| [`public readonly struct Inno.Editor.Graph.ImGui.GraphCanvasPointerInput`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L8) | Carries one frame's already-routed pointer input without depending on a native backend. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.position { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L13) | Gets the screen-space pointer position. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.isHovered { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L18) | Gets whether this canvas owns the pointer for starting an interaction. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.isPressed { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L23) | Gets whether the primary pointer button was pressed this frame. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.isDown { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L28) | Gets whether the primary pointer button remains held. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.isReleased { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L33) | Gets whether the primary pointer button was released this frame. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.shift { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L38) | Gets whether selection extension is requested. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.cancel { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L43) | Gets whether the current preview must be canceled without modifying document data. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPointerInput.readOnly { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs#L48) | Gets whether selection is allowed while data-editing intents are prohibited. | — | — | +| [`public readonly struct Inno.Editor.Graph.ImGui.GraphCanvasPointerResult`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L9) | Reports neutral interaction intents for the feature controller to validate and apply through History. | — | — | +| [`public Inno.Core.Graphs.GraphNodeId? Inno.Editor.Graph.ImGui.GraphCanvasPointerResult.nodePressed { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L28) | Gets the pressed node header, or null when no header was pressed. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary? Inno.Editor.Graph.ImGui.GraphCanvasPointerResult.movedNodes { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L33) | Gets completed movement positions, or null when no data edit was requested. | — | — | +| [`public Inno.Core.Graphs.GraphEndpoint? Inno.Editor.Graph.ImGui.GraphCanvasPointerResult.output { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L38) | Gets a requested connection's output endpoint; its domain type still requires validation. | — | — | +| [`public Inno.Core.Graphs.GraphEndpoint? Inno.Editor.Graph.ImGui.GraphCanvasPointerResult.input { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L43) | Gets a requested connection's input endpoint, paired with the output endpoint. | — | — | +| [`public Inno.Core.Graphs.GraphEndpoint? Inno.Editor.Graph.ImGui.GraphCanvasPointerResult.releasedOutput { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs#L48) | Gets an output released over empty canvas, allowing the feature to offer a creation workflow. | — | — | +| [`public readonly record struct Inno.Editor.Graph.ImGui.GraphCanvasPort : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Describes a neutral port's node-relative geometry and display state without retaining extensions. | — | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasPort.GraphCanvasPort(Inno.Core.Graphs.GraphEndpoint endpoint, Inno.Core.Graphs.GraphPortDirection direction, System.Numerics.Vector2 position, string label, bool isOptional = false, bool isMissing = false, (System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum)? labelBounds = null)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L45) | Creates a port presentation whose position is measured in unscaled node coordinates.
`endpoint`: The existing document endpoint represented by this port.
`direction`: The connection direction, independent of any domain-specific type system.
`position`: Node-relative graph coordinates of the port center.
`label`: The current display label, without domain objects or callbacks.
`isOptional`: Whether to distinguish an optional input visually.
`isMissing`: Whether retained document structure currently lacks its domain implementation.
`labelBounds`: Optional node-relative label bounds used by drawing and row hit testing; null limits hits to the socket. | — | `System.ArgumentException`: An endpoint identity is empty.
`System.ArgumentNullException`: The label is null.
`System.ArgumentOutOfRangeException`: The position or direction is invalid. | +| [`public Inno.Core.Graphs.GraphEndpoint Inno.Editor.Graph.ImGui.GraphCanvasPort.endpoint { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L77) | Gets the document endpoint; it does not identify a live engine object. | — | — | +| [`public Inno.Core.Graphs.GraphPortDirection Inno.Editor.Graph.ImGui.GraphCanvasPort.direction { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L82) | Gets the neutral connection direction. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Graph.ImGui.GraphCanvasPort.position { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L87) | Gets the node-relative center in graph coordinates. | — | — | +| [`public string Inno.Editor.Graph.ImGui.GraphCanvasPort.label { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L92) | Gets the current display label. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPort.isOptional { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L97) | Gets whether the input is visually optional. | — | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPort.isMissing { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L102) | Gets whether this retained endpoint currently lacks an implementation. | — | — | +| [`public (System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum)? Inno.Editor.Graph.ImGui.GraphCanvasPort.labelBounds { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L107) | Gets optional node-relative label geometry shared by rendering and row hit testing. | — | — | +| [`public override string Inno.Editor.Graph.ImGui.GraphCanvasPort.ToString()`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Graph.ImGui.GraphCanvasPort.operator !=(Inno.Editor.Graph.ImGui.GraphCanvasPort left, Inno.Editor.Graph.ImGui.GraphCanvasPort right)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Graph.ImGui.GraphCanvasPort.operator ==(Inno.Editor.Graph.ImGui.GraphCanvasPort left, Inno.Editor.Graph.ImGui.GraphCanvasPort right)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Graph.ImGui.GraphCanvasPort.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Graph.ImGui.GraphCanvasPort.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasPort.Equals(Inno.Editor.Graph.ImGui.GraphCanvasPort other)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs#L10) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly struct Inno.Editor.Graph.ImGui.GraphCanvasView`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L12) | Projects and presents an arbitrary graph viewport without owning document mutations or domain semantics. | — | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasView.GraphCanvasView(Inno.Editor.Graph.GraphCanvasState state, System.Numerics.Vector2 origin, System.Numerics.Vector2 size)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L34) | Creates a frame-local graph presenter over navigation owned by its document.
`state`: The existing neutral navigation and selection state.
`origin`: The screen-space canvas origin.
`size`: The finite nonnegative canvas extent. | — | `System.ArgumentOutOfRangeException`: The canvas geometry is invalid. | +| [`public float Inno.Editor.Graph.ImGui.GraphCanvasView.zoom { get; }`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L56) | Gets the graph-to-screen zoom captured for this frame, independently of later navigation changes. | — | `System.InvalidOperationException`: The view was not initialized through its constructor. | +| [`public System.Numerics.Vector2 Inno.Editor.Graph.ImGui.GraphCanvasView.ToScreen(Inno.Core.Graphs.GraphPosition point)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L67) | Projects a graph-space location into the current screen rectangle.
`point`: The domain-independent graph coordinate. | The corresponding screen coordinate. | — | +| [`public Inno.Core.Graphs.GraphPosition Inno.Editor.Graph.ImGui.GraphCanvasView.ToGraph(System.Numerics.Vector2 point)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L77) | Converts a screen location back into graph coordinates.
`point`: The screen coordinate to project. | The graph coordinate under the pointer. | — | +| [`public void Inno.Editor.Graph.ImGui.GraphCanvasView.DrawGrid(uint color, float spacing = 32)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L90) | Draws a bounded graph-space grid through the current UI drawing context.
`color`: The packed presentation color.
`spacing`: Positive graph-space grid spacing. | — | `System.ArgumentOutOfRangeException`: Spacing is nonpositive or nonfinite. | +| [`public void Inno.Editor.Graph.ImGui.GraphCanvasView.DrawConnection(Inno.Editor.ImGui.EditorDrawList draw, System.Numerics.Vector2 output, System.Numerics.Vector2 input, uint color, float thickness = 2)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L127) | Draws a horizontal-tangent connection using the same curve used for hit testing.
`draw`: The current window's frame-local drawing borrow; it is neither retained nor released by this call.
`output`: The output endpoint in screen space.
`input`: The input endpoint in screen space.
`color`: The packed connection color.
`thickness`: The positive screen-space stroke width. | — | `System.ArgumentOutOfRangeException`: An endpoint or derived control point is nonfinite, or the stroke width is not positive and finite.
`System.InvalidOperationException`: The canvas is uninitialized or a visible connection uses an expired or mismatched drawing borrow. | +| [`public static bool Inno.Editor.Graph.ImGui.GraphCanvasView.HitConnection(System.Numerics.Vector2 point, System.Numerics.Vector2 output, System.Numerics.Vector2 input, float radius = 6)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs#L184) | Checks pointer distance against the displayed cubic connection without any UI or document access.
`point`: The screen-space pointer location.
`output`: The displayed output endpoint.
`input`: The displayed input endpoint.
`radius`: Nonnegative screen-space acceptance radius. | True when the pointer is within the curve's hit area, including its endpoints. | `System.ArgumentOutOfRangeException`: Radius is negative or nonfinite. | +| [`public (System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum) Inno.Editor.Graph.ImGui.GraphCanvasView.NodeBounds(Inno.Editor.Graph.ImGui.GraphCanvasNode node, Inno.Core.Graphs.GraphPosition? previewPosition = null)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs#L27) | Projects neutral node geometry, optionally substituting a transient drag position.
`node`: The immutable display node.
`previewPosition`: An uncommitted gesture position, or null to use the document position. | The screen-space node bounds. | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasNode? Inno.Editor.Graph.ImGui.GraphCanvasView.HitNode(Inno.Editor.Graph.ImGui.GraphCanvasLayout layout, System.Numerics.Vector2 point)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs#L48) | Finds the frontmost node beneath a pointer using the same geometry as drawing.
`layout`: The current document presentation snapshot.
`point`: The screen-space pointer. | The frontmost neutral node, or null over empty canvas. | — | +| [`public Inno.Editor.Graph.ImGui.GraphCanvasPort? Inno.Editor.Graph.ImGui.GraphCanvasView.HitPort(Inno.Editor.Graph.ImGui.GraphCanvasLayout layout, System.Numerics.Vector2 point, float radius = 8)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs#L81) | Finds a port by its displayed socket or label row, without interpreting its domain type.
`layout`: The current document presentation snapshot.
`point`: The screen-space pointer.
`radius`: The nonnegative minimum socket hit radius in screen pixels. | The hit neutral port, including Missing state, or null over non-port content. | `System.ArgumentOutOfRangeException`: The radius is negative or nonfinite. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Graph.ImGui.GraphCanvasView.BoxNodes(Inno.Editor.Graph.ImGui.GraphCanvasLayout layout, System.Numerics.Vector2 first, System.Numerics.Vector2 second)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs#L124) | Finds nodes intersecting a screen-space selection rectangle in document draw order.
`layout`: The current neutral display snapshot.
`first`: One corner of the selection box.
`second`: The opposite corner, in any drag direction. | Stable graph node IDs; no live domain objects are returned. | — | +| [`public bool Inno.Editor.Graph.ImGui.GraphCanvasView.DrawNode(Inno.Editor.ImGui.EditorDrawList draw, Inno.Editor.Graph.ImGui.GraphCanvasNode node, uint headerColor, bool selected, System.Action? drawContent = null, Inno.Core.Graphs.GraphPosition? previewPosition = null)`](../../src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs#L173) | Draws a visible neutral node and its ports, borrowing optional content only for this call.
`draw`: The current window's frame-local drawing borrow; it is neither retained nor released by this call.
`node`: The immutable geometric and textual node input.
`headerColor`: The domain-selected packed header color; the presenter does not classify node types.
`selected`: Whether to display the current selection outline.
`drawContent`: Optional frame-local custom drawing. Exceptions propagate after restoring the node clip.
`previewPosition`: An uncommitted movement position, or null to draw the document position. | True when the node intersects the canvas and effective window clip and was submitted; false when culled. | `System.ArgumentNullException`: The node is null.
`System.InvalidOperationException`: The canvas is uninitialized or a visible node uses an expired or mismatched drawing borrow. | + +## 项目依赖 + +| 工程 | 边界 | +| --- | --- | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 公开签名依赖 | +| `Inno.Core.Graphs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Editor.Graph` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Graph.md b/docs/editor/Inno.Editor.Graph.md index 39544fe2d..18142fc55 100644 --- a/docs/editor/Inno.Editor.Graph.md +++ b/docs/editor/Inno.Editor.Graph.md @@ -31,9 +31,7 @@ GraphDocument 是可编辑创作模型,不是假称不可变的运行快照。 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Graph.GraphCanvasState` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -52,41 +50,9 @@ GraphDocument 是可编辑创作模型,不是假称不可变的运行快照。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Graph.GraphCanvasState.GraphCanvasState()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L12) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.BeginConnection(Inno.Core.Graphs.GraphEndpoint endpoint, Inno.Core.Graphs.GraphPortDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L164) | Begins a connection drag from one endpoint.
`endpoint`:Stable endpoint.
`direction`:Direction of the endpoint within its node interface. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.BeginConnection(Inno.Core.Graphs.GraphEndpoint output)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L178) | Begins a connection drag from one output endpoint.
`output`:Stable output endpoint. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.CancelConnection()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L183) | Cancels any active connection drag. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.ClearSelection()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L147) | Clears all transient node and edge selection. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.PanBy(float deltaX, float deltaY)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L82) | Moves the canvas origin by screen-space pixels.
`deltaX`:Horizontal screen-space delta.
`deltaY`:Vertical screen-space delta. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.SelectNodes(System.Collections.Generic.IEnumerable nodes)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L121) | Replaces node selection.
`nodes`:Complete selected-node set. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.SetViewport(Inno.Core.Graphs.GraphPosition pan, float zoom)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L58) | Restores persistent canvas navigation state with bounded zoom.
`pan`:Graph-space pan offset.
`zoom`:Requested zoom in the inclusive range 0.1 to 4. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.ToggleNode(Inno.Core.Graphs.GraphNodeId nodeId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L135) | Toggles one node while preserving the remaining selection.
`nodeId`:Node to toggle. | — | — | -| [`public void Inno.Editor.Graph.GraphCanvasState.ZoomAt(float factor, float pivotX, float pivotY)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L99) | Changes zoom while preserving the graph point beneath a screen-space pivot.
`factor`:Positive multiplicative zoom factor.
`pivotX`:Screen-space pivot X.
`pivotY`:Screen-space pivot Y. | — | — | -| [`public Inno.Core.Graphs.GraphPosition Inno.Editor.Graph.GraphCanvasState.pan { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L22) | Gets the graph-space origin mapped to canvas screen origin. | — | — | -| [`public Inno.Core.Graphs.GraphEndpoint? Inno.Editor.Graph.GraphCanvasState.pendingConnection { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L42) | Gets the endpoint currently being connected, or null. | — | — | -| [`public Inno.Core.Graphs.GraphPortDirection? Inno.Editor.Graph.GraphCanvasState.pendingConnectionDirection { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L47) | Gets the direction of Inno.Editor.Graph.GraphCanvasState.pendingConnection. | — | — | -| [`public System.Collections.Generic.IReadOnlyCollection Inno.Editor.Graph.GraphCanvasState.selectedEdges { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L37) | Gets selected edge identities. | — | — | -| [`public System.Collections.Generic.IReadOnlyCollection Inno.Editor.Graph.GraphCanvasState.selectedNodes { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L32) | Gets selected node identities. | — | — | -| [`public float Inno.Editor.Graph.GraphCanvasState.zoom { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L27) | Gets the current graph-to-screen scale. | — | — | -| [`public sealed class Inno.Editor.Graph.GraphCanvasState`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L12) | Stores transient pan, zoom, selection and connection interaction state without ImGui dependencies. | — | — | - -### `Inno.Editor.Graph.GraphClipboardData` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public int Inno.Editor.Graph.GraphClipboardData.edgeCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L28) | Gets the copied connection count whose endpoints both belong to the fragment. | — | — | -| [`public int Inno.Editor.Graph.GraphClipboardData.nodeCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L23) | Gets the copied node count. | — | — | -| [`public sealed class Inno.Editor.Graph.GraphClipboardData`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L14) | Contains a neutral copied node fragment suitable for graph clipboard operations. | — | — | - -### `Inno.Editor.Graph.GraphDocumentController` - @@ -104,29 +70,6 @@ GraphDocument 是可编辑创作模型,不是假称不可变的运行快照。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Core.Graphs.GraphNodeId Inno.Editor.Graph.GraphDocumentController.AddNode(string definitionId, Inno.Core.Graphs.GraphPosition position, System.Collections.Generic.IReadOnlyDictionary? values = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L122) | Adds a node with a generated stable identity.
返回:The new stable node identity.
`definitionId`:Stable node definition ID.
`position`:Initial graph-space position.
`values`:Optional neutral property values. | — | — | -| [`public Inno.Core.Graphs.GraphEdgeId Inno.Editor.Graph.GraphDocumentController.Connect(Inno.Core.Graphs.GraphEndpoint output, Inno.Core.Graphs.GraphEndpoint input)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L217) | Creates or reconnects one input endpoint to an output endpoint.
返回:The generated connection identity.
`output`:Source endpoint.
`input`:Destination endpoint. | — | — | -| [`public Inno.Editor.Graph.GraphClipboardData Inno.Editor.Graph.GraphDocumentController.Copy(System.Collections.Generic.IEnumerable nodeIds)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L331) | Copies selected nodes and only their internal connections.
返回:A detached neutral clipboard fragment.
`nodeIds`:Nodes to copy. | — | — | -| [`public bool Inno.Editor.Graph.GraphDocumentController.Disconnect(Inno.Core.Graphs.GraphEdgeId edgeId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L254) | Removes one stable connection.
返回:true after a connection was removed.
`edgeId`:Connection to remove. | — | — | -| [`public void Inno.Editor.Graph.GraphDocumentController.MarkSaved()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L82) | Marks the current revision as saved without creating a data History entry. | — | — | -| [`public void Inno.Editor.Graph.GraphDocumentController.MoveNodes(System.Collections.Generic.IReadOnlyDictionary positions)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L180) | Moves one or more nodes as one completed gesture, independently undoable from earlier drags.
`positions`:Complete destination positions keyed by node identity. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Graph.GraphDocumentController.Paste(Inno.Editor.Graph.GraphClipboardData clipboard, Inno.Core.Graphs.GraphPosition offset, System.Action>? remapNode = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L365) | Pastes a clipboard fragment with new stable identities and a graph-space offset.
返回:New node identities in clipboard document order.
`clipboard`:Detached neutral fragment.
`offset`:Offset added to every copied node position.
`remapNode`:Optional synchronous domain remapping of neutral node values after identity allocation. The callback is never stored in History. | — | — | -| [`public void Inno.Editor.Graph.GraphDocumentController.RemoveNodes(System.Collections.Generic.IEnumerable nodeIds)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L151) | Removes nodes and all incident connections as one structural edit.
`nodeIds`:Nodes to remove. | — | — | -| [`public bool Inno.Editor.Graph.GraphDocumentController.RemoveNodeValue(Inno.Core.Graphs.GraphNodeId nodeId, string propertyId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L307) | Removes one neutral node property as a structural edit.
返回:true when the property existed.
`nodeId`:Owning node.
`propertyId`:Stable property ID. | — | — | -| [`public void Inno.Editor.Graph.GraphDocumentController.ReplaceDocument(Inno.Core.Graphs.GraphDocument replacement, string historyName, string? gestureId = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L96) | Replaces the complete neutral document as one atomic, undoable authoring operation.
`replacement`:Complete replacement document copied by the operation.
`historyName`:Concise artist-facing history entry name.
`gestureId`:Unique active gesture identity for continuous samples, or null for an independent edit. | — | — | -| [`public void Inno.Editor.Graph.GraphDocumentController.SetNodeValue(Inno.Core.Graphs.GraphNodeId nodeId, string propertyId, Inno.Core.Graphs.GraphSerializedValue value, string? gestureId = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L280) | Creates or replaces one neutral node property; only samples carrying the same explicit gesture identity merge.
`nodeId`:Owning node.
`propertyId`:Stable property ID.
`value`:New neutral value.
`gestureId`:Unique current text/numeric gesture identity, or null for an independent edit. | — | — | -| [`public Inno.Core.Graphs.GraphDocument Inno.Editor.Graph.GraphDocumentController.document { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L67) | Gets the mutable neutral document used by the current asset generation. | — | — | -| [`public System.Guid Inno.Editor.Graph.GraphDocumentController.documentId { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L57) | Gets the stable project-relative document identity. | — | — | -| [`public bool Inno.Editor.Graph.GraphDocumentController.isAvailable { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L62) | Gets whether the current document identity and source are both available. | — | — | -| [`public bool Inno.Editor.Graph.GraphDocumentController.isDirty { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L77) | Gets whether content changed after its last explicit saved marker. | — | — | -| [`public ulong Inno.Editor.Graph.GraphDocumentController.revision { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L72) | Gets the monotonic in-session content revision. | — | — | -| [`public sealed class Inno.Editor.Graph.GraphDocumentController`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L36) | Applies neutral graph edits atomically and records every data mutation in reload-safe Editor History. | — | — | - -### `Inno.Editor.Graph.GraphEditorModule` - @@ -137,27 +80,66 @@ GraphDocument 是可编辑创作模型,不是假称不可变的运行快照。 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public bool Inno.Editor.Graph.GraphEditorModule.CloseDocument(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L89) | Closes a document explicitly; its neutral History entries remain unavailable until the identity is reopened.
返回:True when a live document was closed.
`documentId`:The persistent document identity to release. | — | — | -| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L208) | Unregisters every document identity so issued controllers cannot resolve retired sessions. | — | — | -| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L191) | Initializes this feature when its owning runtime becomes active.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L199) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public Inno.Editor.Graph.GraphDocumentController Inno.Editor.Graph.GraphEditorModule.OpenDocument(System.Guid documentId, Inno.Core.Graphs.GraphDocument document, Inno.Editor.Interactions.IEditorHistory history)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L57) | Opens or joins an active graph document session.
返回:A presentation-independent document controller.
`documentId`:Persistent document identity; use the Asset identity for asset-backed graphs.
`document`:Mutable neutral document owned by the current asset generation.
`history`:Shared editor history used for every data mutation. | — | `System.InvalidOperationException`:The bounded document owner is full. | -| [`public void Inno.Editor.Graph.GraphEditorModule.RebindDocument(System.Guid documentId, Inno.Core.Graphs.GraphDocument document)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L156) | Rebinds an active session to a newly imported current-generation document.
`documentId`:Stable document identity.
`document`:New current-generation document instance. | — | `System.InvalidOperationException`:Thrown when the document has no active session. | -| [`public void Inno.Editor.Graph.GraphEditorModule.SetAvailability(System.Guid documentId, bool available)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L135) | Marks a temporarily Missing source without discarding its graph or advancing its History stack.
`documentId`:The persistent identity of an open document.
`available`:Whether its source can be resolved in the current generation. | — | — | -| [`public bool Inno.Editor.Graph.GraphEditorModule.TryOpenDocument(System.Guid documentId, Inno.Editor.Interactions.IEditorHistory history, out Inno.Editor.Graph.GraphDocumentController? controller)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L115) | Resolves an already open document without replacing its authored contents.
返回:True when the document is still owned by this module.
`documentId`:The persistent document identity.
`history`:The shared history owner for the returned controller.
`controller`:The joined controller, or null when the document is closed. | — | — | +| [`public sealed class Inno.Editor.Graph.GraphCanvasState`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L12) | Stores transient pan, zoom, selection and connection interaction state without ImGui dependencies. | — | — | +| [`public Inno.Core.Graphs.GraphPosition Inno.Editor.Graph.GraphCanvasState.pan { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L22) | Gets the graph-space origin mapped to canvas screen origin. | — | — | +| [`public float Inno.Editor.Graph.GraphCanvasState.zoom { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L27) | Gets the current graph-to-screen scale. | — | — | +| [`public System.Collections.Generic.IReadOnlyCollection Inno.Editor.Graph.GraphCanvasState.selectedNodes { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L32) | Gets selected node identities. | — | — | +| [`public System.Collections.Generic.IReadOnlyCollection Inno.Editor.Graph.GraphCanvasState.selectedEdges { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L37) | Gets selected edge identities. | — | — | +| [`public Inno.Core.Graphs.GraphEndpoint? Inno.Editor.Graph.GraphCanvasState.pendingConnection { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L42) | Gets the endpoint currently being connected, or . | — | — | +| [`public Inno.Core.Graphs.GraphPortDirection? Inno.Editor.Graph.GraphCanvasState.pendingConnectionDirection { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L47) | Gets the direction of . | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.SetViewport(Inno.Core.Graphs.GraphPosition pan, float zoom)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L58) | Restores persistent canvas navigation state with bounded zoom.
`pan`: Graph-space pan offset.
`zoom`: Requested zoom in the inclusive range 0.1 to 4. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.PanBy(float deltaX, float deltaY)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L82) | Moves the canvas origin by screen-space pixels.
`deltaX`: Horizontal screen-space delta.
`deltaY`: Vertical screen-space delta. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.ZoomAt(float factor, float pivotX, float pivotY)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L99) | Changes zoom while preserving the graph point beneath a screen-space pivot.
`factor`: Positive multiplicative zoom factor.
`pivotX`: Screen-space pivot X.
`pivotY`: Screen-space pivot Y. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.SelectNodes(System.Collections.Generic.IEnumerable nodes)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L121) | Replaces node selection.
`nodes`: Complete selected-node set. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.ToggleNode(Inno.Core.Graphs.GraphNodeId nodeId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L135) | Toggles one node while preserving the remaining selection.
`nodeId`: Node to toggle. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.ClearSelection()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L147) | Clears all transient node and edge selection. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.BeginConnection(Inno.Core.Graphs.GraphEndpoint endpoint, Inno.Core.Graphs.GraphPortDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L164) | Begins a connection drag from one endpoint.
`endpoint`: Stable endpoint.
`direction`: Direction of the endpoint within its node interface. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.BeginConnection(Inno.Core.Graphs.GraphEndpoint output)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L178) | Begins a connection drag from one output endpoint.
`output`: Stable output endpoint. | — | — | +| [`public void Inno.Editor.Graph.GraphCanvasState.CancelConnection()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L183) | Cancels any active connection drag. | — | — | +| [`public Inno.Editor.Graph.GraphCanvasState.GraphCanvasState()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphCanvasState.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Graph.GraphClipboardData`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L14) | Contains a neutral copied node fragment suitable for graph clipboard operations. | — | — | +| [`public int Inno.Editor.Graph.GraphClipboardData.nodeCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L23) | Gets the copied node count. | — | — | +| [`public int Inno.Editor.Graph.GraphClipboardData.edgeCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L28) | Gets the copied connection count whose endpoints both belong to the fragment. | — | — | +| [`public sealed class Inno.Editor.Graph.GraphDocumentController`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L36) | Applies neutral graph edits atomically and records every data mutation in reload-safe Editor History. | — | — | +| [`public System.Guid Inno.Editor.Graph.GraphDocumentController.documentId { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L57) | Gets the stable project-relative document identity. | — | — | +| [`public bool Inno.Editor.Graph.GraphDocumentController.isAvailable { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L62) | Gets whether the current document identity and source are both available. | — | — | +| [`public Inno.Core.Graphs.GraphDocument Inno.Editor.Graph.GraphDocumentController.document { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L67) | Gets the mutable neutral document used by the current asset generation. | — | — | +| [`public ulong Inno.Editor.Graph.GraphDocumentController.revision { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L72) | Gets the monotonic in-session content revision. | — | — | +| [`public bool Inno.Editor.Graph.GraphDocumentController.isDirty { get; }`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L77) | Gets whether content changed after its last explicit saved marker. | — | — | +| [`public void Inno.Editor.Graph.GraphDocumentController.MarkSaved()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L82) | Marks the current revision as saved without creating a data History entry. | — | — | +| [`public void Inno.Editor.Graph.GraphDocumentController.ReplaceDocument(Inno.Core.Graphs.GraphDocument replacement, string historyName, string? gestureId = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L96) | Replaces the complete neutral document as one atomic, undoable authoring operation.
`replacement`: Complete replacement document copied by the operation.
`historyName`: Concise artist-facing history entry name.
`gestureId`: Unique active gesture identity for continuous samples, or null for an independent edit. | — | — | +| [`public Inno.Core.Graphs.GraphNodeId Inno.Editor.Graph.GraphDocumentController.AddNode(string definitionId, Inno.Core.Graphs.GraphPosition position, System.Collections.Generic.IReadOnlyDictionary? values = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L122) | Adds a node with a generated stable identity.
`definitionId`: Stable node definition ID.
`position`: Initial graph-space position.
`values`: Optional neutral property values. | The new stable node identity. | — | +| [`public void Inno.Editor.Graph.GraphDocumentController.RemoveNodes(System.Collections.Generic.IEnumerable nodeIds)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L151) | Removes nodes and all incident connections as one structural edit.
`nodeIds`: Nodes to remove. | — | — | +| [`public void Inno.Editor.Graph.GraphDocumentController.MoveNodes(System.Collections.Generic.IReadOnlyDictionary positions)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L180) | Moves one or more nodes as one completed gesture, independently undoable from earlier drags.
`positions`: Complete destination positions keyed by node identity. | — | — | +| [`public Inno.Core.Graphs.GraphEdgeId Inno.Editor.Graph.GraphDocumentController.Connect(Inno.Core.Graphs.GraphEndpoint output, Inno.Core.Graphs.GraphEndpoint input)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L217) | Creates or reconnects one input endpoint to an output endpoint.
`output`: Source endpoint.
`input`: Destination endpoint. | The generated connection identity. | — | +| [`public bool Inno.Editor.Graph.GraphDocumentController.Disconnect(Inno.Core.Graphs.GraphEdgeId edgeId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L254) | Removes one stable connection.
`edgeId`: Connection to remove. | after a connection was removed. | — | +| [`public void Inno.Editor.Graph.GraphDocumentController.SetNodeValue(Inno.Core.Graphs.GraphNodeId nodeId, string propertyId, Inno.Core.Graphs.GraphSerializedValue value, string? gestureId = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L280) | Creates or replaces one neutral node property; only samples carrying the same explicit gesture identity merge.
`nodeId`: Owning node.
`propertyId`: Stable property ID.
`value`: New neutral value.
`gestureId`: Unique current text/numeric gesture identity, or null for an independent edit. | — | — | +| [`public bool Inno.Editor.Graph.GraphDocumentController.RemoveNodeValue(Inno.Core.Graphs.GraphNodeId nodeId, string propertyId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L307) | Removes one neutral node property as a structural edit.
`nodeId`: Owning node.
`propertyId`: Stable property ID. | when the property existed. | — | +| [`public Inno.Editor.Graph.GraphClipboardData Inno.Editor.Graph.GraphDocumentController.Copy(System.Collections.Generic.IEnumerable nodeIds)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L331) | Copies selected nodes and only their internal connections.
`nodeIds`: Nodes to copy. | A detached neutral clipboard fragment. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Graph.GraphDocumentController.Paste(Inno.Editor.Graph.GraphClipboardData clipboard, Inno.Core.Graphs.GraphPosition offset, System.Action>? remapNode = null)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphDocumentController.cs#L365) | Pastes a clipboard fragment with new stable identities and a graph-space offset.
`clipboard`: Detached neutral fragment.
`offset`: Offset added to every copied node position.
`remapNode`: Optional synchronous domain remapping of neutral node values after identity allocation. The callback is never stored in History. | New node identities in clipboard document order. | — | | [`public sealed class Inno.Editor.Graph.GraphEditorModule : Inno.Editor.Core.EditorModule, Inno.Editor.Core.IEditorReloadParticipant`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L19) | Owns active neutral graph document sessions resolved by reload-safe Editor History. | — | — | +| [`public Inno.Editor.Graph.GraphDocumentController Inno.Editor.Graph.GraphEditorModule.OpenDocument(System.Guid documentId, Inno.Core.Graphs.GraphDocument document, Inno.Editor.Interactions.IEditorHistory history)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L57) | Opens or joins an active graph document session.
`documentId`: Persistent document identity; use the Asset identity for asset-backed graphs.
`document`: Mutable neutral document owned by the current asset generation.
`history`: Shared editor history used for every data mutation. | A presentation-independent document controller. | `System.InvalidOperationException`: The bounded document owner is full. | +| [`public bool Inno.Editor.Graph.GraphEditorModule.CloseDocument(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L89) | Closes a document explicitly; its neutral History entries remain unavailable until the identity is reopened.
`documentId`: The persistent document identity to release. | True when a live document was closed. | — | +| [`public bool Inno.Editor.Graph.GraphEditorModule.TryOpenDocument(System.Guid documentId, Inno.Editor.Interactions.IEditorHistory history, out Inno.Editor.Graph.GraphDocumentController? controller)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L115) | Resolves an already open document without replacing its authored contents.
`documentId`: The persistent document identity.
`history`: The shared history owner for the returned controller.
`controller`: The joined controller, or null when the document is closed. | True when the document is still owned by this module. | — | +| [`public void Inno.Editor.Graph.GraphEditorModule.SetAvailability(System.Guid documentId, bool available)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L135) | Marks a temporarily Missing source without discarding its graph or advancing its History stack.
`documentId`: The persistent identity of an open document.
`available`: Whether its source can be resolved in the current generation. | — | — | +| [`public void Inno.Editor.Graph.GraphEditorModule.RebindDocument(System.Guid documentId, Inno.Core.Graphs.GraphDocument document)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L155) | Rebinds an active session to a newly imported current-generation document.
`documentId`: Stable document identity.
`document`: New current-generation document instance. | — | `System.InvalidOperationException`: Thrown when the document has no active session. | +| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L190) | Initializes this feature when its owning runtime becomes active.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L198) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Graph.GraphEditorModule.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Graph/GraphEditorModule.cs#L207) | Unregisters every document identity so issued controllers cannot resolve retired sessions. | — | — | ## 项目依赖 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):实现依赖,PrivateAssets="compile"。 -- [Inno.References](../references/Inno.References.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Graphs](../core/Inno.Core.Graphs.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.References` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Core.Graphs` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Hosting.md b/docs/editor/Inno.Editor.Hosting.md index f487faa2d..9b962e3d0 100644 --- a/docs/editor/Inno.Editor.Hosting.md +++ b/docs/editor/Inno.Editor.Hosting.md @@ -8,6 +8,14 @@ `src/composition/editor/EditorProduct.props` 是内置 Editor 功能、Panel 和呈现程序集的唯一产品依赖声明。Windows、macOS 产品共同导入它,保证仅由 attribute 发现、没有 CLR 调用引用的扩展仍进入部署目录和产品依赖清单。Hosting 的项目引用仅承担自身实现或公开契约所需依赖,不通过类型锚点、运行时目录扫描或重复平台列表补齐产品。 +## 新项目默认布局 + +`Layout/DefaultEditorLayout.ini` 是标准 Host 拥有的嵌入资源,初始化 `EditorContext` 时传入,随后才由 presentation 加载。新项目首次打开会立即获得可读的 `editor.ini`;已有文件始终优先,不因升级或打开项目自动覆盖。 + +默认排列使用标准停靠结构:左上 Scene/Game 并排、左下 File,中间 Hierarchy,右上 Inspector/Shader Editor 标签,右下 Stats/Console 标签。初始比例由正常 ImGui dock tree 表达,现有呈现逻辑适应实际窗口尺寸。 + +模板仅包含八个标准窗口、停靠数据和标准面板开关;不包含示例场景、Shader ID、导航状态、Plugin 窗口、Selection、History、编译或导出弹窗。默认文件不是另一个 Workspace 系统,后续保存仍归现有统一生命周期。 + ## 组合、生命周期与扩展 公开入口为 `EditorApplication.RunAsync(EditorLaunchOptions, CancellationToken)`。产品传入项目、catalog、selection、窗口、帧驱动、键盘约定、distribution、明确初始导出目标、SDK上下文、模块/序列化来源与日志工厂。 @@ -35,39 +43,16 @@ Project 创作 IO 属于创作服务;OS 用户位置由平台产品提供。 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Hosting.EditorApplication` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static System.Threading.Tasks.Task Inno.Editor.Hosting.EditorApplication.RunAsync(Inno.Editor.Hosting.EditorLaunchOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorApplication.cs#L40) | Validates composition, owns the created Editor host and retires resources on exit, cancellation or failure.
返回:Zero after orderly completion; startup, execution and retirement failures propagate.
`options`:Borrowed catalogs, factories and product configuration; created resources transfer to the host.
`cancellationToken`:Cancels before creation or requests orderly owner-thread shutdown. | — | `System.ArgumentNullException`:Options or a required dependency is null.
`System.ArgumentException`:A required location or product selection is invalid.
`System.NotSupportedException`:A selected backend lacks its matching runtime or authoring provider.
`System.OperationCanceledException`:Startup or execution was canceled after resource retirement.
`System.InvalidOperationException`:Product startup or resource retirement fails. | -| [`public static class Inno.Editor.Hosting.EditorApplication`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorApplication.cs#L11) | Runs the shared Editor product with explicitly supplied platform and publication composition. | — | — | - -### `Inno.Editor.Hosting.EditorCommandLineOptions` - - - + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public bool Inno.Editor.Hosting.EditorCommandLineOptions.windowVisible { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L27) | 是否显示真实平台窗口;默认 true,false 保留同一 Editor 初始化、帧循环与退出。 | 启动配置不可变;产品组合将值传给 PlatformWindowOptions。 | 参数解析失败发生在创建设备前;隐藏运行不验证 GUI 或焦点。 | -| [`public static Inno.Editor.Hosting.EditorCommandLineOptions Inno.Editor.Hosting.EditorCommandLineOptions.Parse(string[] arguments)`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L41) | Parses the common Editor command line without selecting a platform.
返回:The immutable project startup configuration.
`arguments`:The project path followed by optional name/value pairs. | — | `System.ArgumentException`:The project or an option is missing, repeated or invalid. | -| [`public Inno.Rendering.GraphicsApi? Inno.Editor.Hosting.EditorCommandLineOptions.graphicsApi { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L27) | Gets an explicit renderer preference, or null for backend policy. | — | — | -| [`public string Inno.Editor.Hosting.EditorCommandLineOptions.projectDirectory { get; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L17) | Gets the caller-selected authoring project. | — | — | -| [`public int? Inno.Editor.Hosting.EditorCommandLineOptions.smokeFrameLimit { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L22) | Gets the optional bounded verification frame count. | — | — | -| [`public sealed class Inno.Editor.Hosting.EditorCommandLineOptions`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L10) | Parses the shared Editor project and verification workflow before platform composition starts. | — | — | - -### `Inno.Editor.Hosting.EditorLaunchOptions` - @@ -86,72 +71,82 @@ Project 创作 IO 属于创作服务;OS 用户位置由平台产品提供。 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Hosting.EditorLaunchOptions.EditorLaunchOptions()`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L17) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | +| [`public static class Inno.Editor.Hosting.EditorApplication`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorApplication.cs#L11) | Runs the shared Editor product with explicitly supplied platform and publication composition. | — | — | +| [`public static System.Threading.Tasks.Task Inno.Editor.Hosting.EditorApplication.RunAsync(Inno.Editor.Hosting.EditorLaunchOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorApplication.cs#L40) | Validates composition, owns the created Editor host and retires resources on exit, cancellation or failure.
`options`: Borrowed catalogs, factories and product configuration; created resources transfer to the host.
`cancellationToken`: Cancels before creation or requests orderly owner-thread shutdown. | Zero after orderly completion; startup, execution and retirement failures propagate. | `System.ArgumentNullException`: Options or a required dependency is null.
`System.ArgumentException`: A required location or product selection is invalid.
`System.NotSupportedException`: A selected backend lacks its matching runtime or authoring provider.
`System.OperationCanceledException`: Startup or execution was canceled after resource retirement.
`System.InvalidOperationException`: Product startup or resource retirement fails. | +| [`public sealed class Inno.Editor.Hosting.EditorCommandLineOptions`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L10) | Parses the shared Editor project and verification workflow before platform composition starts. | — | — | +| [`public string Inno.Editor.Hosting.EditorCommandLineOptions.projectDirectory { get; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L17) | Gets the caller-selected authoring project. | — | — | +| [`public int? Inno.Editor.Hosting.EditorCommandLineOptions.smokeFrameLimit { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L22) | Gets the optional bounded verification frame count. | — | — | +| [`public bool Inno.Editor.Hosting.EditorCommandLineOptions.windowVisible { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L27) | Gets whether the platform window is shown; hidden execution retains the same Editor lifecycle. | — | — | +| [`public Inno.Rendering.GraphicsApi? Inno.Editor.Hosting.EditorCommandLineOptions.graphicsApi { get; private init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L32) | Gets an explicit renderer preference, or null for backend policy. | — | — | +| [`public static Inno.Editor.Hosting.EditorCommandLineOptions Inno.Editor.Hosting.EditorCommandLineOptions.Parse(string[] arguments)`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorCommandLineOptions.cs#L46) | Parses the common Editor command line without selecting a platform.
`arguments`: The project path followed by optional name/value pairs. | The immutable project startup configuration. | `System.ArgumentException`: The project or an option is missing, repeated or invalid. | +| [`public sealed class Inno.Editor.Hosting.EditorLaunchOptions`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L17) | Supplies product-owned composition to the shared Editor lifecycle without selecting a platform or backend. | — | — | +| [`public required string Inno.Editor.Hosting.EditorLaunchOptions.projectDirectory { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L22) | Gets the current authoring project location; shared authoring services own its project IO. | — | — | +| [`public required Inno.Editor.Core.EditorKeyboardPolicy Inno.Editor.Hosting.EditorLaunchOptions.keyboard { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L27) | Gets explicit keyboard conventions selected by the Editor product. | — | — | | [`public required Inno.Adapter.Presentation.IAuthoringAdapterCatalog Inno.Editor.Hosting.EditorLaunchOptions.adapterCatalog { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L32) | Gets the borrowed immutable runtime, authoring and presentation provider catalogs. | — | — | +| [`public required Inno.Shell.ShellOptions Inno.Editor.Hosting.EditorLaunchOptions.shell { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L37) | Gets explicit backend, primary-window and rendering policy selected by the product. | — | — | +| [`public required Inno.Adapter.Presentation.PresentationBackendId Inno.Editor.Hosting.EditorLaunchOptions.presentation { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L42) | Gets the explicit presentation implementation selected by the product. | — | — | +| [`public required Inno.Shell.IShellFrameDriver Inno.Editor.Hosting.EditorLaunchOptions.frameDriver { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L47) | Gets the borrowed owner-thread scheduling policy. | — | — | +| [`public required Inno.Build.Composition.BuildDistribution Inno.Editor.Hosting.EditorLaunchOptions.distribution { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L52) | Gets the borrowed immutable publication distribution. | — | — | +| [`public required Inno.Build.BuildTargetId Inno.Editor.Hosting.EditorLaunchOptions.defaultBuildTarget { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L57) | Gets the explicit default game target for new project build settings. | — | — | | [`public required Inno.Build.Composition.BuildCompositionContext Inno.Editor.Hosting.EditorLaunchOptions.buildContext { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L62) | Gets the explicitly selected SDK and build execution host. | — | — | +| [`public required string Inno.Editor.Hosting.EditorLaunchOptions.supportPackRoot { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L67) | Gets the explicit installed Support Pack location provided by product composition. | — | — | | [`public required System.Func Inno.Editor.Hosting.EditorLaunchOptions.createEngineHost { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L72) | Gets the factory transferring a newly created metadata-configured engine to the Editor owner. | — | — | -| [`public System.Func? Inno.Editor.Hosting.EditorLaunchOptions.createHostLogSink { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L87) | Gets an optional factory transferring one host log sink to the Editor lifetime. | — | — | | [`public required System.Func Inno.Editor.Hosting.EditorLaunchOptions.createScriptModuleSource { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L77) | Gets the factory transferring a candidate script module source to the existing reload transaction. | — | — | | [`public System.Func? Inno.Editor.Hosting.EditorLaunchOptions.createSessionLogSink { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L82) | Gets an optional factory transferring each edit or play log sink to its owning session. | — | — | -| [`public required Inno.Build.BuildTargetId Inno.Editor.Hosting.EditorLaunchOptions.defaultBuildTarget { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L57) | Gets the explicit default game target for new project build settings. | — | — | -| [`public required Inno.Build.Composition.BuildDistribution Inno.Editor.Hosting.EditorLaunchOptions.distribution { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L52) | Gets the borrowed immutable publication distribution. | — | — | -| [`public required Inno.Shell.IShellFrameDriver Inno.Editor.Hosting.EditorLaunchOptions.frameDriver { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L47) | Gets the borrowed owner-thread scheduling policy. | — | — | -| [`public required Inno.Editor.Core.EditorKeyboardPolicy Inno.Editor.Hosting.EditorLaunchOptions.keyboard { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L27) | Gets explicit keyboard conventions selected by the Editor product. | — | — | -| [`public required Inno.Adapter.Presentation.PresentationBackendId Inno.Editor.Hosting.EditorLaunchOptions.presentation { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L42) | Gets the explicit presentation implementation selected by the product. | — | — | -| [`public required string Inno.Editor.Hosting.EditorLaunchOptions.projectDirectory { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L22) | Gets the current authoring project location; shared authoring services own its project IO. | — | — | -| [`public required Inno.Shell.ShellOptions Inno.Editor.Hosting.EditorLaunchOptions.shell { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L37) | Gets explicit backend, primary-window and rendering policy selected by the product. | — | — | +| [`public System.Func? Inno.Editor.Hosting.EditorLaunchOptions.createHostLogSink { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L87) | Gets an optional factory transferring one host log sink to the Editor lifetime. | — | — | | [`public int? Inno.Editor.Hosting.EditorLaunchOptions.smokeFrameLimit { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L92) | Gets an optional positive frame limit for bounded product verification. | — | — | -| [`public required string Inno.Editor.Hosting.EditorLaunchOptions.supportPackRoot { get; init; }`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L67) | Gets the explicit installed Support Pack location provided by product composition. | — | — | -| [`public sealed class Inno.Editor.Hosting.EditorLaunchOptions`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L17) | Supplies product-owned composition to the shared Editor lifecycle without selecting a platform or backend. | — | — | +| [`public Inno.Editor.Hosting.EditorLaunchOptions.EditorLaunchOptions()`](../../src/composition/editor/hosting/Inno.Editor.Hosting/EditorLaunchOptions.cs#L17) | Inherited or compiler-provided contract. | — | — | ## 项目依赖 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Engine.Default](../runtime/Inno.Engine.Default.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter](../runtime/Inno.Adapter.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Audio](../audio/Inno.Adapter.Audio.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Animation.Assets](../animation/Inno.Animation.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Animation.Runtime](../animation/Inno.Animation.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Audio](Inno.Editor.Audio.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.PlayMode](Inno.Editor.PlayMode.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Rendering](Inno.Editor.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Audio.Assets](../audio/Inno.Audio.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Audio.Runtime](../audio/Inno.Audio.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Text.Assets](../text/Inno.Text.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.UI.Assets](../ui/Inno.UI.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.UI.Runtime](../ui/Inno.UI.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Plugins.Authoring](../plugins/Inno.Plugins.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Events](../core/Inno.Core.Events.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Layers](../core/Inno.Core.Layers.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Input.Runtime](../input/Inno.Input.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene](../scene/Inno.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene.Assets](../scene/Inno.Scene.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Platform](../platform/Inno.Platform.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets](../rendering/Inno.Rendering.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Runtime](../rendering/Inno.Rendering.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Storage.Runtime](../storage/Inno.Storage.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Managed](../build/Inno.Build.Managed.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Runtime.Contracts](../runtime/Inno.Runtime.Contracts.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets.Authoring](../rendering/Inno.Rendering.Assets.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Build.Composition](../build/Inno.Build.Composition.md):公开引用边界由实际签名核对。 -- [Inno.Shell](../runtime/Inno.Shell.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Presentation](../platform/Inno.Adapter.Presentation.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):公开引用边界由实际签名核对。 -- [Inno.Build](../build/Inno.Build.md):公开引用边界由实际签名核对。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):公开引用边界由实际签名核对。 -- [Inno.Scripting.Compiler](../scripting/Inno.Scripting.Compiler.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Engine.Default` | 实现依赖;不传递 compile surface | +| `Inno.Adapter` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Audio` | 实现依赖;不传递 compile surface | +| `Inno.Animation.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Animation.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Audio` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Audio.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Audio.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Text.Assets` | 实现依赖;不传递 compile surface | +| `Inno.UI.Assets` | 实现依赖;不传递 compile surface | +| `Inno.UI.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Plugins.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Events` | 实现依赖;不传递 compile surface | +| `Inno.Core.Layers` | 实现依赖;不传递 compile surface | +| `Inno.Input.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Platform` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Storage.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Build.Managed` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Runtime.Contracts` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Build.Composition` | 公开签名依赖 | +| `Inno.Shell` | 公开签名依赖 | +| `Inno.Adapter.Presentation` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Runtime` | 公开签名依赖 | +| `Inno.Build` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Extensibility.Modules` | 公开签名依赖 | +| `Inno.Scripting.Compiler` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.ImGui.md b/docs/editor/Inno.Editor.ImGui.md index 0f447825a..286fa28a4 100644 --- a/docs/editor/Inno.Editor.ImGui.md +++ b/docs/editor/Inno.Editor.ImGui.md @@ -13,7 +13,7 @@ ## 共享 Inspector 与窗口呈现 -`ImGuiWidget.SectionLayout(Action drawContent)` 为一段内容建立可复用的 fieldset scope;scope 内连续调用 `bool SectionHeader(string title, string? description = null, Action? drawLeadingControl = null)` 时,标题嵌入上边框,直到下一个标题或 scope 结束的内容都被同一边框完整包裹。展开的内部 content fieldset 使用轻微圆角;窗口、Panel、顶层 Header 和其他大容器保持直角。返回值决定当前 section 正文是否应绘制;标题文字本身通过共享的 `ClickableText` widget 切换展开状态,不显示额外的加减号或整行 hover 背景。标题与 Stats 的共享 `CollectionSectionHeader` 使用同一 `sectionHeaderPadding`,因此左侧缩进不会由各 Panel 自行估算。折叠态保留中断式横线,并在整条线的左右端各绘制一根以横线为中心的短竖帽,形成 `⊢ … ⊣` 轮廓;竖帽与横线共享颜色、粗细和交点,不形成向下的残留边框。折叠状态由 ImGui 按当前 Inspector scope 的稳定顺序保存,标题或 Local/World 文案变化不会重置状态。`EnsureSection` 为没有 `[Header]` 的默认属性自动建立 `Properties` fieldset,`isSectionContentVisible` 则让 serialized-property pipeline 在任意 section 折叠后持续跳过内容,直到下一个 Header。可选 leading control 用于 Local/World 这类属于分组语义本身的开关;scope 外的 `SectionHeader` 仍保持普通分隔标题并返回 `true`。`HeaderSurface(id, drawContent, spanWindowPadding)` 是 Inspector target 与 Shader Editor 顶部共用的直角 header 容器;`CollectionSectionHeader` 是 Stats 等 collection surface 的直角分组条。`HelpBox(string text, string icon, Vector4 color)` 绘制有边框、状态图标与侧边语义色的内部提示卡片。`DrawItemTooltip` 使用父 viewport work area、真实内宽和缩放 padding 测量,靠边自动翻向并约束位置。File Browser、Hierarchy 与 Stats 共用暗色 collectionRow/collectionRowAlternate,交替条纹、hover、选择仍保持区分。 +`EditorUiSession.current.widgets.SectionLayout(Action drawContent)` 为一段内容建立可复用的 fieldset scope;scope 内连续调用 `bool SectionHeader(string title, string? description = null, Action? drawLeadingControl = null)` 时,标题嵌入上边框,直到下一个标题或 scope 结束的内容都被同一边框完整包裹。展开的内部 content fieldset 使用轻微圆角;窗口、Panel、顶层 Header 和其他大容器保持直角。返回值决定当前 section 正文是否应绘制;标题文字本身通过共享的 `ClickableText` widget 切换展开状态,不显示额外的加减号或整行 hover 背景。标题与 Stats 的共享 `CollectionSectionHeader` 使用同一 `sectionHeaderPadding`,因此左侧缩进不会由各 Panel 自行估算。折叠态保留中断式横线,并在整条线的左右端各绘制一根以横线为中心的短竖帽,形成 `⊢ … ⊣` 轮廓;竖帽与横线共享颜色、粗细和交点,不形成向下的残留边框。折叠状态由 ImGui 按当前 Inspector scope 的稳定顺序保存,标题或 Local/World 文案变化不会重置状态。`EnsureSection` 为没有 `[Header]` 的默认属性自动建立 `Properties` fieldset,`isSectionContentVisible` 则让 serialized-property pipeline 在任意 section 折叠后持续跳过内容,直到下一个 Header。可选 leading control 用于 Local/World 这类属于分组语义本身的开关;scope 外的 `SectionHeader` 仍保持普通分隔标题并返回 `true`。`HeaderSurface(id, drawContent, spanWindowPadding)` 是 Inspector target 与 Shader Editor 顶部共用的直角 header 容器;`CollectionSectionHeader` 是 Stats 等 collection surface 的直角分组条。`HelpBox(string text, string icon, Vector4 color)` 绘制有边框、状态图标与侧边语义色的内部提示卡片。`DrawItemTooltip` 使用父 viewport work area、真实内宽和缩放 padding 测量,靠边自动翻向并约束位置。File Browser、Hierarchy 与 Stats 共用暗色 collectionRow/collectionRowAlternate,交替条纹、hover、选择仍保持区分。 浮动 Panel 在标题右侧显示统一关闭按钮;docked Panel 保持每个 dock node 一个关闭入口。Docked close glyph 以 ImGui 实际 `TabBar.BarRect` 的几何中心定位,不用字体高度或 DockNode 顶点推测,因此不同缩放与顶栏布局下仍严格垂直居中。窗口标题不继承输入控件的 FrameBorderSize,避免额外分隔线;这与透明接缝是两个不同的问题。窗口、Child、Tab 和 component header 的外轮廓使用直角;按钮、输入框、菜单、tooltip、HelpBox 与 Inspector content fieldset 等内部交互/内容表面仍使用语义圆角。所有按钮仍走现有 Panel close/dirty 确认生命周期。 @@ -63,15 +63,21 @@ Inno.Editor.ImGui/ └─ ... ``` -Palette 与 Style Metrics 并列位于 `Styling`,runtime host 与三个表现桥统一位于 `Runtime`,不再人为拆分只有三个文件的 Renderers 层。它们仍使用项目 namespace `Inno.Editor.ImGui`。所有 `ImGuiWidget.*.cs` 位于 `Widgets`,namespace 统一为 `Inno.Editor.ImGui.ImGuiWidget`;实现统一组成 `static partial ImGuiWidget`,可复用入口全部是 static 方法。Options、Result、presentation 与私有状态收口在对应的 `ImGuiWidget..cs` 中,不创建独立 Widget helper 文件。 +Palette 与 Style Metrics 并列位于 `Styling`,runtime host 与三个表现桥统一位于 `Runtime`,不再人为拆分只有三个文件的 Renderers 层。它们仍使用项目 namespace `Inno.Editor.ImGui`。所有 `ImGuiWidget.*.cs` 位于 `Widgets`,namespace 统一为 `Inno.Editor.ImGui.ImGuiWidget`;实现统一组成 Session 所有的 `sealed partial ImGuiWidget`,可复用入口由当前 `EditorUiSession.widgets` 提供。Options、Result、presentation 与私有状态收口在对应的 `ImGuiWidget..cs` 中,不创建独立 Widget helper 文件。 ## Palette 与 Style -所有主题颜色集中在 `EditorPalette`:原生 ImGui col、Inspector、Hierarchy、Asset Browser、Logging、轴颜色与 drag target 都不在 Panel 中声明。换主题只需替换这一个 palette surface。 +`EditorPalette` 只提供通用内容、选择、错误和控件表面的语义色。Browser 的图标/网格/列布局由 `Inno.Editor.Assets.ImGui.AssetBrowserStyle` 拥有;Shader 节点分类及其颜色由 Shader 呈现 owner 解释。通用层不维护资产或 Shader 类型名单。 -所有跨 Panel 的像素布局、padding、spacing、rounding、列比例和最小尺寸集中在 `ImGuiWidget.style`(`EditorStyleMetrics`)。Panel 可以读取语义名,例如 `panelTabFramePadding`、`sectionHeaderPadding`、`inspectorSectionPadding`、`inspectorSectionRounding`、`inspectorCollapsedSectionCapLength`、`propertyMetadataSpacing`、`assetListNameSeparatorPosition`、`inspectorCardSpacing`、`hierarchyItemSpacing`、`hierarchyRenameMinimumWidth` 与 `settingsFieldPadding`,不应新增散落的固定像素。Inspector fieldset 的 outline 同样来自 `EditorPalette.inspectorSectionBorder`。 +基础与通用控件的语义色集中在 `EditorPalette`,包括原生 ImGui col、选择、禁用、错误、轴颜色和 drag target。领域分类及专属预览颜色由对应 presentation owner 解释;它们复用基础色,不把 Shader 类型名单或资产布局放回共享层。 -`ImGuiWidget.SetupStyle()` 把 layout metrics 和 `EditorPalette` 应用到原生 ImGui style;运行期间 zoom 改变时,runtime 只在倍率发生变化后重新应用一次 native style。普通窗口绘制时,`ResizeGrip`、`ResizeGripHovered` 与 `ResizeGripActive` 使用透明色,因此可缩放窗口仍保留边缘/角落命中能力,但不会显示右下角三角形。Dear ImGui 在更新 Dock tree splitter 时会把 separator hover/active 临时映射到 resize-grip hover/active;`ImGuiEditorRuntime` 只在 `DockSpaceOverViewport` 调用范围内恢复这两个 accent color,保证 Panel 间连接线的 hover/drag feedback 可见,同时不恢复窗口三角形。 +`widgets.NavigateList(interactions, itemCount, focusedIndex, pageSize)` 返回当前可见顺序上的 `ListNavigationResult`,不保存对象或替领域修改 Selection。调用方把结果交给同一个 Core Identity/Interactions owner:方向、Home/End、Page Up/Down 移动,Shift 扩展范围,产品主修饰键允许只移动焦点或用 Space 切换选择,Enter 请求激活,Escape 请求取消。输入框、其他活动控件或 Popup 占有输入时不产生底层列表操作。树的行 ID 只在现有 Session/Window 状态中参与按键归属;叠加按钮仍由原生控件处理,不变成第二套对象身份或选择服务。`ImGui.FocusNextItem()` 在有效绘制 scope 内请求下一控件的键盘焦点;`TreeNodeOptions.requestFocus` 用于当前可见行的焦点恢复。 + +`widgets.SearchInput` 使用稳定 ID,Escape 先清空非空查询,再次按下退出输入。`widgets.Trend` 只在当前绘制调用内借用有限数值 Span,不复制缓冲或保留 provider;空 Span 绘制空图,非有限样本或非法环形偏移明确失败。采样窗口、频率及 buffer 生命周期由使用它的 Panel 拥有。 + +基础控件的像素布局、padding、spacing、rounding、列比例和最小尺寸集中在 `EditorUiSession.current.widgets.style`(`EditorStyleMetrics`)。Panel 可以读取语义名,例如 `panelTabFramePadding`、`sectionHeaderPadding`、`inspectorSectionPadding`、`inspectorSectionRounding`、`inspectorCollapsedSectionCapLength`、`propertyMetadataSpacing`、`inspectorCardSpacing`、`hierarchyItemSpacing`、`hierarchyRenameMinimumWidth` 与 `settingsFieldPadding`,不应新增散落的固定像素。Inspector fieldset 的 outline 同样来自 `EditorPalette.inspectorSectionBorder`。 + +`EditorUiSession.current.widgets.SetupStyle()` 把 layout metrics 和 `EditorPalette` 应用到原生 ImGui style;运行期间 zoom 改变时,runtime 只在倍率发生变化后重新应用一次 native style。普通窗口绘制时,`ResizeGrip`、`ResizeGripHovered` 与 `ResizeGripActive` 使用透明色,因此可缩放窗口仍保留边缘/角落命中能力,但不会显示右下角三角形。Dear ImGui 在更新 Dock tree splitter 时会把 separator hover/active 临时映射到 resize-grip hover/active;`ImGuiEditorRuntime` 只在 `DockSpaceOverViewport` 调用范围内恢复这两个 accent color,保证 Panel 间连接线的 hover/drag feedback 可见,同时不恢复窗口三角形。 主 dockspace 在窗口尺寸变化时按 ImGui 当前节点数据逐层更新子节点的 `SizeRef`,中央 node 不再独占新增加的空间。只有两个子节点的实际尺寸之和等于父节点的可用尺寸时,才用实际尺寸推导比例;刚载入 layout 时的子节点 `Size` 可能为零或错误地等于父尺寸,这时改用 ImGui 保存的 `SizeRef`,避免比例在启动首帧逐次漂移。宿主按当前 ImGui viewport ID 推导 dockspace ID,在第一次提交 dockspace 前读取已加载的节点并等比更新;拖动 splitter 的实际结果成为下一次 resize 的比例,最小尺寸仍由原生 `WindowMinSize` 和 separator 限制。比例不另存一份,布局的唯一持久来源仍是 `editor.ini` 的 `[Docking][Data]`。 @@ -115,6 +121,7 @@ Editor ImGui context 默认启用 Inno overlay scrollbar 扩展。纵横滚动 ```csharp using System; +using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; static bool DrawCardHeader( @@ -126,7 +133,7 @@ static bool DrawCardHeader( Action drawTrailingControl, Action drawContextMenu ) { - return ImGuiWidget.CollapsingCard(id, title, + return EditorUiSession.current.widgets.CollapsingCard(id, title, drawLeadingControl: drawLeadingControl, drawTrailingControl: drawTrailingControl, defaultOpen: true, dimmed: !enabled, trailingControlWidth: trailingControlWidth, @@ -153,6 +160,7 @@ Component(包含 Transform)与 GameSystem 的排序拖拽也绑定在这个 ```csharp using System; using Inno.Editor.ImGui; +using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; static void DrawOpenCardBody( @@ -165,7 +173,7 @@ static void DrawOpenCardBody( return; try { - ImGuiWidget.CardBody(id, drawContent: drawContent, dimmed: !enabled); + EditorUiSession.current.widgets.CardBody(id, drawContent: drawContent, dimmed: !enabled); } finally { @@ -204,24 +212,785 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN [`Inno.Editor.ImGui.Tests`](../../tests/editor/Inno.Editor.ImGui.Tests/WidgetLayoutTests.cs) 使用真实 ImGui context 和公开 Widget API,通过鼠标事件打开弹层并测量稳定后的布局。覆盖三档缩放下的 2:3 列、长标签换行、短列表无滚动、长列表高度边界、菜单搜索填满内容宽度,以及重叠窗口仅前景控件接收点击。测试宿主必须部署与当前绑定一致的 native 目录;测试不依赖桌面解锁或截图推测内部布局。 -## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 -### `Inno.Editor.ImGui.EditorDragDropRenderer` +## Session 与原生边界 - - - +每个 ImGuiEditorRuntime 拥有一个 EditorUiSession。widgets 与 menus 的缓存、搜索、树布局及手势状态均按 session/owner 管理;Panel 关闭或 generation 退休时清理。静态 current 只访问同步绘制 scope,跨线程、跨 context 或失序释放明确失败。 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static bool Inno.Editor.ImGui.EditorDragDropRenderer.Source(Inno.Editor.Interactions.EditorInteraction interaction, Inno.Editor.Interactions.EditorDragData data, System.Action? drawPreview = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L33) | Publishes managed drag data for the most recently submitted ImGui item.
返回:true while the item is an active drag source.
`interaction`:The interaction area and target that produced the drag source.
`data`:The managed drag data published by the source.
`drawPreview`:An optional callback that draws the native drag preview. | — | — | -| [`public static Inno.Editor.ImGui.EditorDropWidgetResult Inno.Editor.ImGui.EditorDragDropRenderer.Target(Inno.Editor.Interactions.EditorInteraction interaction, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L57) | Evaluates and, on delivery, accepts a managed drag on the most recently submitted ImGui item.
返回:The native preview state together with the managed compatibility and delivery results.
`interaction`:The interaction area and managed drop target.
`placement`:The requested position relative to the target. | — | — | -| [`public static class Inno.Editor.ImGui.EditorDragDropRenderer`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L14) | Bridges managed editor drag sessions to the native ImGui payload API. | — | — | +普通 Panel、Inspection 和插件使用 pointer-free facade 及引擎自己的 options。EditorDrawList 仅借用当前帧和窗口,切换窗口、下一帧、离开 frame 或更换 context 后失效。字体用 ImGuiFontScope 成对恢复。TransformGizmo 封装 native gizmo,仅暴露中立矩阵与操作结果。 + +InputText 的 UTF-8 临时内存按大小使用 stack/共享 ArrayPool,finally 归还并清空;未改变文本不解码,不引入私有池。性能结论以验收采样为准。 + +```csharp +using InnoEditor.ImGui; + +static void DrawHelp() +{ + EditorUiSession.current.widgets.Hint("Changes use the shared Editor History."); +} +``` + +## 当前源码公开 API 清单 -### `Inno.Editor.ImGui.EditorDropWidgetResult` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -234,62 +1003,49 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.ImGui.EditorDropWidgetResult.EditorDropWidgetResult(bool isPreviewing, Inno.Editor.Interactions.EditorDropStatus status, Inno.Editor.Interactions.EditorDropResult result)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L101) | Creates a combined native-preview and managed-drop result.
`isPreviewing`:Whether a compatible native payload is currently hovering the target.
`status`:The compatibility and visual state returned by the managed drop router.
`result`:The result produced when the payload was delivered. | — | — | -| [`public bool Inno.Editor.ImGui.EditorDropWidgetResult.Equals(Inno.Editor.ImGui.EditorDropWidgetResult other)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.ImGui.EditorDropWidgetResult.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.ImGui.EditorDropWidgetResult.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.ImGui.EditorDropWidgetResult.operator ==(Inno.Editor.ImGui.EditorDropWidgetResult left, Inno.Editor.ImGui.EditorDropWidgetResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.ImGui.EditorDropWidgetResult.operator !=(Inno.Editor.ImGui.EditorDropWidgetResult left, Inno.Editor.ImGui.EditorDropWidgetResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.ImGui.EditorDropWidgetResult.ToString()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.ImGui.EditorDropWidgetResult.isPreviewing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L114) | Gets whether a compatible native payload is hovering the target. | — | — | -| [`public static Inno.Editor.ImGui.EditorDropWidgetResult Inno.Editor.ImGui.EditorDropWidgetResult.none { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L129) | Gets an inactive drop target result. | — | — | -| [`public Inno.Editor.Interactions.EditorDropResult Inno.Editor.ImGui.EditorDropWidgetResult.result { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L124) | Gets the delivered drop result. | — | — | -| [`public Inno.Editor.Interactions.EditorDropStatus Inno.Editor.ImGui.EditorDropWidgetResult.status { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L119) | Gets the managed drop compatibility status. | — | — | -| [`public readonly record struct Inno.Editor.ImGui.EditorDropWidgetResult : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L87) | Reports the preview and delivery state of an ImGui editor drop target. | — | — | - -### `Inno.Editor.ImGui.EditorGameInputCapture` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.ImGui.EditorGameInputCapture.EditorGameInputCapture(System.Func resolveWindow)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L35) | Creates a capture policy using a presentation viewport to platform window resolver.
`resolveWindow`:Returns a live window ID for a viewport, or null when absent. | — | `System.ArgumentNullException`:Thrown when the resolver is null. | -| [`public void Inno.Editor.ImGui.EditorGameInputCapture.BeginFrame()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L43) | Starts a presentation pass in which the visible Game View must report its current bounds. | — | — | -| [`public uint Inno.Editor.ImGui.EditorGameInputCapture.CompleteFrame()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L89) | Ends a presentation pass and identifies input that must be released after focus loss.
返回:The former platform window ID to reset, or zero if no reset is needed. | — | — | -| [`public bool Inno.Editor.ImGui.EditorGameInputCapture.Report(uint viewportId, System.Numerics.Vector2 origin, System.Numerics.Vector2 size, bool focused, bool hovered)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L66) | Reports the Game View image in coordinates local to its platform window.
返回:true when the Game View and its platform window both own focus.
`viewportId`:The presentation viewport containing the Game View.
`origin`:The image's upper-left corner relative to that window.
`size`:The visible image size in logical pixels.
`focused`:Whether the Game View is selected and its platform window has focus.
`hovered`:Whether the image is inside the frontmost hovered ImGui window. | — | — | -| [`public Inno.Core.Events.Event? Inno.Editor.ImGui.EditorGameInputCapture.Route(Inno.Core.Events.Event evnt)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L126) | Filters an engine event through the current Game View focus and pointer bounds.
返回:A routed event or a synthetic focus reset, or null when the Game View must not receive input.
`evnt`:The original backend-neutral platform event. | A globally consumed key, button, focus or close release is not replayed to the game. It returns a new focus-reset event so previously held input is released before simulation. Consumed pointer movement still updates hit-test bounds without reaching the game backend. | `System.ArgumentNullException`:Thrown when the event is null. | -| [`public sealed class Inno.Editor.ImGui.EditorGameInputCapture`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L13) | Routes platform input to the Play session only while the visible Game View owns focus. | — | — | - -### `Inno.Editor.ImGui.EditorMenuRenderer` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static bool Inno.Editor.ImGui.EditorMenuRenderer.ContextMenu(string id, Inno.Editor.Interactions.EditorInteraction scopeInteraction, Inno.Editor.Interactions.EditorInteraction itemInteraction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L80) | Draws one context popup composed from an item interaction and its containing-scope interaction.
返回:true while the context popup is open and at least one command group was drawn.
`id`:Stable popup identifier in the current ImGui ID scope.
`scopeInteraction`:Container interaction whose commands are shown first, such as creation commands for a directory.
`itemInteraction`:Selected-item interaction whose commands are shown after the container commands. | — | — | -| [`public static bool Inno.Editor.ImGui.EditorMenuRenderer.ContextMenu(string id, Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L38) | Draws a resolved right-click menu for the most recently submitted ImGui item.
返回:true while the context popup is open and its items were drawn.
`id`:The stable popup identifier in the current ImGui ID scope.
`interaction`:The interaction area and optional operation target. | — | — | -| [`public static void Inno.Editor.ImGui.EditorMenuRenderer.DrawItems(Inno.Editor.Interactions.EditorInteraction interaction, System.Collections.Generic.IReadOnlyList items)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L322) | Recursively draws resolved menu nodes into the currently open popup or menu.
`interaction`:The interaction used to resolve shortcuts and enqueue selected actions.
`items`:The immutable menu nodes to draw in display order. | — | — | -| [`public static bool Inno.Editor.ImGui.EditorMenuRenderer.DrawSearchItems(Inno.Editor.Interactions.EditorInteraction interaction, System.Collections.Generic.IReadOnlyList items, string search)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L379) | Draws matching leaf commands as a flat searchable list.
返回:true when a command was selected.
`interaction`:The interaction used to enqueue selected actions.
`items`:The immutable menu tree whose leaves should be searched.
`search`:The case-insensitive text matched against each slash-delimited leaf path. | — | — | -| [`public static void Inno.Editor.ImGui.EditorMenuRenderer.MainMenu(Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L216) | Draws the complete editor main menu bar for the supplied menu context.
`interaction`:The main-menu interaction area. | — | — | -| [`public static bool Inno.Editor.ImGui.EditorMenuRenderer.WindowContextMenu(string id, Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L126) | Draws a resolved right-click menu when the current ImGui window's unoccupied background is clicked.
返回:true while the background context popup is open and its items were drawn.
`id`:The stable popup identifier in the current ImGui ID scope.
`interaction`:The interaction area and directory target for the background operation. | — | — | -| [`public static class Inno.Editor.ImGui.EditorMenuRenderer`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L17) | Renders immutable editor menu models through ImGui. | — | — | - -### `Inno.Editor.ImGui.EditorPalette` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -309,9 +1065,6 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - - - @@ -348,184 +1101,42 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityEmphasized = 0.82`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L61) | Gets emphasized opacity for borders and interactive grips. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityFaint = 0.04`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L26) | Gets faint opacity for quiet guides and separators. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityMedium = 0.45`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L46) | Gets medium opacity for secondary content and overlays. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityMuted = 0.28`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L41) | Gets muted opacity for shadows and subdued outlines. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityNearOpaque = 0.92`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L66) | Gets near-opaque opacity for active overlays. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityNone = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L16) | Gets zero opacity for invisible surfaces. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityOpaque = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L76) | Gets full opacity. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityPopup = 0.985`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L71) | Gets popup opacity, retaining only enough transparency for depth. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityProminent = 0.7`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L56) | Gets prominent opacity for navigation emphasis. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacitySoft = 0.2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L36) | Gets soft opacity for tinted container surfaces. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityStrong = 0.55`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L51) | Gets strong opacity for accents that remain translucent. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacitySubtle = 0.12`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L31) | Gets subtle opacity for selection washes and low-emphasis overlays. | — | — | -| [`public const float Inno.Editor.ImGui.EditorPalette.opacityTrace = 0.005`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L21) | Gets trace opacity for barely perceptible alternating surfaces. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetActive(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L458) | Gets an active treatment derived from a base theme color.
返回:The base color blended toward the palette text color using the standard active amount.
`color`:The color consumed by get active; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetHovered(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L447) | Gets a hover treatment derived from a base theme color.
返回:The base color blended toward the palette text color using the standard hover amount.
`color`:The color consumed by get hovered; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetLogExpandedBorder(System.Numerics.Vector4 severityColor)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L575) | Gets the expanded log card border derived from a severity color.
返回:The standard expanded-border base blended with the severity color.
`severityColor`:The palette color representing the log severity. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetLogExpandedCard(System.Numerics.Vector4 severityColor)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L564) | Gets the expanded log card background derived from a severity color.
返回:The standard expanded-card base blended with the severity color.
`severityColor`:The palette color representing the log severity. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetLogSeparator(System.Numerics.Vector4 cardColor)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L586) | Gets the separator color used inside an expanded log card.
返回:The card color blended toward the palette text color.
`cardColor`:The resolved background color of the expanded card. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.Lerp(System.Numerics.Vector4 from, System.Numerics.Vector4 to, float amount)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L603) | Linearly interpolates two palette colors using a clamped amount.
返回:The component-wise interpolated color.
`from`:The color returned when amount is zero.
`to`:The color returned when amount is one.
`amount`:The interpolation amount, clamped to the inclusive zero-to-one range. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.WithOpacity(System.Numerics.Vector4 color, float opacity)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L686) | Returns a theme color with a caller-selected opacity, normally one of the shared opacity levels.
返回:The validated vector4 that represents the completed operation.
`color`:Base RGB color.
`opacity`:Replacement alpha in the inclusive zero-to-one range. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L216) | Gets the standard accent color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accentActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L226) | Gets the active accent color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accentHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L221) | Gets the hovered accent color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetAccent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L436) | Gets the opaque asset browser accent. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L411) | Gets the asset browser border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetBorderSoft { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L416) | Gets the soft asset browser border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetBreadcrumbText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L431) | Gets the subdued text color used by asset browser breadcrumb paths. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetField { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L406) | Gets the asset browser field background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L421) | Gets the asset browser text color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.assetTextMuted { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L426) | Gets the muted asset browser text color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisW { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L493) | Gets W axis color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisX { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L478) | Gets X axis color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisY { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L483) | Gets Y axis color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisZ { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L488) | Gets Z axis color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.border { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L161) | Gets the standard border color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.borderShadow { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L166) | Gets the standard border shadow color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L381) | Gets the deepest collection background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionRow { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L386) | Gets the primary collection row background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionRowAlternate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L391) | Gets the alternate collection row background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.compactControlHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L376) | Gets compact control hover color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.dragDropTarget { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L281) | Gets the standard drag target color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.error { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L96) | Gets the standard error color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frame { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L171) | Gets the standard frame background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frameActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L181) | Gets the active frame background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frameHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L176) | Gets the hovered frame background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.hierarchyInactiveText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L468) | Gets inactive hierarchy text. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.hierarchySceneRow { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L463) | Gets scene row background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorCardBody { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L326) | Gets inspector card body background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorCardBodyBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L331) | Gets inspector card body border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorCardDisabledText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L366) | Gets disabled inspector card text. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorCardDisclosureHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L371) | Gets inspector disclosure hover background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorCardHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L301) | Gets inspector card header background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorLayerLabel { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L321) | Gets the subdued background of Layer labels in Inspector target headers. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorSectionBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L336) | Gets the outline color of framed Inspector sections. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorTagLabel { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L316) | Gets the subdued background of Tag labels in Inspector target headers. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorTargetHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L306) | Gets the persistent Inspector target header background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.inspectorTargetHeaderBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L311) | Gets the persistent Inspector target header border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logCollapsedBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L528) | Gets collapsed log card border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logCollapsedCard { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L523) | Gets collapsed log card background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logDebug { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L498) | Gets debug log color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logError { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L513) | Gets error log color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logExpandedBase { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L533) | Gets the base expanded log card background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logExpandedBorderBase { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L538) | Gets the base expanded log card border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logFatal { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L518) | Gets fatal log color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logInfo { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L503) | Gets informational log color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logToggle { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L543) | Gets log header button background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logToggleActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L553) | Gets active log header button background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logToggleHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L548) | Gets hovered log header button background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.logWarning { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L508) | Gets warning log color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L136) | Gets the background used by editor context menus. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItem { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L141) | Gets the resting background of an editor context-menu item. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItemActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L151) | Gets the active background of an editor context-menu item. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItemHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L146) | Gets the hovered background of an editor context-menu item. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuSeparator { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L156) | Gets the separator color used by editor context menus. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L131) | Gets text color used by editor context menus. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.modalDim { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L296) | Gets modal dim background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.navigationDim { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L291) | Gets navigation dim background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.navigationHighlight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L286) | Gets navigation highlight color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.popupBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L126) | Gets the editor popup background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrab { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L201) | Gets the standard scrollbar thumb. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrabActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L211) | Gets the active scrollbar thumb. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrabHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L206) | Gets the hovered scrollbar thumb. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.settingsField { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L396) | Gets the translucent primary background of a complete Settings field. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.settingsFieldAlternate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L401) | Gets the translucent alternate background of a complete Settings field. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderInputNodeHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L111) | Gets the orange heading surface shared by Shader input nodes. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderNodeHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L106) | Gets the neutral Shader node heading surface. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderOutputNodeHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L116) | Gets the purple heading surface shared by Shader output nodes. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderTypeBoolean { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L351) | Gets the accent used by Boolean Shader type badges. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderTypeInteger { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L346) | Gets the accent used by integer Shader type badges. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderTypeNumeric { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L341) | Gets the accent used by numeric Shader type badges. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderTypeOther { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L361) | Gets the accent used by unclassified Shader type badges. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.shaderTypeResource { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L356) | Gets the accent used by Shader resource type badges. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tab { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L231) | Gets the standard tab color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabDimmed { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L246) | Gets the dimmed tab color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabDimmedSelected { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L251) | Gets the selected dimmed tab color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L236) | Gets the hovered tab color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableBorderLight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L271) | Gets the light table border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableBorderStrong { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L266) | Gets the strong table border. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L261) | Gets the table header background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableRowAlternate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L276) | Gets the alternate table row background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabSelected { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L241) | Gets the selected tab color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabSelectedOverline { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L256) | Gets the selected tab overline color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.text { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L86) | Gets the primary text color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.textDisabled { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L91) | Gets disabled text color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.title { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L186) | Gets the inactive title background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.titleActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L191) | Gets the active title background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.titleCollapsed { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L196) | Gets the collapsed title background. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.transparent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L81) | Gets fully transparent color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.treeGuide { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L473) | Gets hierarchy tree guide color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.warning { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L101) | Gets the standard warning color. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.windowBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L121) | Gets the editor window background. | — | — | -| [`public static class Inno.Editor.ImGui.EditorPalette`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L11) | Defines the complete editor color palette in one theme surface. | — | — | - -### `Inno.Editor.ImGui.EditorStyleMetrics` - + + @@ -552,77 +1163,44 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - + + + - - + + - - - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -636,511 +1214,231 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_MAX_ZOOM = 1.5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L19) | Defines the largest supported editor UI zoom. | — | — | -| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_MIN_ZOOM = 0.75`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L14) | Defines the smallest supported editor UI zoom. | — | — | -| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_ZOOM_STEP = 0.1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L24) | Defines one keyboard or menu zoom increment. | — | — | -| [`public Inno.Editor.ImGui.EditorStyleMetrics.EditorStyleMetrics()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ResetZoom()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L101) | Restores the baseline editor UI zoom.
返回:true when the zoom changed. | — | — | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.SetCompactMode(bool value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L48) | Switches between comfortable and compact editor density.
返回:true when the density changed.
`value`:true for compact density. | — | — | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.SetZoom(float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L68) | Sets the editor UI zoom after clamping it to the supported range.
返回:true when the effective zoom changed.
`value`:The requested UI zoom multiplier. | — | `System.ArgumentOutOfRangeException`:Thrown when value is not finite. | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ZoomIn()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L85) | Increases editor UI zoom by one bounded increment.
返回:true when the zoom changed. | — | — | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ZoomOut()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L93) | Decreases editor UI zoom by one bounded increment.
返回:true when the zoom changed. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetBreadcrumbHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L287) | Gets asset breadcrumb bar height. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetBreadcrumbSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L562) | Gets spacing around breadcrumb separators. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.assetCellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L237) | Gets asset browser cell padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetFrameRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L247) | Gets asset browser frame rounding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridCellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L297) | Gets asset grid cell padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridDefaultScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L302) | Gets default asset grid scale. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridFixedCellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L537) | Gets fixed padding added to calculated asset grid cells. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridIconHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L552) | Gets the horizontal inset that constrains an asset grid icon inside its card. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridIconLabelSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L557) | Gets the vertical spacing between an asset grid icon and its label. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridIconTopPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L547) | Gets the top inset reserved above an asset grid icon. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridLabelBottomPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L527) | Gets bottom padding for asset grid labels. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridLabelHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L522) | Gets horizontal padding for asset grid labels. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridLabelLineSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L532) | Gets the additional vertical spacing between asset grid label lines. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridMaximumScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L312) | Gets maximum asset grid scale. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridMinimumCellSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L292) | Gets minimum asset grid cell size. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridMinimumScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L307) | Gets minimum asset grid scale. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetGridScaleBias { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L542) | Gets the scale bias added to asset grid icons. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.assetItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L242) | Gets asset browser item spacing. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListContentHorizontalPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L517) | Gets the horizontal inset between an asset list separator and column content. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListMinimumColumnRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L507) | Gets the minimum normalized width reserved for each asset list column. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListNameSeparatorPosition { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L497) | Gets the default normalized position of the asset list name/type separator. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListRowSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L317) | Gets spacing between asset list rows. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListSeparatorHitWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L512) | Gets the horizontal hit width of an asset list column separator. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetListTypeSeparatorPosition { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L502) | Gets the default normalized position of the asset list type/source separator. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetPaneMinimumVisibleWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L277) | Gets the minimum visible width retained for either asset browser pane while its splitter is dragged. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetSplitterMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L282) | Gets the minimum draggable asset browser splitter width. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetToolbarSectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L492) | Gets spacing between asset browser toolbar sections. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetToolbarSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L487) | Gets the regular asset browser toolbar spacing. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.assetToolbarTightSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L482) | Gets the tight asset browser toolbar spacing. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.assetWindowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L232) | Gets asset browser window padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisPrefixMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L572) | Gets the minimum width of an axis prefix. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisPrefixWidthRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L577) | Gets the axis prefix share of the complete control. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisValueMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L567) | Gets the minimum width of one axis value field. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.borderSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L126) | Gets standard border thickness. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.breadcrumbFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L192) | Gets breadcrumb frame padding. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.cellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L222) | Gets standard table cell padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.columnMinimumSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L257) | Gets minimum column spacing. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.compactCheckboxSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L417) | Gets the compact checkbox size used by card headers. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.compactFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L177) | Gets compact frame padding. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.compactItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L207) | Gets compact vertical item spacing. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.disabledAlpha { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L111) | Gets disabled content opacity. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.disclosureButtonInset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L387) | Gets inspector disclosure inset. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.fontScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L106) | Gets global content scale. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.framePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L136) | Gets standard frame padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.frameRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L197) | Gets standard frame rounding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.grabMinimumSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L272) | Gets minimum grab size. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.hierarchyBlankMinimumHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L477) | Gets the minimum height of the hierarchy blank drop area. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.hierarchyItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L212) | Gets hierarchy item spacing. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.hierarchyRenameMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L467) | Gets the minimum inline hierarchy rename width. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.hierarchyRenameTrailingGap { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L472) | Gets the hierarchy rename trailing-control gap. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.iconLabelSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L412) | Gets spacing between a leading icon and its text. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.indentSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L252) | Gets tree indentation. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inlineRenameFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L182) | Gets the compact inner padding of inline rename fields. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorAddButtonTopPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L462) | Gets top padding above inspector add buttons. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inspectorCardBodyPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L342) | Gets inspector card body padding. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inspectorCardHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L347) | Gets inspector card header padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorCardSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L322) | Gets spacing between inspector cards. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorCollapsedSectionCapLength { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L367) | Gets the length of each centered end cap on a collapsed Inspector section. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorHeaderControlSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L422) | Gets spacing between leading controls inside card headers. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorHeaderSectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L457) | Gets spacing between distinct control groups in an Inspector target header row. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorSectionLegendGap { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L362) | Gets the gap between an Inspector section legend and its interrupted top border. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inspectorSectionPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L357) | Gets the inner padding of a framed Inspector section. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorSectionRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L382) | Gets the subtle corner rounding used by framed content sections inside editor containers. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorSectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L377) | Gets the vertical distance after a framed Inspector section. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inspectorTargetHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L327) | Gets the inner padding of the persistent Inspector target header. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorTargetHeaderRowSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L332) | Gets spacing between the two rows of the persistent Inspector target header. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.inspectorTargetIconScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L337) | Gets the font-size multiplier used by the large Inspector target icon. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.interactionOverlayThickness { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L582) | Gets the standard foreground interaction-overlay thickness. | — | — | -| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.isCompact { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L37) | Gets whether compact editor density is active. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.itemInnerSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L217) | Gets standard inner item spacing. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.itemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L202) | Gets standard item spacing. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.labelChipPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L427) | Gets the inner padding of compact colored label chips. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.labelChipRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L432) | Gets the corner rounding of compact colored label chips. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.logAutoScrollTolerance { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L397) | Gets the distance from the bottom treated as auto-scroll. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.logDisclosurePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L402) | Gets log disclosure padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.menuBorderSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L172) | Gets editor context-menu border thickness. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L151) | Gets the uniform item padding of editor context menus. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L162) | Gets the uniform spacing between editor context-menu items. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.menuRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L167) | Gets editor context-menu corner rounding. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuSearchFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L157) | Gets the compact padding used by the search field at the top of editor context menus. Its vertical padding matches the half-spacing that menu entries add above and below a line. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuWindowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L146) | Gets the uniform content padding of editor context menus. | — | — | -| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalFadeInSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L597) | Gets the modal fade-in duration in seconds. | — | — | -| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalFadeOutSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L607) | Gets the modal fade-out duration in seconds. | — | — | -| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalMinimumVisibleSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L602) | Gets the minimum modal visibility duration in seconds. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.modalWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L592) | Gets the standard centered modal width. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.panelTabFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L141) | Gets the frame padding used while docked panel tabs are laid out. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.propertyLabelRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L392) | Gets property label width ratio. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.propertyMetadataSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L372) | Gets the spacing around the separator between inline metadata and its value. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scriptCompilationWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L587) | Gets script compilation modal width. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scrollbarPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L267) | Gets the inset that keeps an overlay scrollbar grab visually lightweight. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scrollbarSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L262) | Gets scrollbar width. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.searchPopupWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L407) | Gets the default width of searchable editor popups. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.sectionHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L352) | Gets the shared padding around separator-style section titles. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.settingsFieldPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L227) | Gets the inner padding applied to one complete Settings field. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.statisticRowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L452) | Gets the vertical padding inside one statistics row. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.textDecorationOffset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L627) | Gets the vertical offset of text decorations from the baseline. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.toolbarFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L187) | Gets toolbar frame padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeFolderConnectorPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L622) | Gets additional connector padding for expandable tree nodes. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeGuideLeftOffset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L612) | Gets tree guide left offset. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeGuideLineOverlap { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L617) | Gets the overlap used to join adjacent tree guide segments. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.typeBadgePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L437) | Gets the compact inner padding of outlined type badges. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.typeBadgeRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L442) | Gets the corner rounding of outlined type badges. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.typeBadgeSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L447) | Gets the horizontal gap between a property name and its type badge. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.windowMinimumSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L131) | Gets minimum window size. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.windowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L116) | Gets standard window padding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.windowRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L121) | Gets standard window rounding. | — | — | -| [`public float Inno.Editor.ImGui.EditorStyleMetrics.zoom { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L32) | Gets the current editor UI zoom multiplier. | — | — | -| [`public sealed class Inno.Editor.ImGui.EditorStyleMetrics`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L9) | Defines named editor layout metrics used by widgets and feature panels. | — | — | - -### `Inno.Editor.ImGui.ImGui` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginChild(string id, System.Numerics.Vector2 size = default(System.Numerics.Vector2), Inno.Native.ImGui.ImGuiChildFlags flags = Inno.Native.ImGui.ImGuiChildFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L304) | Begins a child region.
返回:true when child contents should be submitted.
`id`:The child identity.
`size`:The requested child size.
`flags`:Child-region behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginCombo(string label, string preview, Inno.Native.ImGui.ImGuiComboFlags flags = Inno.Native.ImGui.ImGuiComboFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L399) | Begins a combo popup.
返回:true when combo contents should be submitted.
`label`:The control label.
`preview`:The preview text.
`flags`:Combo behavior. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.BeginDisabled(bool disabled = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L590) | Begins a disabled block.
`disabled`:Whether controls in the block are disabled. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.BeginGroup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L600) | Begins an item group. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginPopup(string id, Inno.Native.ImGui.ImGuiWindowFlags flags = Inno.Native.ImGui.ImGuiWindowFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L569) | Begins a named popup.
返回:true when popup contents should be submitted.
`id`:The popup identity.
`flags`:Popup window behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabBar(string id, Inno.Native.ImGui.ImGuiTabBarFlags flags = Inno.Native.ImGui.ImGuiTabBarFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L485) | Begins a tab bar.
返回:true when tab contents should be submitted.
`id`:The tab bar identity.
`flags`:Tab-bar behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabItem(string label, Inno.Native.ImGui.ImGuiTabItemFlags flags = Inno.Native.ImGui.ImGuiTabItemFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L507) | Begins one tab item.
返回:true when tab contents should be submitted.
`label`:The tab label and identity.
`flags`:Tab-item behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabItem(string label, ref bool isOpen, Inno.Native.ImGui.ImGuiTabItemFlags flags = Inno.Native.ImGui.ImGuiTabItemFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L527) | Begins one closeable tab item.
返回:true when tab contents should be submitted.
`label`:The tab label and identity.
`isOpen`:Receives whether the tab remains open.
`flags`:Tab-item behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.BeginTable(string id, int columns, Inno.Native.ImGui.ImGuiTableFlags flags = Inno.Native.ImGui.ImGuiTableFlags.None, System.Numerics.Vector2 size = default(System.Numerics.Vector2))`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L334) | Begins a table.
返回:true when table contents should be submitted.
`id`:The table identity.
`columns`:The positive column count.
`flags`:Table behavior.
`size`:The requested outer size. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.Button(string label, System.Numerics.Vector2 size = default(System.Numerics.Vector2))`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L35) | Draws a standard button.
返回:true when the button is activated.
`label`:The visible label and optional ImGui identity suffix.
`size`:The requested size, or zero components for automatic sizing. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.Checkbox(string label, ref bool value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L63) | Draws and edits a Boolean value.
返回:true when the value changes.
`label`:The control label.
`value`:The concrete value read or transformed by this operation. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.CloseCurrentPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L582) | Closes the current popup. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.CollapsingHeader(string label, Inno.Native.ImGui.ImGuiTreeNodeFlags flags = Inno.Native.ImGui.ImGuiTreeNodeFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L445) | Draws a collapsible section header.
返回:true when the section is open.
`label`:The visible label and identity.
`flags`:Tree-node behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.ColorEdit4(string label, ref System.Numerics.Vector4 value, Inno.Native.ImGui.ImGuiColorEditFlags flags = Inno.Native.ImGui.ImGuiColorEditFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L243) | Draws and edits a four-component color.
返回:true when the value changes.
`label`:The control label.
`value`:The RGBA value to display and update.
`flags`:Color editing behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.ColorEditLinear4(string label, ref System.Numerics.Vector4 value, Inno.Native.ImGui.ImGuiColorEditFlags flags = Inno.Native.ImGui.ImGuiColorEditFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L270) | Displays a linear RGBA color through a display-sRGB picker and returns edits in linear space.
返回:True if the display color changed and value contains decoded linear RGB.
`label`:The stable identity and visible label of the color control.
`value`:Linear RGB and alpha; only changed RGB channels are decoded, while alpha is never transfer-encoded.
`flags`:Dear ImGui color-edit flags; display modes interpret sRGB values and input storage must be RGB. | — | `System.ArgumentException`:HSV input storage conflicts with the linear-RGB value contract. | -| [`public static bool Inno.Editor.ImGui.ImGui.DragFloat(string label, ref float value, float speed = 1, float minimum = 0, float maximum = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L189) | Draws a floating-point drag control.
返回:true when the value changes.
`label`:The control label.
`value`:The value to display and update.
`speed`:The drag speed.
`minimum`:The inclusive minimum, or zero with a zero maximum for no clamping.
`maximum`:The inclusive maximum, or zero with a zero minimum for no clamping. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.DrawFilledRectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L899) | Adds a filled rectangle to the current window draw list.
`minimum`:The screen-space minimum corner.
`maximum`:The screen-space maximum corner.
`color`:The packed ImGui color.
`rounding`:The corner rounding radius. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.DrawLine(System.Numerics.Vector2 start, System.Numerics.Vector2 end, uint color, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L849) | Adds a line to the current window draw list.
`start`:The screen-space start point.
`end`:The screen-space end point.
`color`:The packed ImGui color.
`thickness`:The line thickness. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.DrawRectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L875) | Adds a rectangle outline to the current window draw list.
`minimum`:The screen-space minimum corner.
`maximum`:The screen-space maximum corner.
`color`:The packed ImGui color.
`rounding`:The corner rounding radius.
`thickness`:The outline thickness. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.DrawText(System.Numerics.Vector2 position, uint color, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L919) | Adds text to the current window draw list.
`position`:The screen-space text origin.
`color`:The packed ImGui color.
`text`:The text to draw. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.Dummy(System.Numerics.Vector2 size)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L724) | Advances layout by an invisible size.
`size`:The size consumed by dummy; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndChild()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L314) | Ends the current child region. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndCombo()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L409) | Ends the current combo popup. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndDisabled()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L595) | Ends the current disabled block. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndGroup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L605) | Ends the current item group. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L577) | Ends the current popup. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndTabBar()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L493) | Ends the current tab bar. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndTabItem()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L541) | Ends the current tab item. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.EndTable()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L345) | Ends the current table. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetContentRegionAvailable()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L740) | Gets the remaining content size in the current region.
返回:The remaining local width and height. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetCursorPosition()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L748) | Gets the current local cursor position.
返回:The local cursor position. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetCursorScreenPosition()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L764) | Gets the current screen-space cursor position.
返回:The screen-space cursor position. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetItemMaximum()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L780) | Gets the maximum screen-space corner of the previous item.
返回:The item maximum corner. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetItemMinimum()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L772) | Gets the minimum screen-space corner of the previous item.
返回:The item minimum corner. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.InputFloat(string label, ref float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L163) | Draws and edits a floating-point value.
返回:true when the value changes.
`label`:The control label.
`value`:The concrete value read or transformed by this operation. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.InputInt(string label, ref int value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L146) | Draws and edits an integer value.
返回:true when the value changes.
`label`:The control label.
`value`:The concrete value read or transformed by this operation. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.InputText(string label, ref string value, int capacity = 1024, Inno.Native.ImGui.ImGuiInputTextFlags flags = Inno.Native.ImGui.ImGuiInputTextFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L89) | Draws and edits a bounded UTF-8 text value.
返回:true when the control reports an edit or submit event.
`label`:The control label.
`value`:The managed string to display and update.
`capacity`:The positive maximum UTF-8 buffer capacity, including the terminator.
`flags`:Text editing behavior. | — | `System.ArgumentOutOfRangeException`:Thrown when capacity is not positive. | -| [`public static bool Inno.Editor.ImGui.ImGui.InputTextWithHint(string label, string hint, ref string value, int capacity = 1024, Inno.Native.ImGui.ImGuiInputTextFlags flags = Inno.Native.ImGui.ImGuiInputTextFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L123) | Draws and edits a bounded UTF-8 text value with placeholder text.
返回:true when the control reports an edit or submit event.
`label`:The control label.
`hint`:The placeholder shown while the value is empty.
`value`:The managed string to display and update.
`capacity`:The positive maximum UTF-8 buffer capacity, including the terminator.
`flags`:Text editing behavior. | — | `System.ArgumentOutOfRangeException`:Thrown when capacity is not positive. | -| [`public static bool Inno.Editor.ImGui.ImGui.IsItemActive()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L810) | Gets whether the previous item is active.
返回:true while the item owns an active interaction. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseButtonDown(Inno.Native.ImGui.ImGuiMouseButton button = Inno.Native.ImGui.ImGuiMouseButton.Left)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L836) | UI 帧中的按钮保持状态;必须与 item 命中/激活状态共同使用。 | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetMouseDragDelta(Inno.Native.ImGui.ImGuiMouseButton button = Inno.Native.ImGui.ImGuiMouseButton.Left, float threshold = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L851) | UI 拖动开始位置至当前指针的累计位移;负阈值使用 Editor 配置。 | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.IsItemClicked(Inno.Native.ImGui.ImGuiMouseButton button = Inno.Native.ImGui.ImGuiMouseButton.Left)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L821) | Gets whether the previous item was clicked with a mouse button.
返回:true when the item was clicked.
`button`:The mouse button to query. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.IsItemHovered(Inno.Native.ImGui.ImGuiHoveredFlags flags = Inno.Native.ImGui.ImGuiHoveredFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L802) | Gets whether the previous item is hovered.
返回:true when the query succeeds.
`flags`:Hovered query behavior. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.MeasureText(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L791) | Measures text using the current font.
返回:The measured width and height.
`text`:The text text validated by the measure text operation. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.OpenPopup(string id, Inno.Native.ImGui.ImGuiPopupFlags flags = Inno.Native.ImGui.ImGuiPopupFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L552) | Opens a named popup.
`id`:The popup identity.
`flags`:Popup opening behavior. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PopId()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L626) | Pops one identity from the ImGui ID stack. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PopStyleColor(int count = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L648) | Pops color overrides.
`count`:The positive number of overrides to pop. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PopStyleVar(int count = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L684) | Pops style-variable overrides.
`count`:The positive number of overrides to pop. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PushId(int id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L621) | Pushes an integer identity onto the ImGui ID stack.
`id`:The stable identity used to locate the requested value. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PushId(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L613) | Pushes a string identity onto the ImGui ID stack.
`id`:The stable identity used to locate the requested value. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PushStyleColor(Inno.Native.ImGui.ImGuiCol color, System.Numerics.Vector4 value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L637) | Pushes one color override.
`color`:The style color slot.
`value`:The linear RGBA value. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PushStyleVar(Inno.Native.ImGui.ImGuiStyleVar style, System.Numerics.Vector2 value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L673) | Pushes one two-component style override.
`style`:The style variable.
`value`:The vector value. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.PushStyleVar(Inno.Native.ImGui.ImGuiStyleVar style, float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L659) | Pushes one scalar style override.
`style`:The style variable.
`value`:The scalar value. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.SameLine(float offset = 0, float spacing = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L695) | Places the next item on the same line.
`offset`:The horizontal offset from the line start, or zero for automatic placement.
`spacing`:The explicit spacing, or a negative value for the current style spacing. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.Selectable(string label, bool selected = false, Inno.Native.ImGui.ImGuiSelectableFlags flags = Inno.Native.ImGui.ImGuiSelectableFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L426) | Draws one selectable item.
返回:true when the item is activated.
`label`:The visible label and identity.
`selected`:Whether the item is currently selected.
`flags`:Selectable behavior. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.Separator()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L703) | Draws a horizontal separator. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.SeparatorText(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L711) | Draws a labeled horizontal separator.
`label`:The label text validated by the separator text operation. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.SetCursorPosition(System.Numerics.Vector2 position)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L756) | Sets the current local cursor position.
`position`:The position consumed by set cursor position; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.SetNextItemWidth(float width)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L732) | Sets the width of the next item.
`width`:The width in logical units or pixels required by this operation. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.SliderFloat(string label, ref float value, float minimum, float maximum, Inno.Native.ImGui.ImGuiSliderFlags flags = Inno.Native.ImGui.ImGuiSliderFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L219) | Draws a bounded floating-point slider.
返回:true when the value changes.
`label`:The control label.
`value`:The value to display and update.
`minimum`:The inclusive minimum.
`maximum`:The inclusive maximum.
`flags`:Slider behavior. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.SmallButton(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L49) | Draws a compact button.
返回:true when the button is activated.
`label`:The label text validated by the small button operation. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.Spacing()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L716) | Adds one standard vertical spacing unit. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.TableHeadersRow()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L369) | Draws the table header row from configured column labels. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.TableNextColumn()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L382) | Advances to the next table column.
返回:true when the column is visible. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.TableNextRow()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L374) | Advances to the next table row. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.TableSetupColumn(string label, Inno.Native.ImGui.ImGuiTableColumnFlags flags = Inno.Native.ImGui.ImGuiTableColumnFlags.None, float widthOrWeight = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L359) | Declares one table column.
`label`:The column label.
`flags`:Column behavior.
`widthOrWeight`:The initial fixed width or stretch weight. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.Text(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L21) | Draws unformatted text.
`text`:The text text validated by the text operation. | — | — | -| [`public static uint Inno.Editor.ImGui.ImGui.ToPackedColor(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L832) | Converts a floating-point RGBA color to Dear ImGui packed color order.
返回:The packed color value.
`color`:The color consumed by to packed color; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGui.TreeNode(string label, Inno.Native.ImGui.ImGuiTreeNodeFlags flags = Inno.Native.ImGui.ImGuiTreeNodeFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L463) | Draws a tree node and pushes its tree scope when it is open.
返回:true when child contents should be submitted.
`label`:The visible label and identity.
`flags`:Tree-node behavior. | — | — | -| [`public static void Inno.Editor.ImGui.ImGui.TreePop()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L471) | Ends the current open tree-node scope. | — | — | -| [`public static class Inno.Editor.ImGui.ImGui`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L13) | Exposes a script-safe, pointer-free subset of Dear ImGui for custom Editor tools. Every call is valid only while an Editor drawing callback is active. | — | — | - -### `Inno.Editor.ImGui.ImGuiEditorRuntime` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.ImGui.ImGuiEditorRuntime.ImGuiEditorRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IEnumerable hostServices)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L51) | Creates an ImGui editor runtime with stable host-owned extension services.
`context`:The shared editor context that owns project settings and frame state.
`types`:The host-owned type catalog that coordinates extension generations.
`logs`:The host-owned logging router used by editor infrastructure.
`hostServices`:Services available to discovered extension constructors. | — | `System.ArgumentNullException`:Thrown when context, types, or hostServices is null. | -| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Dispose()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L267) | Releases the resources owned by this implementation. | — | `Inno.Core.Execution.RetirementPendingException`:Editor extensions still own live work. The interaction runtime remains retained for safe shutdown. | -| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.Draw()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L120) | Draws the complete editor frame through ImGui. | — | — | -| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.HandleKeyPressed(Inno.Core.Events.KeyPressedEvent keyEvent)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L248) | Dispatches contextual shortcuts, leaving editing keys with active text widgets while permitting explicit save.
`keyEvent`:The keyboard event received from the application event stream. | — | — | -| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.PrepareShutdown()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L111) | Freezes automatic extension-state persistence and writes the final project state before editor modules begin shutting down. | This operation is idempotent and prevents module teardown from overwriting the saved module sections with transient empty state. | `System.ObjectDisposedException`:Thrown after this runtime has been disposed. | -| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.SaveState()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L94) | Captures all stateful active modules and panels and flushes their project state to disk. | — | `System.ObjectDisposedException`:Thrown after this runtime has been disposed. | -| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Start()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L78) | Starts value processing after validating the current state. | — | — | -| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L86) | Recomputes owned state from the current validated inputs.
`frame`:The frame consumed by update; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.ImGui.ImGuiEditorRuntime.interactions { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L68) | Gets the active presentation-independent interaction entry point. | — | — | -| [`public int Inno.Editor.ImGui.ImGuiEditorRuntime.panelCount { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L73) | Gets the number of active dockable panels. | — | — | -| [`public sealed class Inno.Editor.ImGui.ImGuiEditorRuntime : Inno.Editor.Core.EditorRuntime`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L23) | Presents the backend-independent editor interaction runtime through ImGui. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AddGlyphCentered(Inno.Native.ImGui.ImDrawList* drawList, Inno.Native.ImGui.ImFont* font, float fontSize, string text, System.Numerics.Vector2 center, uint color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L80) | Draws one glyph so the center of its visible bounds matches a requested point.
`drawList`:Draw list that receives the glyph.
`font`:Font that owns the glyph.
`fontSize`:Requested baked font size.
`text`:Text containing the glyph to draw.
`center`:Target center in screen coordinates.
`color`:Packed ImGui text color. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AxisDragFloat(string id, string axis, ref float value, float width, float speed = 0.1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L225) | Draws a float drag field with a compact colored axis prefix.
返回:true when the value changed.
`id`:Stable control identifier.
`axis`:Axis label such as X, Y, Z, or W.
`value`:Mutable numeric value.
`width`:Total width including the axis prefix.
`speed`:Drag speed. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AxisDragInt(string id, string axis, ref int value, float width, float speed = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L351) | Draws an integer drag field with a compact colored axis prefix.
返回:true when the value changed.
`id`:Stable control identifier.
`axis`:Axis label such as X, Y, Z, or W.
`value`:Mutable numeric value.
`width`:Total width including the axis prefix.
`speed`:Drag speed. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo(string id, string preview, Inno.Native.ImGui.ImGuiComboFlags flags = Inno.Native.ImGui.ImGuiComboFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L40) | Begins a combo whose popup opens below the control and stays within its containing window.
返回:true when the combo popup is open and its contents should be submitted; otherwise, false.
`id`:Stable combo identifier in the current ImGui scope.
`preview`:Text displayed by the closed combo.
`flags`:Native combo presentation flags. Only Inno.Native.ImGui.ImGuiComboFlags.WidthFitPreview is supported. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when preview is null.
`System.ArgumentOutOfRangeException`:Thrown when flags contains a presentation mode unsupported by the editor selector. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginContextMenu(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L163) | Begins a parent-viewport styled right-click context menu for the most recently submitted item.
返回:true when context-menu content should be drawn; otherwise, false.
`id`:The stable popup identifier in the current ImGui ID scope. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuPopup(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L34) | Begins an explicitly opened popup using the editor context-menu presentation contract. The popup stays in its parent viewport, sizes itself to submitted content, and scrolls when its work-area bound is reached.
返回:true when popup content should be submitted; otherwise, false.
`id`:The stable identifier previously passed to ImGui when opening the popup. | — | `System.ArgumentException`:Thrown when id is empty. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuSelector(string id, string preview, float width, float minimumPopupWidth)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L90) | Draws a compact selector control and begins a work-area-bounded menu popup.
返回:true when the popup is open and its contents should be submitted; otherwise, false.
`id`:Stable selector identifier in the current ImGui scope.
`preview`:Text displayed by the closed selector.
`width`:Width reserved for the selector control.
`minimumPopupWidth`:Minimum outer width of the popup, including its window padding. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when preview is null.
`System.ArgumentOutOfRangeException`:Thrown when width or minimumPopupWidth is not positive. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuTooltip()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L63) | Begins a parent-viewport tooltip using the same padding, colors, border, and spacing as editor menus.
返回:true when tooltip content should be submitted; otherwise, false. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginSearchPopup(string id, ref string query, string hint, nuint capacity = 256, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L67) | Begins a popup and draws its focused search field.
返回:true when popup content should be drawn; otherwise, false.
`id`:The popup identifier previously supplied to ImGui.
`query`:The mutable search query.
`hint`:The hint shown while the query is empty.
`capacity`:The maximum UTF-8 buffer capacity.
`width`:The search field width, or a negative value to use the editor default. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginWindowContextMenu(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L184) | Begins a parent-viewport styled right-click context menu for the current window's unoccupied background.
返回:true when context-menu content should be drawn; otherwise, false.
`id`:The stable popup identifier in the current ImGui ID scope. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CardBody(string id, System.Action drawContent, bool dimmed = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L253) | Draws expanded collapsible-card content inside a framed body.
`id`:The stable card identifier.
`drawContent`:The callback that draws card content.
`dimmed`:Whether content is visually disabled and non-interactive. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredButton(string label, float topPadding = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L262) | Draws a horizontally centered button with optional space above it.
返回:true when the button is pressed.
`label`:Visible label and optional ImGui identifier suffix.
`topPadding`:Additional vertical space above the button. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredProgressBar(float fraction, System.Numerics.Vector2 size, string overlay)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L298) | Draws a progress bar whose overlay remains centered over the complete bar.
`fraction`:The completed fraction rendered by the progress bar.
`size`:The requested ImGui progress-bar size.
`overlay`:The text drawn at the geometric center of the complete progress bar. | — | `System.ArgumentNullException`:Thrown when overlay is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredText(string text, System.Numerics.Vector2 areaSize)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L311) | Draws non-interactive text centered inside a reserved layout area.
`text`:Visible text.
`areaSize`:Size of the layout area that contains the text. | — | `System.ArgumentNullException`:Thrown when text is null.
`System.ArgumentOutOfRangeException`:Thrown when either component of areaSize is not positive. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredWrappedText(string text, System.Numerics.Vector2 areaSize, System.Numerics.Vector2 padding)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L351) | Draws wrapped non-interactive text as a centered block inside a padded layout area.
`text`:The literal text to wrap and draw.
`areaSize`:The complete area reserved by the widget.
`padding`:The minimum horizontal and vertical distance retained from the area edges whenever space permits. | — | `System.ArgumentNullException`:Thrown when text is null.
`System.ArgumentOutOfRangeException`:Thrown when an area component is not positive or a padding component is negative. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Checkbox(string label, ref bool value, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L239) | Draws a standard checkbox with the shared editor tooltip behavior.
返回:true when the value changed.
`label`:Visible label and stable ImGui identifier.
`value`:Mutable checked state.
`tooltip`:Optional explanation displayed while the checkbox is hovered. | — | `System.ArgumentException`:Thrown when label is empty or whitespace. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableIcon(string id, string icon, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L199) | Draws a clickable icon inside the same square interaction slot used by editor close controls. The icon has no resting background and changes only its glyph color while hovered or active.
返回:true when the icon is pressed.
`id`:Stable identifier used by ImGui to track the interaction.
`icon`:The visible icon glyph.
`tooltip`:Optional tooltip displayed while the interaction is hovered. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when icon is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableText(string id, string text, System.Numerics.Vector2 controlSize, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L257) | Draws clickable text centered inside an explicitly sized transparent interaction area.
返回:true when the text is pressed.
`id`:Stable identifier used by ImGui to track the interaction.
`text`:Visible text or icon glyph.
`controlSize`:Size of the transparent interaction area.
`tooltip`:Optional tooltip displayed while the interaction is hovered. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when text is null.
`System.ArgumentOutOfRangeException`:Thrown when either component of controlSize is not positive. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableText(string id, string text, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L122) | Draws clickable text without a persistent or hovered background.
返回:true when the text is pressed.
`id`:Stable identifier used by ImGui to track the interaction.
`text`:Visible text or icon glyph.
`tooltip`:Optional tooltip displayed while the interaction is hovered. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when text is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CollapsingCard(string id, string title, System.Action? drawLeadingControl = null, System.Action? drawTrailingControl = null, bool defaultOpen = true, bool dimmed = false, float trailingControlWidth = 0, System.Action? drawContextMenu = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L44) | Draws a component-style collapsible card header.
返回:true when card content should be drawn; otherwise, false.
`id`:The stable card identifier.
`title`:The visible card title.
`drawLeadingControl`:An optional control drawn before the title.
`drawTrailingControl`:An optional control aligned to the right edge.
`defaultOpen`:Whether the card starts expanded.
`dimmed`:Whether header controls and title use the inactive text color.
`trailingControlWidth`:The optional width reserved for the trailing control group.
`drawContextMenu`:An optional callback that binds a context menu to the complete header item. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CollectionSectionHeader(string title)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L76) | Draws a square collection-section band whose background, separator, and padding are shared with hierarchy and Inspector presentation.
`title`:Literal section title. | — | `System.ArgumentNullException`:Thrown when title is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ColoredText(System.Numerics.Vector4 color, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L416) | Draws unformatted text with a temporary foreground color.
`color`:The foreground color applied only while drawing the text.
`text`:The literal text to draw without format-string interpretation. | — | `System.ArgumentNullException`:Thrown when text is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactCheckbox(string id, ref bool value, float size = -1, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L156) | Draws a compact checkbox whose checked fill uses the current text color.
返回:true when the value changed.
`id`:Stable control identifier.
`value`:Mutable checked state.
`size`:Visual square size in pixels.
`tooltip`:Optional explanation displayed while the checkbox is hovered. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactDragFloat(string label, ref float value, float speed = 0.1, float? minimum = null, float? maximum = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L257) | Draws a floating-point drag field with a compact one-decimal presentation and precise text editing.
返回:true when the exact value changed.
`label`:Stable ImGui label and identifier.
`value`:Exact floating-point value to edit. Compact presentation never rounds the stored value.
`speed`:Drag speed.
`minimum`:Optional inclusive lower bound.
`maximum`:Optional inclusive upper bound. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactSliderFloat(string label, ref float value, float minimum, float maximum)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L300) | Draws a bounded slider with compact presentation and a double-click precise input mode.
返回:true when the exact value changed.
`label`:Stable ImGui label and identifier.
`value`:Exact floating-point value to edit. Compact presentation never rounds the stored value.
`minimum`:Inclusive lower bound.
`maximum`:Inclusive upper bound. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ConstrainedContent(string id, System.Action drawContent, bool useWindowPadding = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L112) | Draws a vertically auto-sized content region that is constrained to the current available width and cannot create an independent scroll range.
`id`:Stable identifier used by ImGui to track the content region.
`drawContent`:Callback that draws the complete region contents.
`useWindowPadding`:Whether the region should apply the centralized standard window padding exactly once. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when drawContent is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Disabled(bool disabled, System.Action draw)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L371) | Draws content inside an ImGui disabled scope when requested.
`disabled`:Whether interaction is disabled.
`draw`:Drawing callback. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropSource(string payloadType, in TPayload payload, System.Action? drawPreview = null, bool allowHoldToOpenOthers = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L38) | Publishes an unmanaged drag payload for the most recently submitted item.
返回:true while the item is an active drag source.
`payloadType`:Stable ImGui payload type identifier.
`payload`:Payload value.
`drawPreview`:Optional preview drawing callback.
`allowHoldToOpenOthers`:Whether hovering over another openable control may open it after the native drag-hold delay. | — | `System.ArgumentException`:Thrown when payloadType is empty or whitespace. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropSource(string payloadType, System.Func payloadFactory, System.Action? drawPreview = null, bool allowHoldToOpenOthers = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L88) | Publishes a lazily created unmanaged drag payload for the most recently submitted item.
返回:true while the item is an active drag source.
`payloadType`:Stable ImGui payload type identifier.
`payloadFactory`:Creates the payload only after dragging starts.
`drawPreview`:Optional preview drawing callback.
`allowHoldToOpenOthers`:Whether hovering over another openable control may open it after the native drag-hold delay. | — | `System.ArgumentException`:Thrown when payloadType is empty or whitespace.
`System.ArgumentNullException`:Thrown when payloadFactory is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload, out bool isPreviewing, bool drawDefaultHighlight) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L186) | Accepts an unmanaged payload and controls whether ImGui draws its default target rectangle.
返回:true only when a compatible payload is delivered.
`payloadType`:Stable ImGui payload type identifier.
`payload`:Previewed or delivered payload value.
`isPreviewing`:Whether a compatible payload is hovering over the target.
`drawDefaultHighlight`:Whether ImGui draws its default target rectangle. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload, out bool isPreviewing) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L155) | Accepts an unmanaged payload and reports its preview state on the most recently submitted item.
返回:true only when a compatible payload is delivered.
`payloadType`:Stable ImGui payload type identifier.
`payload`:Previewed or delivered payload value.
`isPreviewing`:Whether a compatible payload is hovering over the target. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L128) | Accepts an unmanaged payload on the most recently submitted item.
返回:true only when a compatible payload is delivered.
`payloadType`:Stable ImGui payload type identifier.
`payload`:Delivered payload value. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint targetId, out TPayload payload, out bool isPreviewing, bool drawDefaultHighlight = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L248) | Accepts an unmanaged payload on an explicit screen-space rectangle.
返回:true only when a compatible payload is delivered.
`payloadType`:Stable ImGui payload type identifier.
`minimum`:Minimum screen coordinate of the complete target.
`maximum`:Maximum screen coordinate of the complete target.
`targetId`:ImGui identifier unique within the current window.
`payload`:Previewed or delivered payload value.
`isPreviewing`:Whether a compatible payload is hovering over the target.
`drawDefaultHighlight`:Whether ImGui draws its default target rectangle. | — | `System.ArgumentException`:Thrown when payloadType is empty or whitespace. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawDisclosureIndicator(System.Numerics.Vector2 min, System.Numerics.Vector2 max, bool open, bool dimmed = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L208) | Draws a vertically centered disclosure triangle with button-style hover feedback.
`min`:The minimum screen coordinate of the indicator area.
`max`:The maximum screen coordinate of the indicator area.
`open`:Whether the represented content is expanded.
`dimmed`:Whether the indicator uses the inactive text color. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawItemTooltip(string? text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L88) | Draws a consistently sized, wrapped editor tooltip for the most recently submitted item.
`text`:Tooltip text. Empty values do not draw a tooltip. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawTooltip(string? text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L105) | Draws the standard viewport-clamped tooltip when a custom-drawn canvas element is hovered.
`text`:Tooltip contents. The caller owns hit testing; empty text draws nothing. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DropTargetHighlight(System.Numerics.Vector2 min, System.Numerics.Vector2 max)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L422) | Draws the standard yellow rectangular drag-and-drop target highlight above all normal window content in screen coordinates.
`min`:Minimum target coordinate.
`max`:Maximum target coordinate. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L60) | Ends a combo opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo(System.String,System.String,Inno.Native.ImGui.ImGuiComboFlags). | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndContextMenu()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L203) | Ends a context menu opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginContextMenu(System.String) or Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginWindowContextMenu(System.String). | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L50) | Ends a popup opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuPopup(System.String) and restores the previous style. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuSelector()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L134) | Ends a selector popup opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuSelector(System.String,System.String,System.Single,System.Single). | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuTooltip()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L76) | Ends a tooltip opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuTooltip and restores the previous style. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndSearchPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L89) | Ends a popup opened by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginSearchPopup(System.String,System.String@,System.String,System.UIntPtr,System.Single). | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EnsureSection(string title = "Properties", string? description = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L148) | Ensures that subsequently drawn default properties belong to a framed section.
返回:true when the current section accepts content; otherwise, false when that section is collapsed.
`title`:Title used only when the current section layout has not declared a section yet.
`description`:Optional hover explanation for the implicit section title. | — | `System.ArgumentNullException`:Thrown when title is null. | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetClickableTextSize(string text, System.Numerics.Vector2 padding)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L475) | Calculates a clickable text area from visible text and requested inner padding.
返回:The complete transparent interaction area size.
`text`:Visible text whose dimensions determine the content size.
`padding`:Horizontal and vertical padding surrounding the text. | — | `System.ArgumentNullException`:Thrown when text is null.
`System.ArgumentOutOfRangeException`:Thrown when either component of padding is negative. | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetCompactClickableTextSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L498) | Gets the compact fixed interaction size used by icon-style clickable text.
返回:A fixed-size area that follows the editor icon slot convention. | — | — | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetCompactIconSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L510) | Gets the square icon interaction size shared by dock-header close controls and compact editor icons.
返回:A square size based on the current ImGui font size. | — | — | -| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetGlyphVisualBounds(Inno.Native.ImGui.ImFont* font, float fontSize, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L36) | Gets the visible bounds of the first glyph in a string at a requested font size.
返回:A vector containing the glyph's left, top, right, and bottom offsets relative to the text drawing origin.
`font`:Font that owns the glyph.
`fontSize`:Requested baked font size.
`text`:Text whose first Unicode scalar identifies the glyph. | — | `System.ArgumentException`:Thrown when text is empty. | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetLabelChipSize(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L26) | Gets the layout size of a compact colored label chip using the centralized editor style.
返回:The size reserved by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.LabelChip(System.String,System.Numerics.Vector4) for the supplied text.
`label`:Visible label text. | — | `System.ArgumentNullException`:Thrown when label is null. | -| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetTypeBadgeSize(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L82) | Gets the layout size of a compact outlined type badge.
返回:The size reserved by Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TypeBadge(System.String,System.Numerics.Vector4,System.String).
`label`:Visible type name. | — | `System.ArgumentNullException`:Thrown when label is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HeaderSurface(string id, System.Action drawContent, bool spanWindowPadding = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L164) | Draws a square-cornered editor header surface using the shared target-header palette, border, and padding.
`id`:Stable identifier used by ImGui to track the header region.
`drawContent`:Callback that draws the complete header content.
`spanWindowPadding`:Whether the header should extend through the current parent window padding to both edges. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when drawContent is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HelpBox(string text, string icon, System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L28) | Draws a wrapped contextual message with a semantic icon and a subdued status surface.
`text`:Literal message shown within the current content width.
`icon`:Icon-font glyph identifying the message severity.
`color`:Semantic accent for the icon, outline, and leading stripe. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Hint(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L352) | Draws disabled hint text that wraps to the current content width.
`text`:Hint text. | — | `System.ArgumentNullException`:Thrown when text is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HoverText(string id, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L147) | Draws a text link with no button background and highlights it only through text presentation.
返回:true when the link is pressed.
`id`:Stable identifier used by ImGui to track the interaction.
`text`:Visible link text. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentNullException`:Thrown when text is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.IconText(string icon, string text, bool highlight)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L544) | Draws icon and text with the icon's visible glyph bounds centered in a slot that expands when the glyph is wider than the normal editor icon slot.
`icon`:Icon text.
`text`:Main text.
`highlight`:Whether to underline and emphasize the drawn icon and text. | — | — | -| [`public static Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.InlineRename(string id, ref string text, ref bool requestFocus, float rowHeight, nuint capacity = 512, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L47) | Draws a compact single-line rename editor inside an existing row.
返回:The current rename outcome.
`id`:The stable control identifier.
`text`:The mutable text buffer.
`requestFocus`:Whether keyboard focus and complete-value selection should be requested. The value remains true until the input becomes active and its current contents are selected.
`rowHeight`:The height of the row area in which the field is centered.
`capacity`:The maximum UTF-8 buffer capacity.
`width`:The control width, or a negative value to fill the available space. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentOutOfRangeException`:Thrown when rowHeight is not finite or is not positive. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.InsertionLine(float fromX, float toX, float y)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L399) | Draws a horizontal insertion marker above normal window content in screen coordinates.
`fromX`:Marker start X coordinate.
`toX`:Marker end X coordinate.
`y`:Marker Y coordinate. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.LabelChip(string label, System.Numerics.Vector4 background)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L49) | Draws non-interactive text centered on a compact, softly rounded colored background.
`label`:Visible label text.
`background`:Background color drawn behind the label. | — | `System.ArgumentNullException`:Thrown when label is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.MetadataValue(string metadata, System.Action drawValue, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L152) | Draws a subdued metadata prefix followed by an interactive value on the same line.
`metadata`:Short contextual text such as a declared value type.
`drawValue`:Callback that draws the value or action following the metadata.
`tooltip`:Optional explanation displayed while the metadata is hovered. | — | `System.ArgumentNullException`:Thrown when metadata or drawValue is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.MetadataValue(string metadata, string value, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L185) | Draws one wrapped, subdued value whose metadata and content remain in the value column.
`metadata`:Short contextual text such as a declared value type.
`value`:Literal value description shown after the metadata separator.
`tooltip`:Optional explanation displayed while the combined value is hovered. | — | `System.ArgumentNullException`:Thrown when metadata or value is null. | -| [`public static string Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.NicifyName(string name)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L444) | Converts an identifier into a readable editor label.
返回:A label with separators and title casing.
`name`:Source identifier. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PanelWindow(string title, ref bool isOpen, System.Action drawBody, Inno.Native.ImGui.ImGuiWindowFlags flags = Inno.Native.ImGui.ImGuiWindowFlags.NoCollapse, bool useWindowPadding = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L34) | Opens a standard panel window and executes panel body.
`title`:Window title.
`isOpen`:Visible state.
`drawBody`:Panel body callback.
`flags`:Window flags.
`useWindowPadding`:Whether the panel body should use the current standard window padding. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyLabel(string label, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L126) | Draws a label within its current table cell, wrapping when the column is narrow.
`label`:Visible label text.
`tooltip`:Optional explanation shown while the label is hovered. | — | `System.ArgumentNullException`:Thrown when label is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyRow(string id, System.Action drawLabel, System.Action drawValue)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L70) | Draws a two-column property row whose label is supplied by a custom presentation callback.
`id`:Stable row identifier.
`drawLabel`:Label presentation callback. It may wrap or draw badges inside the label column.
`drawValue`:Value control callback. | — | `System.ArgumentException`:Thrown when id is empty or whitespace.
`System.ArgumentNullException`:Thrown when drawLabel or drawValue is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyRow(string id, string label, System.Action drawValue, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L39) | Draws a two-column property row with a stable internal identifier.
`id`:Stable row identifier.
`label`:Human-readable property label.
`drawValue`:Value control callback.
`tooltip`:Optional hover help displayed from the property label. | — | `System.ArgumentException`:Thrown when id is empty or whitespace.
`System.ArgumentNullException`:Thrown when label or drawValue is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SearchInput(string id, string hint, ref string query, nuint capacity = 256, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L34) | Draws a single-line search field with a stable identifier.
返回:true when the query changed; otherwise, false.
`id`:The stable control identifier.
`hint`:The hint shown while the query is empty.
`query`:The mutable search query.
`capacity`:The maximum UTF-8 buffer capacity.
`width`:The control width, or a negative value to fill the available space. | — | — | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SectionHeader(string title, string? description = null, System.Action? drawLeadingControl = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L180) | Draws a section heading with hover-only description using the shared Inspector presentation.
返回:true when the section content should be drawn; otherwise, false when the section is collapsed.
`title`:Section title.
`description`:Optional hover explanation.
`drawLeadingControl`:Optional interactive control drawn before the section title. | — | `System.ArgumentNullException`:Thrown when title is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SectionLayout(System.Action drawContent)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L106) | Draws content in a scope where consecutive section headers become framed fieldsets.
`drawContent`:Callback that draws all content participating in the section layout. | — | `System.ArgumentNullException`:Thrown when drawContent is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetNextTreeNodeOpen(bool open)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L176) | Overrides the retained expansion state of the next submitted non-leaf tree row.
`open`:Whether the next row should be expanded. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetupPropertyColumns()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L108) | Configures the label and input columns of an active table with a shared two-to-three ratio. A following fixed-width action column may be added by the caller. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetupStyle()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs#L25) | Applies the centralized editor layout metrics and palette to the current ImGui context. | — | — | -| [`public static Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TreeNode(string id, System.Action onDraw, in Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions options)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L47) | Draws a full-width interactive tree row whose non-leaf content toggles expansion when double-clicked, while preserving single-click disclosure-arrow behavior.
返回:Interaction and row geometry for the submitted item.
`id`:Stable row identifier.
`onDraw`:Content drawing callback that receives the native row geometry established by ImGui.
`options`:Tree row options. | — | — | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TypeBadge(string label, System.Numerics.Vector4 accent, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L104) | Draws a compact, non-interactive type badge with a subdued fill and semantic outline.
`label`:Visible type name.
`accent`:Semantic type-family accent.
`tooltip`:Optional explanation shown while the badge is hovered. | — | `System.ArgumentNullException`:Thrown when label is null. | -| [`public static void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.WrappedText(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L442) | Draws literal text wrapped to the remaining content width without using a native variadic formatting entry point.
`text`:The literal text to draw. | — | `System.ArgumentNullException`:Thrown when text is null. | -| [`public static bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.isSectionContentVisible { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L130) | Gets whether content belonging to the current Inspector section should be drawn. | Attribute-driven drawers use this value to skip properties until the next section header when the current section is collapsed. | — | -| [`public static Inno.Editor.ImGui.EditorStyleMetrics Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.style { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs#L20) | Gets the centralized editor layout metrics shared by every widget and feature panel. | — | — | -| [`public static class Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L12) | Provides reusable editor controls and rendering helpers built on the native ImGui API. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.InlineRenamePresentation(string id, float width, float rowHeight, nuint bufferSize = 512)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L176) | Creates inline rename presentation data.
`id`:The stable ImGui identifier used by the input field.
`width`:The requested input width in logical pixels.
`rowHeight`:The height of the row area in which the field is centered.
`bufferSize`:The maximum UTF-8 buffer size accepted by the input. | — | `System.ArgumentException`:Thrown when id is empty.
`System.ArgumentOutOfRangeException`:Thrown when width or rowHeight is not finite and positive, or when bufferSize is zero. | -| [`public nuint Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.bufferSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L229) | Gets the maximum UTF-8 buffer size accepted by the input. | — | — | -| [`public string Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.id { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L214) | Gets the stable ImGui identifier used by the input field. | — | — | -| [`public float Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.rowHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L224) | Gets the height of the row area in which the input field is centered. | — | — | -| [`public float Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.width { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L219) | Gets the requested input width in logical pixels. | — | — | -| [`public sealed class Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L152) | Describes the view-owned geometry used to present an active inline rename action. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.Cancel = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L255) | Indicates that the edited text should be discarded. | — | — | -| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.Commit = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L245) | Indicates that the edited text should be committed. | — | — | -| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.FocusLost = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L250) | Indicates that the input lost focus and its valid value should be committed before closing. | — | — | -| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L240) | Indicates that the interaction remains active. | — | — | -| [`public enum Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L235) | Describes the outcome of an inline rename control. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.TreeNodeDrawContext` - + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public float Inno.Editor.ImGui.ImGuiWidget.TreeNodeDrawContext.rowHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L403) | Gets the actual native row height established for the current tree node. | — | — | -| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeDrawContext`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L393) | Provides the native geometry of a tree row while its custom content is being drawn. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions` - + + + @@ -1148,21 +1446,8 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Numerics.Vector4 Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.backgroundColor { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L429) | Gets the custom background color used when Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.showBackground is enabled. | — | — | -| [`public System.Action? Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.drawViewportOverlay { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L445) | Gets the callback that draws controls fixed to the current viewport without extending the tree's horizontal content boundary. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.hideGuideLines { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L439) | Gets whether ancestor and branch guide lines are omitted for this row. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.isLeaf { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L419) | Gets whether the row has no expandable children. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.selected { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L414) | Gets whether the row is selected. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.showBackground { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L424) | Gets whether a custom background is drawn behind an unselected row. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.suppressHoverHighlight { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L434) | Gets whether the row keeps its configured background while hovered. | — | — | -| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L409) | Configures an interactive tree row. | — | — | - -### `Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult` - + @@ -1171,28 +1456,1244 @@ Scripting facade 同时导出 `TreeNodeDrawContext`、`TreeNodeOptions`、`TreeN -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.contentMin { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L505) | Gets the minimum screen coordinate of the row's interactive content, excluding tree indentation. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isClicked { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L479) | Gets whether the content row was clicked. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isDoubleClicked { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L485) | Gets whether the content row was double-clicked. Double-clicking a non-leaf content row also toggles its retained expansion state for the next frame. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L490) | Gets whether the full row is hovered. | — | — | -| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isOpen { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L474) | Gets whether child content should be rendered. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.max { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L500) | Gets the row maximum screen coordinate. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.min { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L495) | Gets the row minimum screen coordinate. | — | — | -| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L451) | Describes interaction state produced by a tree row. | — | — | +| [`public static class Inno.Editor.ImGui.ImGui`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs#L9) | Provides pointer-free controls within the active Editor UI session. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.InvisibleButton(string label, System.Numerics.Vector2 size, Inno.Editor.ImGui.ImGuiButtonFlags flags = Inno.Editor.ImGui.ImGuiButtonFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs#L26) | Submits an explicitly drawn control interaction rectangle.
`label`: The visible label and optional stable ID suffix.
`size`: The explicit two-dimensional extent.
`flags`: The explicit control and interaction options. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.MenuItem(string label, string shortcut = "", bool selected = false, bool enabled = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs#L53) | Draws a menu item with explicit availability and checked state.
`label`: The visible label and optional stable ID suffix.
`shortcut`: A display label only; execution remains with the Action router.
`selected`: Whether the item is currently selected.
`enabled`: Whether the control can accept input. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.SliderInt(string label, ref int value, int minimum, int maximum, string format = "%d", Inno.Editor.ImGui.ImGuiSliderFlags flags = Inno.Editor.ImGui.ImGuiSliderFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs#L87) | Edits an integer within explicit inclusive bounds.
`label`: The visible label and optional stable ID suffix.
`value`: The caller-owned value updated only after an accepted edit.
`minimum`: The inclusive lower bound or upper-left corner.
`maximum`: The inclusive upper bound or lower-right corner.
`format`: The numeric display format.
`flags`: The explicit control and interaction options. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.DragInt(string label, ref int value, float speed = 1, int minimum = 0, int maximum = 0, string format = "%d", Inno.Editor.ImGui.ImGuiSliderFlags flags = Inno.Editor.ImGui.ImGuiSliderFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs#L126) | Edits an integer by pointer drag with explicit speed and optional bounds.
`label`: The visible label and optional stable ID suffix.
`value`: The caller-owned value updated only after an accepted edit.
`speed`: The pointer movement multiplier.
`minimum`: The inclusive lower bound or upper-left corner.
`maximum`: The inclusive upper bound or lower-right corner.
`format`: The numeric display format.
`flags`: The explicit control and interaction options. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static uint Inno.Editor.ImGui.ImGui.GetStyleColor(Inno.Editor.ImGui.ImGuiCol color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs#L20) | Gets the current style color including the active alpha.
`color`: The packed RGBA color or semantic style slot. | A copied value from the active presentation context. | — | +| [`public static uint Inno.Editor.ImGui.ImGui.GetControlId(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs#L35) | Derives a control identity from the current window and ID stack.
`label`: The visible label and optional stable ID suffix. | A copied value from the active presentation context. | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushClipRect(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, bool intersect)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs#L53) | Clips subsequent controls and drawing to a screen rectangle.
`minimum`: The inclusive lower bound or upper-left corner.
`maximum`: The inclusive upper bound or lower-right corner.
`intersect`: Whether to intersect the enclosing clip. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PopClipRect()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs#L65) | Restores the enclosing control clip after a matching push. | — | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetMousePos()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L17) | Gets the current pointer position in screen-space UI units. | A copied value from the active presentation context. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseClicked(Inno.Editor.ImGui.ImGuiMouseButton button, bool repeat = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L35) | Checks a pointer-button press in the current frame.
`button`: The pointer button to inspect.
`repeat`: Whether a held key or button may produce repeats. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseReleased(Inno.Editor.ImGui.ImGuiMouseButton button)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L52) | Checks a pointer-button release in the current frame.
`button`: The pointer button to inspect. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseDoubleClicked(Inno.Editor.ImGui.ImGuiMouseButton button)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L67) | Checks the backend-classified double-click event.
`button`: The pointer button to inspect. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsKeyDown(Inno.Editor.ImGui.ImGuiKey key)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L82) | Checks whether a presentation key remains held.
`key`: The presentation key to inspect. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsKeyPressed(Inno.Editor.ImGui.ImGuiKey key, bool repeat = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L100) | Checks a presentation-key press with explicit repeat policy.
`key`: The presentation key to inspect.
`repeat`: Whether a held key or button may produce repeats. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static void Inno.Editor.ImGui.ImGui.ResetMouseDragDelta(Inno.Editor.ImGui.ImGuiMouseButton button)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L114) | Resets the consumed pointer-drag distance.
`button`: The pointer button to inspect. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetMouseCursor(Inno.Editor.ImGui.ImGuiMouseCursor cursor)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L126) | Requests the semantic cursor for the hovered control.
`cursor`: The semantic pointer shape. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseHoveringRect(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, bool clip = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L147) | Checks pointer containment in a screen rectangle with optional window clipping.
`minimum`: The inclusive lower bound or upper-left corner.
`maximum`: The inclusive upper bound or lower-right corner.
`clip`: Whether the current clipping rectangle participates. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsWindowHovered(Inno.Editor.ImGui.ImGuiHoveredFlags flags = Inno.Editor.ImGui.ImGuiHoveredFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L165) | Checks window hover under the declared interaction policy.
`flags`: The explicit control and interaction options. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsWindowFocused(Inno.Editor.ImGui.ImGuiFocusedFlags flags = Inno.Editor.ImGui.ImGuiFocusedFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L180) | Checks keyboard focus within the declared window scope.
`flags`: The explicit control and interaction options. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsAnyItemHovered()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L192) | Checks whether a submitted control currently owns hover. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsAnyItemActive()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L204) | Checks whether a submitted control owns an active interaction. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsItemActivated()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L216) | Checks the first frame of the last control interaction. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsItemDeactivatedAfterEdit()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L228) | Checks the completion frame of an edited control. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetKeyboardFocusHere(int offset = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L240) | Requests focus for a relative control position.
`offset`: The relative focus offset from the next submitted control. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetItemDefaultFocus()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L249) | Chooses the last item as the default selection in an appearing popup. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetClipboardText(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L261) | Copies literal text through the host clipboard adapter.
`text`: Literal text; no formatting interpretation is applied. | — | — | +| [`public static int Inno.Editor.ImGui.ImGui.GetFrameCount()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs#L273) | Gets the current native frame identity for transient diagnostics. | A copied value from the active presentation context. | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetCursorScreenPosition(System.Numerics.Vector2 position)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L17) | Places the next control at a screen-space location.
`position`: The requested coordinate in UI units. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetCursorPosX(float position)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L29) | Changes only the local horizontal layout cursor.
`position`: The requested coordinate in UI units. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetCursorPosY(float position)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L41) | Changes only the local vertical layout cursor.
`position`: The requested coordinate in UI units. | — | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetCursorPosX()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L53) | Gets the local horizontal layout cursor. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetCursorPosY()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L65) | Gets the local vertical layout cursor. | A copied value from the active presentation context. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetWindowPos()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L77) | Gets the current window screen-space origin. | A copied value from the active presentation context. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetWindowSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L89) | Gets the current window size in UI units. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetFontSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L101) | Gets the active font size in UI units. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetFrameHeight()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L113) | Gets the standard control height including frame padding. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetTextLineHeight()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L125) | Gets the active font line height without spacing. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetFrameHeightWithSpacing()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L137) | Gets the standard control height and following vertical spacing. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetWindowDpiScale()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L149) | Gets the native viewport scale for the current window. | A copied value from the active presentation context. | — | +| [`public static void Inno.Editor.ImGui.ImGui.AlignTextToFramePadding()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L158) | Aligns the next text baseline with framed controls. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetNextItemAllowOverlap()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L167) | Allows explicitly layered controls to share the next item bounds. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetNextWindowSize(System.Numerics.Vector2 size, Inno.Editor.ImGui.ImGuiCond condition = Inno.Editor.ImGui.ImGuiCond.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L182) | Requests a size for the next window under an explicit layout condition.
`size`: The explicit two-dimensional extent.
`condition`: The condition controlling when the request is applied. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetNextWindowContentSize(System.Numerics.Vector2 size)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L196) | Declares virtual content size for the next scrollable window.
`size`: The explicit two-dimensional extent. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.Indent(float amount = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L208) | Indents subsequent controls by an explicit amount or the standard step.
`amount`: The requested layout distance. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.Unindent(float amount = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L220) | Restores a matching indentation step.
`amount`: The requested layout distance. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushTextWrapPos(float position = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L232) | Restricts subsequent text wrapping to a local horizontal boundary.
`position`: The requested coordinate in UI units. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PopTextWrapPos()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L241) | Restores the enclosing text-wrap boundary. | — | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetScrollY()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L253) | Gets the current vertical scroll position. | A copied value from the active presentation context. | — | +| [`public static float Inno.Editor.ImGui.ImGui.GetScrollMaxY()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L265) | Gets the current vertical scroll range. | A copied value from the active presentation context. | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetScrollHereY(float ratio = 0.5)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs#L277) | Scrolls the most recently submitted control into the requested relative position.
`ratio`: The relative alignment from zero through one. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.TableSetColumnIndex(int index)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Tables.cs#L20) | Selects a declared table column for subsequent controls.
`index`: The zero-based item index. | True when the documented control event or condition is present; otherwise false. | — | +| [`public static void Inno.Editor.ImGui.ImGui.TableSetBgColor(Inno.Editor.ImGui.ImGuiTableBgTarget target, uint color, int column = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Tables.cs#L38) | Sets a table row or cell background through the current table scope.
`target`: The table background role.
`color`: The packed RGBA color or semantic style slot.
`column`: The target column, or -1 for the current row. | — | — | +| [`public static Inno.Editor.ImGui.EditorUiLayout Inno.Editor.ImGui.ImGui.GetLayout()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L16) | Copies the current UI layout metrics. | Value metrics valid independently from the native style pointer. | — | +| [`public static Inno.Editor.ImGui.EditorUiInput Inno.Editor.ImGui.ImGui.GetInput()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L28) | Copies the input facts for this frame. | A neutral value snapshot; no pointer or callback is retained. | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.ImGui.GetStyleColorValue(Inno.Editor.ImGui.ImGuiCol color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L43) | Copies an unmodulated style color for compound presentation.
`color`: The semantic style color slot. | The copied linear vector of UI color components. | — | +| [`public static (uint id, System.Numerics.Vector2 position) Inno.Editor.ImGui.ImGui.GetViewport()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L55) | Gets the logical viewport identity and copied position for the current window. | The viewport ID and its screen-space origin. | — | +| [`public static (float minimumX, float maximumX, float rowMinimum, float rowMaximum) Inno.Editor.ImGui.ImGui.GetTableBounds()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L71) | Gets the table and current row bounds needed by row overlays. | The table's horizontal bounds and current row's vertical bounds. | `System.InvalidOperationException`: No valid drawing frame or active table exists. | +| [`public static (float minimumX, float maximumX, System.Numerics.Vector2 workMaximum, System.Numerics.Vector2 padding) Inno.Editor.ImGui.ImGui.GetWindowBounds()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L89) | Gets copied inner and work bounds for the current window. | Screen-space horizontal bounds and work maximum. | `System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public static (System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum) Inno.Editor.ImGui.ImGui.GetClipBounds()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L105) | Copies the current window's effective clip after parent and viewport clipping. | Screen-space minimum and maximum values for bounding frame-local submission work. | `System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public static Inno.Editor.ImGui.EditorDrawList Inno.Editor.ImGui.ImGui.GetDrawList()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L118) | Borrows drawing commands for the current window and frame without exposing native memory. | A value that rejects use after a window, frame, context or session change. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.HasDragPayload(string protocol)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L128) | Checks the active payload's logical protocol without exposing its native buffer.
`protocol`: The exact drag protocol name. | True when a drag of that protocol is currently active. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.MeasureText(string text, float scale)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs#L150) | Measures literal text at a relative font scale.
`text`: The literal text to measure.
`scale`: Scale relative to the current font. | The copied width and height in UI pixels. | — | +| [`public static void Inno.Editor.ImGui.ImGui.Text(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L20) | Draws unformatted text.
`text`: Literal text to draw; percent characters are displayed without format-string interpretation. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.Button(string label, System.Numerics.Vector2 size = default(System.Numerics.Vector2))`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L38) | Draws a standard button.
`label`: The visible label and optional ImGui identity suffix.
`size`: The requested size, or zero components for automatic sizing. | when the button is activated. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.SmallButton(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L55) | Draws a compact button.
`label`: The visible label and optional ImGui identity suffix. | when the button is activated. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.Checkbox(string label, ref bool value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L73) | Draws and edits a Boolean value.
`label`: The control label.
`value`: The checked state, updated when the user toggles the control. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.InputText(string label, ref string value, int capacity = 1024, Inno.Editor.ImGui.ImGuiInputTextFlags flags = Inno.Editor.ImGui.ImGuiInputTextFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L102) | Draws and edits a bounded UTF-8 text value.
`label`: The control label.
`value`: The managed string to display and update.
`capacity`: The positive maximum UTF-8 buffer capacity, including the terminator.
`flags`: Text editing behavior. | when the control reports an edit or submit event. | `System.ArgumentOutOfRangeException`: Thrown when is not positive. | +| [`public static bool Inno.Editor.ImGui.ImGui.InputTextWithHint(string label, string hint, ref string value, int capacity = 1024, Inno.Editor.ImGui.ImGuiInputTextFlags flags = Inno.Editor.ImGui.ImGuiInputTextFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L136) | Draws and edits a bounded UTF-8 text value with placeholder text.
`label`: The control label.
`hint`: The placeholder shown while the value is empty.
`value`: The managed string to display and update.
`capacity`: The positive maximum UTF-8 buffer capacity, including the terminator.
`flags`: Text editing behavior. | when the control reports an edit or submit event. | `System.ArgumentOutOfRangeException`: Thrown when is not positive. | +| [`public static bool Inno.Editor.ImGui.ImGui.InputInt(string label, ref int value, int step = 1, int fastStep = 100, Inno.Editor.ImGui.ImGuiInputTextFlags flags = Inno.Editor.ImGui.ImGuiInputTextFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L168) | Draws and edits an integer value.
`label`: The control label.
`value`: The integer displayed by the control and replaced after an accepted edit.
`step`: Increment used by the ordinary step button.
`fastStep`: Increment used by the accelerated step button.
`flags`: Text editing options for the numeric field. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.InputFloat(string label, ref float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L191) | Draws and edits a floating-point value.
`label`: The control label.
`value`: The number displayed with three fractional digits and replaced after an accepted edit. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.DragFloat(string label, ref float value, float speed = 1, float minimum = 0, float maximum = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L220) | Draws a floating-point drag control.
`label`: The control label.
`value`: The value to display and update.
`speed`: The drag speed.
`minimum`: The inclusive minimum, or zero with a zero maximum for no clamping.
`maximum`: The inclusive maximum, or zero with a zero minimum for no clamping. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.SliderFloat(string label, ref float value, float minimum, float maximum, Inno.Editor.ImGui.ImGuiSliderFlags flags = Inno.Editor.ImGui.ImGuiSliderFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L252) | Draws a bounded floating-point slider.
`label`: The control label.
`value`: The value to display and update.
`minimum`: The inclusive minimum.
`maximum`: The inclusive maximum.
`flags`: Slider behavior. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.ColorEdit4(string label, ref System.Numerics.Vector4 value, Inno.Editor.ImGui.ImGuiColorEditFlags flags = Inno.Editor.ImGui.ImGuiColorEditFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L278) | Draws and edits a four-component color.
`label`: The control label.
`value`: The RGBA value to display and update.
`flags`: Color editing behavior. | when the value changes. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.ColorEditLinear4(string label, ref System.Numerics.Vector4 value, Inno.Editor.ImGui.ImGuiColorEditFlags flags = Inno.Editor.ImGui.ImGuiColorEditFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L308) | Displays a linear RGBA color through a display-sRGB picker and returns edits in linear space.
`label`: The stable identity and visible label of the color control.
`value`: Linear RGB and alpha; only changed RGB channels are decoded, while alpha is never transfer-encoded.
`flags`: Dear ImGui color-edit flags; display modes interpret sRGB values and input storage must be RGB. | True if the display color changed and contains decoded linear RGB. | `System.ArgumentException`: HSV input storage conflicts with the linear-RGB value contract. | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginChild(string id, System.Numerics.Vector2 size = default(System.Numerics.Vector2), Inno.Editor.ImGui.ImGuiChildFlags flags = Inno.Editor.ImGui.ImGuiChildFlags.None, Inno.Editor.ImGui.ImGuiWindowFlags windowFlags = Inno.Editor.ImGui.ImGuiWindowFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L346) | Begins a child region.
`id`: The child identity.
`size`: The requested child size.
`flags`: Child-region behavior.
`windowFlags`: Scroll and window policies for the child. | when child contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndChild()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L359) | Ends the current child region. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginTable(string id, int columns, Inno.Editor.ImGui.ImGuiTableFlags flags = Inno.Editor.ImGui.ImGuiTableFlags.None, System.Numerics.Vector2 size = default(System.Numerics.Vector2), float innerWidth = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L386) | Begins a table.
`id`: The table identity.
`columns`: The positive column count.
`flags`: Table behavior.
`size`: The requested outer size.
`innerWidth`: Optional inner scrolling width; zero uses the outer size. | when table contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndTable()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L400) | Ends the current table. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.TableSetupColumn(string label, Inno.Editor.ImGui.ImGuiTableColumnFlags flags = Inno.Editor.ImGui.ImGuiTableColumnFlags.None, float widthOrWeight = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L418) | Declares one table column.
`label`: The column label.
`flags`: Column behavior.
`widthOrWeight`: The initial fixed width or stretch weight. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.TableHeadersRow()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L430) | Draws the table header row from configured column labels. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.TableNextRow(Inno.Editor.ImGui.ImGuiTableRowFlags flags = Inno.Editor.ImGui.ImGuiTableRowFlags.None, float minimumHeight = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L445) | Advances to the next table row.
`flags`: Row presentation flags.
`minimumHeight`: Minimum row height in UI pixels. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.TableNextColumn()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L459) | Advances to the next table column. | when the column is visible. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginCombo(string label, string preview, Inno.Editor.ImGui.ImGuiComboFlags flags = Inno.Editor.ImGui.ImGuiComboFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L480) | Begins a combo popup.
`label`: The control label.
`preview`: The preview text.
`flags`: Combo behavior. | when combo contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndCombo()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L492) | Ends the current combo popup. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.Selectable(string label, bool selected = false, Inno.Editor.ImGui.ImGuiSelectableFlags flags = Inno.Editor.ImGui.ImGuiSelectableFlags.None, System.Numerics.Vector2 size = default(System.Numerics.Vector2))`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L516) | Draws one selectable item.
`label`: The visible label and identity.
`selected`: Whether the item is currently selected.
`flags`: Selectable behavior.
`size`: The interaction bounds, or zero for the ordinary content size. | when the item is activated. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.CollapsingHeader(string label, Inno.Editor.ImGui.ImGuiTreeNodeFlags flags = Inno.Editor.ImGui.ImGuiTreeNodeFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L538) | Draws a collapsible section header.
`label`: The visible label and identity.
`flags`: Tree-node behavior. | when the section is open. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.TreeNode(string label, Inno.Editor.ImGui.ImGuiTreeNodeFlags flags = Inno.Editor.ImGui.ImGuiTreeNodeFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L558) | Draws a tree node and pushes its tree scope when it is open.
`label`: The visible label and identity.
`flags`: Tree-node behavior. | when child contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.TreePop()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L569) | Ends the current open tree-node scope. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabBar(string id, Inno.Editor.ImGui.ImGuiTabBarFlags flags = Inno.Editor.ImGui.ImGuiTabBarFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L587) | Begins a tab bar.
`id`: The tab bar identity.
`flags`: Tab-bar behavior. | when tab contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndTabBar()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L598) | Ends the current tab bar. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabItem(string label, Inno.Editor.ImGui.ImGuiTabItemFlags flags = Inno.Editor.ImGui.ImGuiTabItemFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L616) | Begins one tab item.
`label`: The tab label and identity.
`flags`: Tab-item behavior. | when tab contents should be submitted. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginTabItem(string label, ref bool isOpen, Inno.Editor.ImGui.ImGuiTabItemFlags flags = Inno.Editor.ImGui.ImGuiTabItemFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L639) | Begins one closeable tab item.
`label`: The tab label and identity.
`isOpen`: Receives whether the tab remains open.
`flags`: Tab-item behavior. | when tab contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndTabItem()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L650) | Ends the current tab item. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.OpenPopup(string id, Inno.Editor.ImGui.ImGuiPopupFlags flags = Inno.Editor.ImGui.ImGuiPopupFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L665) | Opens a named popup.
`id`: The popup identity.
`flags`: Popup opening behavior. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.BeginPopup(string id, Inno.Editor.ImGui.ImGuiWindowFlags flags = Inno.Editor.ImGui.ImGuiWindowFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L685) | Begins a named popup.
`id`: The popup identity.
`flags`: Popup window behavior. | when popup contents should be submitted. | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L696) | Ends the current popup. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.CloseCurrentPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L705) | Closes the current popup. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsAnyPopupOpen()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L720) | Reads whether an open popup or modal currently prevents underlying canvas and list input. | True when any popup is open in this native context, independently of its identifier or nesting. | `System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public static void Inno.Editor.ImGui.ImGui.BeginDisabled(bool disabled = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L732) | Begins a disabled block.
`disabled`: Whether controls in the block are disabled. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndDisabled()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L741) | Ends the current disabled block. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.BeginGroup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L750) | Begins an item group. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.EndGroup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L759) | Ends the current item group. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushId(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L771) | Pushes a string identity onto the ImGui ID stack.
`id`: The stable identity used to locate the requested value. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushId(int id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L783) | Pushes an integer identity onto the ImGui ID stack.
`id`: The stable identity used to locate the requested value. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PopId()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L792) | Pops one identity from the ImGui ID stack. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushStyleColor(Inno.Editor.ImGui.ImGuiCol color, System.Numerics.Vector4 value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L807) | Pushes one color override.
`color`: The style color slot.
`value`: The linear RGBA value. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PopStyleColor(int count = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L821) | Pops color overrides.
`count`: The positive number of overrides to pop. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushStyleVar(Inno.Editor.ImGui.ImGuiStyleVar style, float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L836) | Pushes one scalar style override.
`style`: The style variable.
`value`: The scalar value. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PushStyleVar(Inno.Editor.ImGui.ImGuiStyleVar style, System.Numerics.Vector2 value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L853) | Pushes one two-component style override.
`style`: The style variable.
`value`: The vector value. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.PopStyleVar(int count = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L867) | Pops style-variable overrides.
`count`: The positive number of overrides to pop. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SameLine(float offset = 0, float spacing = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L882) | Places the next item on the same line.
`offset`: The horizontal offset from the line start, or zero for automatic placement.
`spacing`: The explicit spacing, or a negative value for the current style spacing. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.Separator()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L893) | Draws a horizontal separator. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SeparatorText(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L905) | Draws a labeled horizontal separator.
`label`: The label text validated by the separator text operation. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.Spacing()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L914) | Adds one standard vertical spacing unit. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.Dummy(System.Numerics.Vector2 size)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L926) | Advances layout by an invisible size.
`size`: The size consumed by dummy; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetNextItemWidth(float width)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L938) | Sets the width of the next item.
`width`: The width in logical units or pixels required by this operation. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.FocusNextItem()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L950) | Requests keyboard focus for the next submitted item in the current frame and window. | — | `System.InvalidOperationException`: No matching owner-thread drawing session is active. | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetContentRegionAvailable()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L962) | Gets the remaining content size in the current region. | The remaining local width and height. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetCursorPosition()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L974) | Gets the current local cursor position. | The local cursor position. | — | +| [`public static void Inno.Editor.ImGui.ImGui.SetCursorPosition(System.Numerics.Vector2 position)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L986) | Sets the current local cursor position.
`position`: The position consumed by set cursor position; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetCursorScreenPosition()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L998) | Gets the current screen-space cursor position. | The screen-space cursor position. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetItemMinimum()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1010) | Gets the minimum screen-space corner of the previous item. | The item minimum corner. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetItemMaximum()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1022) | Gets the maximum screen-space corner of the previous item. | The item maximum corner. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.MeasureText(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1037) | Measures text using the current font.
`text`: The text text validated by the measure text operation. | The measured width and height. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsItemHovered(Inno.Editor.ImGui.ImGuiHoveredFlags flags = Inno.Editor.ImGui.ImGuiHoveredFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1052) | Gets whether the previous item is hovered.
`flags`: Hovered query behavior. | when the query succeeds. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsItemActive()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1064) | Gets whether the previous item is active. | while the item owns an active interaction. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsItemClicked(Inno.Editor.ImGui.ImGuiMouseButton button = Inno.Editor.ImGui.ImGuiMouseButton.Left)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1079) | Gets whether the previous item was clicked with a mouse button.
`button`: The mouse button to query. | when the item was clicked. | — | +| [`public static bool Inno.Editor.ImGui.ImGui.IsMouseButtonDown(Inno.Editor.ImGui.ImGuiMouseButton button = Inno.Editor.ImGui.ImGuiMouseButton.Left)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1094) | Gets whether a mouse button is held in the current Editor UI frame, including input consumed by UI controls.
`button`: The UI mouse button to query. | True while the button is held; callers must also check their item's active or hovered state before editing. | — | +| [`public static System.Numerics.Vector2 Inno.Editor.ImGui.ImGui.GetMouseDragDelta(Inno.Editor.ImGui.ImGuiMouseButton button = Inno.Editor.ImGui.ImGuiMouseButton.Left, float threshold = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1113) | Gets accumulated pointer displacement from the current UI drag's starting position for timeline and canvas controls.
`button`: The button that owns the drag.
`threshold`: Movement required to start dragging, or a negative value to use the Editor's configured threshold. | Displacement in UI coordinates, or zero before the drag threshold is crossed or when no drag is available. The value is cumulative; it must be applied to a captured starting value, not added again each frame. | — | +| [`public static uint Inno.Editor.ImGui.ImGui.ToPackedColor(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1130) | Converts a floating-point RGBA color to Dear ImGui packed color order.
`color`: The color consumed by to packed color; ownership remains with the caller unless explicitly stated otherwise. | The packed color value. | — | +| [`public static void Inno.Editor.ImGui.ImGui.DrawLine(System.Numerics.Vector2 start, System.Numerics.Vector2 end, uint color, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1153) | Adds a line to the current window draw list.
`start`: The screen-space start point.
`end`: The screen-space end point.
`color`: The packed ImGui color.
`thickness`: The line thickness. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.DrawRectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1181) | Adds a rectangle outline to the current window draw list.
`minimum`: The screen-space minimum corner.
`maximum`: The screen-space maximum corner.
`color`: The packed ImGui color.
`rounding`: The corner rounding radius.
`thickness`: The outline thickness. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.DrawFilledRectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1207) | Adds a filled rectangle to the current window draw list.
`minimum`: The screen-space minimum corner.
`maximum`: The screen-space maximum corner.
`color`: The packed ImGui color.
`rounding`: The corner rounding radius. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.DrawCubicCurve(System.Numerics.Vector2 start, System.Numerics.Vector2 firstControl, System.Numerics.Vector2 secondControl, System.Numerics.Vector2 end, uint color, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1238) | Draws a cubic curve without exposing a native draw list or borrowed pointer.
`start`: The first screen-space endpoint.
`firstControl`: The control point beside the first endpoint.
`secondControl`: The control point beside the final endpoint.
`end`: The final screen-space endpoint.
`color`: The packed presentation color.
`thickness`: The positive stroke width. | — | — | +| [`public static void Inno.Editor.ImGui.ImGui.DrawText(System.Numerics.Vector2 position, uint color, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs#L1263) | Adds text to the current window draw list.
`position`: The screen-space text origin.
`color`: The packed ImGui color.
`text`: The text to draw. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiButtonFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L8) | Defines the supported button flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonLeft = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L17) | React on left mouse button (default) | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonRight = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L21) | React on right mouse button | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonMiddle = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L25) | React on center mouse button | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonMask = Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonLeft \| Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonRight \| Inno.Editor.ImGui.ImGuiButtonFlags.MouseButtonMiddle`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L29) | Selects the mouse button mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiButtonFlags.EnableNav = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs#L33) | InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiChildFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L8) | Defines the supported child flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.Borders = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L17) | Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason) | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.AlwaysUseWindowPadding = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L21) | Pad with style.WindowPadding even if no border are drawn (no padding by default for non-bordered child windows because it makes more sense) | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.ResizeX = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L25) | Allow resize from right border (layout direction). Enable .ini saving (unless ImGuiWindowFlags_NoSavedSettings passed to window flags) | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.ResizeY = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L29) | Allow resize from bottom border (layout direction). " | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.AutoResizeX = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L33) | Enable auto-resizing width. Read "IMPORTANT: Size measurement" details above. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.AutoResizeY = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L37) | Enable auto-resizing height. Read "IMPORTANT: Size measurement" details above. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.AlwaysAutoResize = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L41) | Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.FrameStyle = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L45) | Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. | — | — | +| [`Inno.Editor.ImGui.ImGuiChildFlags.NavFlattened = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs#L49) | Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiCol : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L7) | Defines the supported col choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Text = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L12) | Selects the text presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TextDisabled = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L16) | Selects the text disabled presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.WindowBg = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L20) | Selects the window bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ChildBg = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L24) | Selects the child bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.PopupBg = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L28) | Selects the popup bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Border = 5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L32) | Selects the border presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.BorderShadow = 6`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L36) | Selects the border shadow presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.FrameBg = 7`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L40) | Selects the frame bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.FrameBgHovered = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L44) | Selects the frame bg hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.FrameBgActive = 9`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L48) | Selects the frame bg active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TitleBg = 10`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L52) | Selects the title bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TitleBgActive = 11`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L56) | Selects the title bg active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TitleBgCollapsed = 12`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L60) | Selects the title bg collapsed presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.MenuBarBg = 13`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L64) | Selects the menu bar bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ScrollbarBg = 14`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L68) | Selects the scrollbar bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ScrollbarGrab = 15`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L72) | Selects the scrollbar grab presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ScrollbarGrabHovered = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L76) | Selects the scrollbar grab hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ScrollbarGrabActive = 17`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L80) | Selects the scrollbar grab active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.CheckMark = 18`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L84) | Selects the check mark presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.SliderGrab = 19`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L88) | Selects the slider grab presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.SliderGrabActive = 20`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L92) | Selects the slider grab active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Button = 21`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L96) | Selects the button presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ButtonHovered = 22`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L100) | Selects the button hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ButtonActive = 23`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L104) | Selects the button active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Header = 24`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L108) | Selects the header presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.HeaderHovered = 25`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L112) | Selects the header hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.HeaderActive = 26`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L116) | Selects the header active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Separator = 27`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L120) | Selects the separator presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.SeparatorHovered = 28`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L124) | Selects the separator hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.SeparatorActive = 29`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L128) | Selects the separator active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ResizeGrip = 30`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L132) | Selects the resize grip presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ResizeGripHovered = 31`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L136) | Selects the resize grip hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ResizeGripActive = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L140) | Selects the resize grip active presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.InputTextCursor = 33`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L144) | Selects the input text cursor presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabHovered = 34`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L148) | Selects the tab hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Tab = 35`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L152) | Selects the tab presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabSelected = 36`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L156) | Selects the tab selected presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabSelectedOverline = 37`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L160) | Selects the tab selected overline presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabDimmed = 38`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L164) | Selects the tab dimmed presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabDimmedSelected = 39`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L168) | Selects the tab dimmed selected presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TabDimmedSelectedOverline = 40`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L172) | Selects the tab dimmed selected overline presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.DockingPreview = 41`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L176) | Selects the docking preview presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.DockingEmptyBg = 42`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L180) | Selects the docking empty bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.PlotLines = 43`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L184) | Selects the plot lines presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.PlotLinesHovered = 44`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L188) | Selects the plot lines hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.PlotHistogram = 45`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L192) | Selects the plot histogram presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.PlotHistogramHovered = 46`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L196) | Selects the plot histogram hovered presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TableHeaderBg = 47`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L200) | Selects the table header bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TableBorderStrong = 48`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L204) | Selects the table border strong presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TableBorderLight = 49`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L208) | Selects the table border light presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TableRowBg = 50`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L212) | Selects the table row bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TableRowBgAlt = 51`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L216) | Selects the table row bg alt presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TextLink = 52`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L220) | Selects the text link presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TextSelectedBg = 53`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L224) | Selects the text selected bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.TreeLines = 54`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L228) | Selects the tree lines presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.DragDropTarget = 55`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L232) | Selects the drag drop target presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.DragDropTargetBg = 56`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L236) | Selects the drag drop target bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.UnsavedMarker = 57`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L240) | Selects the unsaved marker presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.NavCursor = 58`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L244) | Selects the nav cursor presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.NavWindowingHighlight = 59`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L248) | Selects the nav windowing highlight presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.NavWindowingDimBg = 60`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L252) | Selects the nav windowing dim bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.ModalWindowDimBg = 61`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L256) | Selects the modal window dim bg presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiCol.Count = 62`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs#L260) | Marks the exclusive end of the valid option range. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiColorEditFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L8) | Defines the supported color edit flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoAlpha = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L17) | // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (will only read 3 components from the input pointer). | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoPicker = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L21) | // ColorEdit: disable picker when clicking on color square. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoOptions = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L25) | // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoSmallPreview = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L29) | // ColorEdit, ColorPicker: disable color square preview next to the inputs. (e.g. to show only the inputs) | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoInputs = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L33) | // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview color square). | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoTooltip = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L37) | // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoLabel = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L41) | // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoSidePreview = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L45) | // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoDragDrop = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L49) | // ColorEdit: disable drag and drop target/source. ColorButton: disable drag and drop source. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoBorder = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L53) | // ColorButton: disable border (which is enforced by default) | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.NoColorMarkers = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L57) | // ColorEdit: disable rendering R/G/B/A color marker. May also be disabled globally by setting style.ColorMarkerSize = 0. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaOpaque = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L61) | // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaNoBg = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L65) | // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaPreviewHalf = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L69) | // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaBar = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L73) | // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.Hdr = 524288`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L77) | Selects the hdr presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayRgb = 1048576`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L81) | Selects the display rgb presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayHsv = 2097152`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L85) | Selects the display hsv presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayHex = 4194304`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L89) | // " | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.Uint8 = 8388608`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L93) | // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.Float = 16777216`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L97) | // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.PickerHueBar = 33554432`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L101) | // ColorPicker: bar for Hue, rectangle for Sat/Value. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.PickerHueWheel = 67108864`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L105) | // ColorPicker: wheel for Hue, triangle for Sat/Value. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.InputRgb = 134217728`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L109) | Selects the input rgb presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.InputHsv = 268435456`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L113) | Selects the input hsv presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DefaultOptions = Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayRgb \| Inno.Editor.ImGui.ImGuiColorEditFlags.Uint8 \| Inno.Editor.ImGui.ImGuiColorEditFlags.PickerHueBar \| Inno.Editor.ImGui.ImGuiColorEditFlags.InputRgb`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L117) | Selects the default options presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaMask = Inno.Editor.ImGui.ImGuiColorEditFlags.NoAlpha \| Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaOpaque \| Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaNoBg \| Inno.Editor.ImGui.ImGuiColorEditFlags.AlphaPreviewHalf`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L121) | Selects the alpha mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayMask = Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayRgb \| Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayHsv \| Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayHex`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L125) | Selects the display mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.DataTypeMask = Inno.Editor.ImGui.ImGuiColorEditFlags.Uint8 \| Inno.Editor.ImGui.ImGuiColorEditFlags.Float`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L129) | Selects the data type mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.PickerMask = Inno.Editor.ImGui.ImGuiColorEditFlags.PickerHueBar \| Inno.Editor.ImGui.ImGuiColorEditFlags.PickerHueWheel`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L133) | Selects the picker mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiColorEditFlags.InputMask = Inno.Editor.ImGui.ImGuiColorEditFlags.InputRgb \| Inno.Editor.ImGui.ImGuiColorEditFlags.InputHsv`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs#L137) | Selects the input mask presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiComboFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L8) | Defines the supported combo flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.PopupAlignLeft = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L17) | Align the popup toward the left by default | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.HeightSmall = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L21) | Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.HeightRegular = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L25) | Max ~8 items visible (default) | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.HeightLarge = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L29) | Max ~20 items visible | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.HeightLargest = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L33) | As many fitting items as possible | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.NoArrowButton = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L37) | Display on the preview box without the square arrow button | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.NoPreview = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L41) | Display only a square arrow button | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.WidthFitPreview = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L45) | Width dynamically calculated from preview contents | — | — | +| [`Inno.Editor.ImGui.ImGuiComboFlags.HeightMask = Inno.Editor.ImGui.ImGuiComboFlags.HeightSmall \| Inno.Editor.ImGui.ImGuiComboFlags.HeightRegular \| Inno.Editor.ImGui.ImGuiComboFlags.HeightLarge \| Inno.Editor.ImGui.ImGuiComboFlags.HeightLargest`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs#L49) | Selects the height mask presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiCond : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L8) | Defines the supported cond choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiCond.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L13) | No condition (always set the variable), same as _Always | — | — | +| [`Inno.Editor.ImGui.ImGuiCond.Always = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L17) | No condition (always set the variable), same as _None | — | — | +| [`Inno.Editor.ImGui.ImGuiCond.Once = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L21) | Set the variable once per runtime session (only the first call will succeed) | — | — | +| [`Inno.Editor.ImGui.ImGuiCond.FirstUseEver = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L25) | Set the variable if the object/window has no persistently saved data (no entry in .ini file) | — | — | +| [`Inno.Editor.ImGui.ImGuiCond.Appearing = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs#L29) | Set the variable if the object/window is appearing after being hidden/inactive (or the first time) | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiFocusedFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L8) | Defines the supported focused flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.ChildWindows = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L17) | Return true if any children of the window is focused | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.RootWindow = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L21) | Test from root window (top most parent of the current hierarchy) | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.AnyWindow = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L25) | Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ! | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.NoPopupHierarchy = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L29) | Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.DockHierarchy = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L33) | Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) | — | — | +| [`Inno.Editor.ImGui.ImGuiFocusedFlags.RootAndChildWindows = Inno.Editor.ImGui.ImGuiFocusedFlags.ChildWindows \| Inno.Editor.ImGui.ImGuiFocusedFlags.RootWindow`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs#L37) | Selects the root and child windows presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiHoveredFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L8) | Defines the supported hovered flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L13) | Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.ChildWindows = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L17) | IsWindowHovered() only: Return true if any children of the window is hovered | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.RootWindow = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L21) | IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AnyWindow = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L25) | IsWindowHovered() only: Return true if any window is hovered | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.NoPopupHierarchy = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L29) | IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.DockHierarchy = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L33) | IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByPopup = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L37) | Return true even if a popup window is normally blocking access to this item/window | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByActiveItem = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L41) | Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByItem = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L45) | IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByWindow = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L49) | IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenDisabled = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L53) | IsItemHovered() only: Return true even if the item is disabled | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.NoNavOverride = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L57) | IsItemHovered() only: Disable using keyboard/gamepad navigation state when active, always query mouse | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlapped = Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByItem \| Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByWindow`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L61) | Selects the allow when overlapped presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.RectOnly = Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByPopup \| Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByActiveItem \| Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenOverlapped`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L65) | Selects the rect only presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.RootAndChildWindows = Inno.Editor.ImGui.ImGuiHoveredFlags.ChildWindows \| Inno.Editor.ImGui.ImGuiHoveredFlags.RootWindow`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L69) | Selects the root and child windows presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.ForTooltip = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L73) | Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.Stationary = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L77) | Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can move on same item/window. Using the stationary test tends to reduces the need for a long delay. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.DelayNone = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L81) | IsItemHovered() only: Return true immediately (default). As this is the default you generally ignore this. | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.DelayShort = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L85) | IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.DelayNormal = 65536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L89) | IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). | — | — | +| [`Inno.Editor.ImGui.ImGuiHoveredFlags.NoSharedDelay = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs#L93) | IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiInputTextFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L8) | Defines the supported input text flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CharsDecimal = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L17) | Allow 0123456789.+-*/ | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CharsHexadecimal = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L21) | Allow 0123456789ABCDEFabcdef | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CharsScientific = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L25) | Allow 0123456789.+-*/eE (Scientific notation input) | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CharsUppercase = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L29) | Turn a..z into A..Z | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CharsNoBlank = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L33) | Filter out spaces, tabs | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.AllowTabInput = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L37) | Pressing TAB input a '\t' character into the text field | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.EnterReturnsTrue = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L41) | Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.EscapeClearsAll = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L45) | Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CtrlEnterForNewLine = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L49) | In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.ReadOnly = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L53) | Read-only mode | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.Password = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L57) | Password mode, display all characters as '*', disable copy | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.AlwaysOverwrite = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L61) | Overwrite mode | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.AutoSelectAll = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L65) | Select entire text when first taking mouse focus | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.ParseEmptyRefVal = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L69) | InputFloat(), InputInt(), InputScalar() etc. only: parse empty string as zero value. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.DisplayEmptyRefVal = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L73) | InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.NoHorizontalScroll = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L77) | Disable following the cursor horizontally | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.NoUndoRedo = 65536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L81) | Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.ElideLeft = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L85) | When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackCompletion = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L89) | Callback on pressing TAB (for completion handling) | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackHistory = 524288`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L93) | Callback on pressing Up/Down arrows (for history handling) | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackAlways = 1048576`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L97) | Callback on each iteration. User code may query cursor position, modify text buffer. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackCharFilter = 2097152`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L101) | Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackResize = 4194304`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L105) | Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.CallbackEdit = 8388608`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L109) | Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. | — | — | +| [`Inno.Editor.ImGui.ImGuiInputTextFlags.WordWrap = 16777216`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs#L113) | InputTextMultiline(): word-wrap lines that are too long. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiKey : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L7) | Defines the supported key choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L12) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.NamedKeyBegin = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L16) | Identifies the NamedKeyBegin presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Tab = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L20) | == ImGuiKey_NamedKey_BEGIN | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftArrow = 513`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L24) | Identifies the LeftArrow presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightArrow = 514`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L28) | Identifies the RightArrow presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.UpArrow = 515`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L32) | Identifies the UpArrow presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.DownArrow = 516`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L36) | Identifies the DownArrow presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.PageUp = 517`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L40) | Identifies the PageUp presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.PageDown = 518`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L44) | Identifies the PageDown presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Home = 519`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L48) | Identifies the Home presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.End = 520`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L52) | Identifies the End presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Insert = 521`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L56) | Identifies the Insert presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Delete = 522`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L60) | Identifies the Delete presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Backspace = 523`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L64) | Identifies the Backspace presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Space = 524`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L68) | Identifies the Space presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Enter = 525`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L72) | Identifies the Enter presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Escape = 526`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L76) | Identifies the Escape presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftCtrl = 527`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L80) | Identifies the LeftCtrl presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftShift = 528`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L84) | Identifies the LeftShift presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftAlt = 529`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L88) | Identifies the LeftAlt presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftSuper = 530`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L92) | Identifies the LeftSuper presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightCtrl = 531`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L96) | Identifies the RightCtrl presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightShift = 532`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L100) | Identifies the RightShift presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightAlt = 533`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L104) | Identifies the RightAlt presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightSuper = 534`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L108) | Identifies the RightSuper presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Menu = 535`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L112) | Identifies the Menu presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._0 = 536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L116) | Identifies the _0 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._1 = 537`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L120) | Identifies the _1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._2 = 538`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L124) | Identifies the _2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._3 = 539`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L128) | Identifies the _3 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._4 = 540`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L132) | Identifies the _4 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._5 = 541`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L136) | Identifies the _5 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._6 = 542`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L140) | Identifies the _6 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._7 = 543`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L144) | Identifies the _7 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._8 = 544`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L148) | Identifies the _8 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey._9 = 545`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L152) | Identifies the _9 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.A = 546`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L156) | Identifies the A presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.B = 547`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L160) | Identifies the B presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.C = 548`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L164) | Identifies the C presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.D = 549`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L168) | Identifies the D presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.E = 550`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L172) | Identifies the E presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F = 551`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L176) | Identifies the F presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.G = 552`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L180) | Identifies the G presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.H = 553`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L184) | Identifies the H presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.I = 554`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L188) | Identifies the I presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.J = 555`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L192) | Identifies the J presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.K = 556`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L196) | Identifies the K presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.L = 557`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L200) | Identifies the L presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.M = 558`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L204) | Identifies the M presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.N = 559`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L208) | Identifies the N presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.O = 560`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L212) | Identifies the O presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.P = 561`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L216) | Identifies the P presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Q = 562`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L220) | Identifies the Q presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.R = 563`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L224) | Identifies the R presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.S = 564`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L228) | Identifies the S presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.T = 565`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L232) | Identifies the T presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.U = 566`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L236) | Identifies the U presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.V = 567`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L240) | Identifies the V presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.W = 568`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L244) | Identifies the W presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.X = 569`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L248) | Identifies the X presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Y = 570`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L252) | Identifies the Y presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Z = 571`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L256) | Identifies the Z presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F1 = 572`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L260) | Identifies the F1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F2 = 573`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L264) | Identifies the F2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F3 = 574`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L268) | Identifies the F3 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F4 = 575`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L272) | Identifies the F4 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F5 = 576`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L276) | Identifies the F5 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F6 = 577`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L280) | Identifies the F6 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F7 = 578`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L284) | Identifies the F7 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F8 = 579`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L288) | Identifies the F8 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F9 = 580`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L292) | Identifies the F9 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F10 = 581`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L296) | Identifies the F10 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F11 = 582`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L300) | Identifies the F11 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F12 = 583`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L304) | Identifies the F12 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F13 = 584`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L308) | Identifies the F13 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F14 = 585`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L312) | Identifies the F14 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F15 = 586`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L316) | Identifies the F15 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F16 = 587`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L320) | Identifies the F16 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F17 = 588`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L324) | Identifies the F17 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F18 = 589`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L328) | Identifies the F18 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F19 = 590`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L332) | Identifies the F19 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F20 = 591`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L336) | Identifies the F20 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F21 = 592`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L340) | Identifies the F21 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F22 = 593`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L344) | Identifies the F22 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F23 = 594`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L348) | Identifies the F23 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.F24 = 595`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L352) | Identifies the F24 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Apostrophe = 596`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L356) | Identifies the Apostrophe presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Comma = 597`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L360) | Identifies the Comma presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Minus = 598`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L364) | Identifies the Minus presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Period = 599`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L368) | Identifies the Period presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Slash = 600`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L372) | Identifies the Slash presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Semicolon = 601`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L376) | Identifies the Semicolon presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Equal = 602`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L380) | Identifies the Equal presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.LeftBracket = 603`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L384) | Identifies the LeftBracket presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Backslash = 604`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L388) | Identifies the Backslash presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.RightBracket = 605`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L392) | Identifies the RightBracket presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GraveAccent = 606`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L396) | Identifies the GraveAccent presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.CapsLock = 607`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L400) | Identifies the CapsLock presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ScrollLock = 608`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L404) | Identifies the ScrollLock presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.NumLock = 609`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L408) | Identifies the NumLock presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.PrintScreen = 610`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L412) | Identifies the PrintScreen presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Pause = 611`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L416) | Identifies the Pause presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad0 = 612`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L420) | Identifies the Keypad0 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad1 = 613`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L424) | Identifies the Keypad1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad2 = 614`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L428) | Identifies the Keypad2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad3 = 615`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L432) | Identifies the Keypad3 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad4 = 616`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L436) | Identifies the Keypad4 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad5 = 617`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L440) | Identifies the Keypad5 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad6 = 618`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L444) | Identifies the Keypad6 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad7 = 619`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L448) | Identifies the Keypad7 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad8 = 620`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L452) | Identifies the Keypad8 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Keypad9 = 621`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L456) | Identifies the Keypad9 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadDecimal = 622`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L460) | Identifies the KeypadDecimal presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadDivide = 623`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L464) | Identifies the KeypadDivide presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadMultiply = 624`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L468) | Identifies the KeypadMultiply presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadSubtract = 625`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L472) | Identifies the KeypadSubtract presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadAdd = 626`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L476) | Identifies the KeypadAdd presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadEnter = 627`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L480) | Identifies the KeypadEnter presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.KeypadEqual = 628`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L484) | Identifies the KeypadEqual presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.AppBack = 629`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L488) | Identifies the AppBack presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.AppForward = 630`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L492) | Identifies the AppForward presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.Oem102 = 631`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L496) | Identifies the Oem102 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadStart = 632`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L500) | Identifies the GamepadStart presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadBack = 633`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L504) | Identifies the GamepadBack presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadFaceLeft = 634`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L508) | Identifies the GamepadFaceLeft presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadFaceRight = 635`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L512) | Identifies the GamepadFaceRight presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadFaceUp = 636`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L516) | Identifies the GamepadFaceUp presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadFaceDown = 637`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L520) | Identifies the GamepadFaceDown presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadDpadLeft = 638`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L524) | Identifies the GamepadDpadLeft presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadDpadRight = 639`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L528) | Identifies the GamepadDpadRight presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadDpadUp = 640`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L532) | Identifies the GamepadDpadUp presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadDpadDown = 641`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L536) | Identifies the GamepadDpadDown presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadL1 = 642`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L540) | Identifies the GamepadL1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadR1 = 643`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L544) | Identifies the GamepadR1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadL2 = 644`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L548) | Identifies the GamepadL2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadR2 = 645`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L552) | Identifies the GamepadR2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadL3 = 646`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L556) | Identifies the GamepadL3 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadR3 = 647`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L560) | Identifies the GamepadR3 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadLStickLeft = 648`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L564) | Identifies the GamepadLStickLeft presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadLStickRight = 649`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L568) | Identifies the GamepadLStickRight presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadLStickUp = 650`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L572) | Identifies the GamepadLStickUp presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadLStickDown = 651`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L576) | Identifies the GamepadLStickDown presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadRStickLeft = 652`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L580) | Identifies the GamepadRStickLeft presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadRStickRight = 653`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L584) | Identifies the GamepadRStickRight presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadRStickUp = 654`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L588) | Identifies the GamepadRStickUp presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.GamepadRStickDown = 655`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L592) | Identifies the GamepadRStickDown presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseLeft = 656`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L596) | Identifies the MouseLeft presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseRight = 657`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L600) | Identifies the MouseRight presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseMiddle = 658`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L604) | Identifies the MouseMiddle presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseX1 = 659`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L608) | Identifies the MouseX1 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseX2 = 660`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L612) | Identifies the MouseX2 presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseWheelX = 661`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L616) | Identifies the MouseWheelX presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.MouseWheelY = 662`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L620) | Identifies the MouseWheelY presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ReservedForModCtrl = 663`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L624) | Identifies the ReservedForModCtrl presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ReservedForModShift = 664`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L628) | Identifies the ReservedForModShift presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ReservedForModAlt = 665`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L632) | Identifies the ReservedForModAlt presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ReservedForModSuper = 666`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L636) | Identifies the ReservedForModSuper presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.NamedKeyEnd = 667`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L640) | Identifies the NamedKeyEnd presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.NamedKeyCount = 155`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L644) | Identifies the NamedKeyCount presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModNone = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L648) | Identifies the ImGuiModNone presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModCtrl = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L652) | Identifies the ImGuiModCtrl presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModShift = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L656) | Identifies the ImGuiModShift presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModAlt = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L660) | Identifies the ImGuiModAlt presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModSuper = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L664) | Identifies the ImGuiModSuper presentation key or modifier. | — | — | +| [`Inno.Editor.ImGui.ImGuiKey.ImGuiModMask = 61440`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs#L668) | Identifies the ImGuiModMask presentation key or modifier. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiMouseButton : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs#L7) | Defines the supported mouse button choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseButton.Left = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs#L12) | Selects the left presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseButton.Right = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs#L16) | Selects the right presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseButton.Middle = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs#L20) | Selects the middle presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseButton.Count = 5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs#L24) | Marks the exclusive end of the valid option range. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiMouseCursor : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L7) | Defines the supported mouse cursor choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.None = -1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L12) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.Arrow = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L16) | Selects the arrow presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.TextInput = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L20) | Selects the text input presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.ResizeAll = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L24) | Selects the resize all presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.ResizeNs = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L28) | Selects the resize ns presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.ResizeEw = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L32) | Selects the resize ew presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.ResizeNesw = 5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L36) | Selects the resize nesw presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.ResizeNwse = 6`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L40) | Selects the resize nwse presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.Hand = 7`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L44) | Selects the hand presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.Wait = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L48) | Selects the wait presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.Progress = 9`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L52) | Selects the progress presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.NotAllowed = 10`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L56) | Selects the not allowed presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiMouseCursor.Count = 11`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs#L60) | Marks the exclusive end of the valid option range. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiPopupFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L8) | Defines the supported popup flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonLeft = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L17) | For BeginPopupContext*(): open on Left Mouse release. Only one button allowed! | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonRight = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L21) | For BeginPopupContext*(): open on Right Mouse release. Only one button allowed! (default) | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonMiddle = Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonLeft \| Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonRight`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L25) | For BeginPopupContext*(): open on Middle Mouse release. Only one button allowed! | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.NoReopen = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L29) | For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation) | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.NoOpenOverExistingPopup = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L33) | For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.NoOpenOverItems = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L37) | For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.AnyPopupId = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L41) | For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.AnyPopupLevel = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L45) | For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.AnyPopup = Inno.Editor.ImGui.ImGuiPopupFlags.AnyPopupId \| Inno.Editor.ImGui.ImGuiPopupFlags.AnyPopupLevel`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L49) | Selects the any popup presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonShift = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L53) | Selects the mouse button shift presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonMask = Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonLeft \| Inno.Editor.ImGui.ImGuiPopupFlags.MouseButtonRight`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L57) | Selects the mouse button mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiPopupFlags.InvalidMask = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs#L61) | Selects the invalid mask presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiSelectableFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L8) | Defines the supported selectable flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.NoAutoClosePopups = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L17) | Clicking this doesn't close parent popup window (overrides ImGuiItemFlags_AutoClosePopups) | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.SpanAllColumns = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L21) | Frame will span all columns of its container table (text will still fit in current column) | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.AllowDoubleClick = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L25) | Generate press events on double clicks too | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.Disabled = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L29) | Cannot be selected, display grayed out text | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.AllowOverlap = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L33) | (WIP) Hit testing to allow subsequent widgets to overlap this one | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.Highlight = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L37) | Make the item be displayed as if it is hovered | — | — | +| [`Inno.Editor.ImGui.ImGuiSelectableFlags.SelectOnNav = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs#L41) | Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiSliderFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L8) | Defines the supported slider flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.Logarithmic = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L17) | Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.NoRoundToFormat = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L21) | Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits). | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.NoInput = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L25) | Disable Ctrl+Click or Enter key allowing to input text directly into the widget. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.WrapAround = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L29) | Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.ClampOnInput = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L33) | Clamp value to min/max bounds when input manually with Ctrl+Click. By default Ctrl+Click allows going out of bounds. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.ClampZeroRange = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L37) | Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.NoSpeedTweaks = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L41) | Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.ColorMarkers = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L45) | DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.AlwaysClamp = Inno.Editor.ImGui.ImGuiSliderFlags.ClampOnInput \| Inno.Editor.ImGui.ImGuiSliderFlags.ClampZeroRange`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L49) | Selects the always clamp presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiSliderFlags.InvalidMask = 1879048207`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs#L53) | Selects the invalid mask presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiStyleVar : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L7) | Defines the supported style var choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.Alpha = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L12) | Selects the alpha presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.DisabledAlpha = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L16) | Selects the disabled alpha presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.WindowPadding = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L20) | Selects the window padding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.WindowRounding = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L24) | Selects the window rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.WindowBorderSize = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L28) | Selects the window border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.WindowMinSize = 5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L32) | Selects the window min size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.WindowTitleAlign = 6`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L36) | Selects the window title align presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ChildRounding = 7`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L40) | Selects the child rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ChildBorderSize = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L44) | Selects the child border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.PopupRounding = 9`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L48) | Selects the popup rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.PopupBorderSize = 10`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L52) | Selects the popup border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.FramePadding = 11`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L56) | Selects the frame padding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.FrameRounding = 12`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L60) | Selects the frame rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.FrameBorderSize = 13`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L64) | Selects the frame border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ItemSpacing = 14`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L68) | Selects the item spacing presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ItemInnerSpacing = 15`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L72) | Selects the item inner spacing presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.IndentSpacing = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L76) | Selects the indent spacing presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.CellPadding = 17`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L80) | Selects the cell padding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ScrollbarSize = 18`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L84) | Selects the scrollbar size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ScrollbarRounding = 19`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L88) | Selects the scrollbar rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ScrollbarPadding = 20`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L92) | Selects the scrollbar padding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.GrabMinSize = 21`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L96) | Selects the grab min size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.GrabRounding = 22`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L100) | Selects the grab rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ImageRounding = 23`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L104) | Selects the image rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ImageBorderSize = 24`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L108) | Selects the image border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabRounding = 25`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L112) | Selects the tab rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabBorderSize = 26`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L116) | Selects the tab border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabMinWidthBase = 27`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L120) | Selects the tab min width base presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabMinWidthShrink = 28`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L124) | Selects the tab min width shrink presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabBarBorderSize = 29`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L128) | Selects the tab bar border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TabBarOverlineSize = 30`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L132) | Selects the tab bar overline size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TableAngledHeadersAngle = 31`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L136) | Selects the table angled headers angle presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TableAngledHeadersTextAlign = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L140) | Selects the table angled headers text align presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TreeLinesSize = 33`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L144) | Selects the tree lines size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.TreeLinesRounding = 34`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L148) | Selects the tree lines rounding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.ButtonTextAlign = 35`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L152) | Selects the button text align presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.SelectableTextAlign = 36`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L156) | Selects the selectable text align presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.SeparatorTextBorderSize = 37`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L160) | Selects the separator text border size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.SeparatorTextAlign = 38`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L164) | Selects the separator text align presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.SeparatorTextPadding = 39`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L168) | Selects the separator text padding presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.DockingSeparatorSize = 40`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L172) | Selects the docking separator size presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiStyleVar.Count = 41`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs#L176) | Marks the exclusive end of the valid option range. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTabBarFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L8) | Defines the supported tab bar flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.Reorderable = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L17) | Allow manually dragging tabs to re-order them + New tabs are appended at the end of list | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.AutoSelectNewTabs = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L21) | Automatically select new tabs when they appear | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.TabListPopupButton = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L25) | Disable buttons to open the tab list popup | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.NoCloseWithMiddleMouseButton = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L29) | Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() & & IsMouseClicked(2)) *p_open = false. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.NoTabListScrollingButtons = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L33) | Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.NoTooltip = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L37) | Disable tooltips when hovering a tab | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.DrawSelectedOverline = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L41) | Draw selected overline markers over selected tab | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyMixed = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L45) | Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyShrink = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L49) | Shrink down tabs when they don't fit | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyScroll = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L53) | Enable scrolling buttons when tabs don't fit | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyMask = Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyDefault \| Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyShrink \| Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyScroll`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L57) | Selects the fitting policy mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabBarFlags.FittingPolicyDefault = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs#L61) | Selects the fitting policy default presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTabItemFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L8) | Defines the supported tab item flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.UnsavedDocument = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L17) | Display a dot next to the title + set ImGuiTabItemFlags_NoAssumedClosure. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.SetSelected = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L21) | Trigger flag to programmatically make the tab selected when calling BeginTabItem() | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.NoCloseWithMiddleMouseButton = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L25) | Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() & & IsMouseClicked(2)) *p_open = false. | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.NoPushId = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L29) | Don't call PushID()/PopID() on BeginTabItem()/EndTabItem() | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.NoTooltip = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L33) | Disable tooltip for the given tab | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.NoReorder = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L37) | Disable reordering this tab or having another tab cross over this tab | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.Leading = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L41) | Enforce the tab position to the left of the tab bar (after the tab list popup button) | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.Trailing = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L45) | Enforce the tab position to the right of the tab bar (before the scrolling buttons) | — | — | +| [`Inno.Editor.ImGui.ImGuiTabItemFlags.NoAssumedClosure = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs#L49) | Tab is selected when trying to close + closure is not immediately assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTableBgTarget : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs#L7) | Defines the supported table bg target choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableBgTarget.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs#L12) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableBgTarget.RowBg0 = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs#L16) | Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableBgTarget.RowBg1 = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs#L20) | Set row background color 1 (generally used for selection marking) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableBgTarget.CellBg = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs#L24) | Set cell background color (top-most color) | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTableColumnFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L8) | Defines the supported table column flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.Disabled = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L17) | Overriding/master disable flag: hide column, won't show in context menu (unlike calling TableSetColumnEnabled() which manipulates the user accessible state) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.DefaultHide = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L21) | Default as a hidden/disabled column. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.DefaultSort = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L25) | Default as a sorting column. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.WidthStretch = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L29) | Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.WidthFixed = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L33) | Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoResize = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L37) | Disable manual resizing. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoReorder = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L41) | Disable manual reordering this column, this will also prevent other columns from crossing over this column. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoHide = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L45) | Disable ability to hide/disable this column. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoClip = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L49) | Disable clipping for this column (all NoClip columns will render in a same draw command). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoSort = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L53) | Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoSortAscending = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L57) | Disable ability to sort in the ascending direction. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoSortDescending = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L61) | Disable ability to sort in the descending direction. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoHeaderLabel = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L65) | TableHeadersRow() will submit an empty label for this column. Convenient for some small columns. Name will still appear in context menu or in angled headers. You may append into this cell by calling TableSetColumnIndex() right after the TableHeadersRow() call. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoHeaderWidth = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L69) | Disable header text width contribution to automatic column width. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.PreferSortAscending = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L73) | Make the initial sort direction Ascending when first sorting on this column (default). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.PreferSortDescending = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L77) | Make the initial sort direction Descending when first sorting on this column. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IndentEnable = 65536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L81) | Use current Indent value when entering cell (default for column 0). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IndentDisable = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L85) | Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.AngledHeader = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L89) | TableHeadersRow() will submit an angled header row for this column. Note this will add an extra row. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IsEnabled = 16777216`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L93) | Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IsVisible = 33554432`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L97) | Status: is visible == is enabled AND not clipped by scrolling. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IsSorted = 67108864`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L101) | Status: is currently part of the sort specs | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IsHovered = 134217728`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L105) | Status: is hovered by mouse | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.WidthMask = Inno.Editor.ImGui.ImGuiTableColumnFlags.WidthStretch \| Inno.Editor.ImGui.ImGuiTableColumnFlags.WidthFixed`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L109) | Selects the width mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.IndentMask = Inno.Editor.ImGui.ImGuiTableColumnFlags.IndentEnable \| Inno.Editor.ImGui.ImGuiTableColumnFlags.IndentDisable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L113) | Selects the indent mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.StatusMask = Inno.Editor.ImGui.ImGuiTableColumnFlags.IsEnabled \| Inno.Editor.ImGui.ImGuiTableColumnFlags.IsVisible \| Inno.Editor.ImGui.ImGuiTableColumnFlags.IsSorted \| Inno.Editor.ImGui.ImGuiTableColumnFlags.IsHovered`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L117) | Selects the status mask presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableColumnFlags.NoDirectResize = 1073741824`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs#L121) | Selects the no direct resize presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTableFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L8) | Defines the supported table flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.Resizable = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L17) | Enable resizing columns. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.Reorderable = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L21) | Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.Hideable = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L25) | Enable hiding/disabling columns in context menu. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.Sortable = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L29) | Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoSavedSettings = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L33) | Disable persisting columns order, width, visibility and sort settings in the .ini file. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.ContextMenuInBody = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L37) | Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.RowBg = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L41) | Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerH = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L45) | Draw horizontal borders between rows. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterH = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L49) | Draw horizontal borders at the top and bottom. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerV = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L53) | Draw vertical borders between columns. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterV = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L57) | Draw vertical borders on the left and right sides. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersH = Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerH \| Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterH`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L61) | Draw horizontal borders. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersV = Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerV \| Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterV`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L65) | Draw vertical borders. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersInner = Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerH \| Inno.Editor.ImGui.ImGuiTableFlags.BordersInnerV`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L69) | Draw inner borders. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.BordersOuter = Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterH \| Inno.Editor.ImGui.ImGuiTableFlags.BordersOuterV`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L73) | Draw outer borders. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.Borders = Inno.Editor.ImGui.ImGuiTableFlags.BordersH \| Inno.Editor.ImGui.ImGuiTableFlags.BordersV`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L77) | Draw all borders. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoBordersInBody = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L81) | Disable vertical borders in columns Body (borders will always appear in Headers). -> May move to style | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoBordersInBodyUntilResize = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L85) | Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers). -> May move to style | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SizingFixedFit = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L89) | Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SizingFixedSame = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L93) | Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SizingStretchProp = Inno.Editor.ImGui.ImGuiTableFlags.SizingFixedFit \| Inno.Editor.ImGui.ImGuiTableFlags.SizingFixedSame`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L97) | Columns default to _WidthStretch with default weights proportional to each columns contents widths. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SizingStretchSame = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L101) | Columns default to _WidthStretch with default weights all equal, unless overridden by TableSetupColumn(). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoHostExtendX = 65536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L105) | Make outer width auto-fit to columns, overriding outer_size.x value. Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoHostExtendY = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L109) | Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoKeepColumnsVisible = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L113) | Disable keeping column always minimally visible when ScrollX is off and table gets too small. Not recommended if columns are resizable. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.PreciseWidths = 524288`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L117) | Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoClip = 1048576`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L121) | Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.PadOuterX = 2097152`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L125) | Default if BordersOuterV is on. Enable outermost padding. Generally desirable if you have headers. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoPadOuterX = 4194304`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L129) | Default if BordersOuterV is off. Disable outermost padding. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.NoPadInnerX = 8388608`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L133) | Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.ScrollX = 16777216`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L137) | Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this creates a child window, ScrollY is currently generally recommended when using ScrollX. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.ScrollY = 33554432`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L141) | Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SortMulti = 67108864`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L145) | Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SortTristate = 134217728`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L149) | Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.HighlightHoveredColumn = 268435456`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L153) | Highlight column headers when hovered (may evolve into a fuller highlight) | — | — | +| [`Inno.Editor.ImGui.ImGuiTableFlags.SizingMask = Inno.Editor.ImGui.ImGuiTableFlags.SizingStretchProp \| Inno.Editor.ImGui.ImGuiTableFlags.SizingStretchSame`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs#L157) | Selects the sizing mask presentation behavior. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTableRowFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs#L8) | Defines the supported table row flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableRowFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTableRowFlags.Headers = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs#L17) | Identify header row (set default background color + width of its contents accounted differently for auto column width) | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiTreeNodeFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L8) | Defines the supported tree node flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.Selected = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L17) | Draw as selected | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.Framed = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L21) | Draw frame with background (e.g. for CollapsingHeader) | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.AllowOverlap = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L25) | Hit testing to allow subsequent widgets to overlap this one | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.NoTreePushOnOpen = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L29) | Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.NoAutoOpenOnLog = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L33) | Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.DefaultOpen = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L37) | Default node to be open | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.OpenOnDoubleClick = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L41) | Open on double-click instead of simple click (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.OpenOnArrow = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L45) | Open when clicking on the arrow part (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.Leaf = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L49) | No collapsing, no arrow (use as a convenience for leaf nodes). | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.Bullet = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L53) | Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.FramePadding = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L57) | Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.SpanAvailWidth = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L61) | Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.SpanFullWidth = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L65) | Extend hit box to the left-most and right-most edges (cover the indent area). | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.SpanLabelWidth = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L69) | Narrow hit box + narrow hovering highlight, will only cover the label text. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.SpanAllColumns = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L73) | Frame will span all columns of its container table (label will still fit in current column) | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.LabelSpanAllColumns = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L77) | Label will span all columns of its container table | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.NavLeftJumpsToParent = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L81) | Nav: left arrow moves back to parent. This is processed in TreePop() when there's an unfulfilled Left nav request remaining. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.CollapsingHeader = Inno.Editor.ImGui.ImGuiTreeNodeFlags.Framed \| Inno.Editor.ImGui.ImGuiTreeNodeFlags.NoTreePushOnOpen \| Inno.Editor.ImGui.ImGuiTreeNodeFlags.NoAutoOpenOnLog`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L85) | Selects the collapsing header presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.DrawLinesNone = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L89) | No lines drawn | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.DrawLinesFull = 524288`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L93) | Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. Faster (for large trees). | — | — | +| [`Inno.Editor.ImGui.ImGuiTreeNodeFlags.DrawLinesToNodes = 1048576`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs#L97) | Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. Slower (for large trees). | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiWindowFlags : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L8) | Defines the supported window flags choices at the Editor presentation boundary. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L13) | Uses the ordinary behavior without additional options. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoTitleBar = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L17) | Disable title-bar | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoResize = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L21) | Disable user resizing with the lower-right grip | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoMove = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L25) | Disable user moving the window | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoScrollbar = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L29) | Disable scrollbars (window can still scroll with mouse or programmatically) | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoScrollWithMouse = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L33) | Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoCollapse = 32`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L37) | Disable user collapsing window by double-clicking on it. Also referred to as Window Menu Button (e.g. within a docking node). | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.AlwaysAutoResize = 64`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L41) | Resize every window to its content every frame | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoBackground = 128`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L45) | Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f). | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoSavedSettings = 256`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L49) | Never load/save settings in .ini file | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoMouseInputs = 512`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L53) | Disable catching mouse, hovering test with pass through. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.MenuBar = 1024`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L57) | Has a menu-bar | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.HorizontalScrollbar = 2048`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L61) | Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoFocusOnAppearing = 4096`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L65) | Disable taking focus when transitioning from hidden to visible state | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoBringToFrontOnFocus = 8192`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L69) | Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.AlwaysVerticalScrollbar = 16384`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L73) | Always show vertical scrollbar (even if ContentSize.y < Size.y) | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.AlwaysHorizontalScrollbar = 32768`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L77) | Always show horizontal scrollbar (even if ContentSize.x < Size.x) | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoNavInputs = 65536`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L81) | No keyboard/gamepad navigation within the window | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoNavFocus = 131072`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L85) | No focusing toward this window with keyboard/gamepad navigation (e.g. skipped by Ctrl+Tab) | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.UnsavedDocument = 262144`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L89) | Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoDocking = 524288`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L93) | Disable docking of this window | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoNav = Inno.Editor.ImGui.ImGuiWindowFlags.NoNavInputs \| Inno.Editor.ImGui.ImGuiWindowFlags.NoNavFocus`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L97) | Selects the no nav presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoDecoration = Inno.Editor.ImGui.ImGuiWindowFlags.NoTitleBar \| Inno.Editor.ImGui.ImGuiWindowFlags.NoResize \| Inno.Editor.ImGui.ImGuiWindowFlags.NoScrollbar \| Inno.Editor.ImGui.ImGuiWindowFlags.NoCollapse`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L101) | Selects the no decoration presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.NoInputs = Inno.Editor.ImGui.ImGuiWindowFlags.NoMouseInputs \| Inno.Editor.ImGui.ImGuiWindowFlags.NoNav`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L105) | Selects the no inputs presentation behavior. | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.DockNodeHost = 8388608`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L109) | Don't use! For internal use by Begin()/NewFrame() | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.ChildWindow = 16777216`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L113) | Don't use! For internal use by BeginChild() | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.Tooltip = 33554432`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L117) | Don't use! For internal use by BeginTooltip() | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.Popup = 67108864`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L121) | Don't use! For internal use by BeginPopup() | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.Modal = 134217728`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L125) | Don't use! For internal use by BeginPopupModal() | — | — | +| [`Inno.Editor.ImGui.ImGuiWindowFlags.ChildMenu = 268435456`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs#L129) | Don't use! For internal use by BeginMenu() | — | — | +| [`public readonly struct Inno.Editor.ImGui.EditorDrawList`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L11) | A pointer-free drawing borrow valid only in its original window, UI session and frame. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.Line(System.Numerics.Vector2 start, System.Numerics.Vector2 end, uint color, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L39) | Adds a straight screen-space stroke.
`start`: The first endpoint.
`end`: The final endpoint.
`color`: Packed RGBA color.
`thickness`: Stroke width in UI pixels. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.FilledRectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0, bool topCornersOnly = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L63) | Adds a filled rectangular surface.
`minimum`: The upper-left corner.
`maximum`: The lower-right corner.
`color`: Packed RGBA color.
`rounding`: Corner radius.
`topCornersOnly`: Whether only the two top corners should be rounded. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.Rectangle(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint color, float rounding = 0, float thickness = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L88) | Adds a rectangular outline.
`minimum`: The upper-left corner.
`maximum`: The lower-right corner.
`color`: Packed RGBA color.
`rounding`: Corner radius.
`thickness`: Stroke width in UI pixels. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.FilledCircle(System.Numerics.Vector2 center, float radius, uint color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L107) | Adds a filled circle with backend-selected tessellation.
`center`: The screen-space center.
`radius`: The nonnegative radius.
`color`: Packed RGBA color. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.Text(System.Numerics.Vector2 position, uint color, string text, float scale = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L131) | Adds literal text using the current font and an explicit scale.
`position`: Screen-space text origin.
`color`: Packed RGBA color.
`text`: Literal managed text; encoded only for this synchronous call.
`scale`: Positive scale relative to the current font size. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.PushClip(System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, bool intersect = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L157) | Restricts subsequent drawing to one screen rectangle.
`minimum`: The clip minimum.
`maximum`: The clip maximum.
`intersect`: Whether to intersect the enclosing clip. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.PopClip()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L169) | Restores the enclosing clip after a matching PushClip. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.SplitChannels(int count)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L176) | Begins ordered draw channels within this window.
`count`: The positive number of layers. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.SetChannel(int index)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L188) | Selects a channel from the current split.
`index`: The zero-based channel. | — | — | +| [`public void Inno.Editor.ImGui.EditorDrawList.MergeChannels()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs#L192) | Merges the current split back into ordered draw commands. | — | — | +| [`public static class Inno.Editor.ImGui.EditorDragDropRenderer`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L12) | Bridges managed editor drag sessions to the native ImGui payload API. | — | — | +| [`public static bool Inno.Editor.ImGui.EditorDragDropRenderer.Source(Inno.Editor.Interactions.EditorInteraction interaction, Inno.Editor.Interactions.EditorDragData data, System.Action? drawPreview = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L30) | Publishes managed drag data for the most recently submitted ImGui item.
`interaction`: The interaction area and target that produced the drag source.
`data`: The managed drag data published by the source.
`drawPreview`: An optional callback that draws the native drag preview. | while the item is an active drag source. | — | +| [`public static Inno.Editor.ImGui.EditorDropWidgetResult Inno.Editor.ImGui.EditorDragDropRenderer.Target(Inno.Editor.Interactions.EditorInteraction interaction, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L51) | Evaluates and, on delivery, accepts a managed drag on the most recently submitted ImGui item.
`interaction`: The interaction area and managed drop target.
`placement`: The requested position relative to the target. | The native preview state together with the managed compatibility and delivery results. | — | +| [`public readonly record struct Inno.Editor.ImGui.EditorDropWidgetResult : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Reports the preview and delivery state of an ImGui editor drop target. | — | — | +| [`public Inno.Editor.ImGui.EditorDropWidgetResult.EditorDropWidgetResult(bool isPreviewing, Inno.Editor.Interactions.EditorDropStatus status, Inno.Editor.Interactions.EditorDropResult result)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L88) | Creates a combined native-preview and managed-drop result.
`isPreviewing`: Whether a compatible native payload is currently hovering the target.
`status`: The compatibility and visual state returned by the managed drop router.
`result`: The result produced when the payload was delivered. | — | — | +| [`public bool Inno.Editor.ImGui.EditorDropWidgetResult.isPreviewing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L101) | Gets whether a compatible native payload is hovering the target. | — | — | +| [`public Inno.Editor.Interactions.EditorDropStatus Inno.Editor.ImGui.EditorDropWidgetResult.status { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L105) | Gets the managed drop compatibility status. | — | — | +| [`public Inno.Editor.Interactions.EditorDropResult Inno.Editor.ImGui.EditorDropWidgetResult.result { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L109) | Gets the delivered drop result. | — | — | +| [`public static Inno.Editor.ImGui.EditorDropWidgetResult Inno.Editor.ImGui.EditorDropWidgetResult.none { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L113) | Gets an inactive drop target result. | — | — | +| [`public override string Inno.Editor.ImGui.EditorDropWidgetResult.ToString()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.ImGui.EditorDropWidgetResult.operator !=(Inno.Editor.ImGui.EditorDropWidgetResult left, Inno.Editor.ImGui.EditorDropWidgetResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.ImGui.EditorDropWidgetResult.operator ==(Inno.Editor.ImGui.EditorDropWidgetResult left, Inno.Editor.ImGui.EditorDropWidgetResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.ImGui.EditorDropWidgetResult.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.ImGui.EditorDropWidgetResult.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.ImGui.EditorDropWidgetResult.Equals(Inno.Editor.ImGui.EditorDropWidgetResult other)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs#L74) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.ImGui.EditorGameInputCapture`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L13) | Routes platform input to the Play session only while the visible Game View owns focus. | — | — | +| [`public Inno.Editor.ImGui.EditorGameInputCapture.EditorGameInputCapture(System.Func resolveWindow)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L35) | Creates a capture policy using a presentation viewport to platform window resolver.
`resolveWindow`: Returns a live window ID for a viewport, or null when absent. | — | `System.ArgumentNullException`: Thrown when the resolver is null. | +| [`public void Inno.Editor.ImGui.EditorGameInputCapture.BeginFrame()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L43) | Starts a presentation pass in which the visible Game View must report its current bounds. | — | — | +| [`public bool Inno.Editor.ImGui.EditorGameInputCapture.Report(uint viewportId, System.Numerics.Vector2 origin, System.Numerics.Vector2 size, bool focused, bool hovered)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L66) | Reports the Game View image in coordinates local to its platform window.
`viewportId`: The presentation viewport containing the Game View.
`origin`: The image's upper-left corner relative to that window.
`size`: The visible image size in logical pixels.
`focused`: Whether the Game View is selected and its platform window has focus.
`hovered`: Whether the image is inside the frontmost hovered ImGui window. | when the Game View and its platform window both own focus. | — | +| [`public uint Inno.Editor.ImGui.EditorGameInputCapture.CompleteFrame()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L89) | Ends a presentation pass and identifies input that must be released after focus loss. | The former platform window ID to reset, or zero if no reset is needed. | — | +| [`public Inno.Core.Events.Event? Inno.Editor.ImGui.EditorGameInputCapture.Route(Inno.Core.Events.Event evnt)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorGameInputCapture.cs#L126) | Filters an engine event through the current Game View focus and pointer bounds.
`evnt`: The original backend-neutral platform event. | A routed event or a synthetic focus reset, or null when the Game View must not receive input. | `System.ArgumentNullException`: Thrown when the event is null. | +| [`public sealed class Inno.Editor.ImGui.EditorMenuRenderer`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L15) | Renders immutable editor menu models through ImGui. | — | — | +| [`public bool Inno.Editor.ImGui.EditorMenuRenderer.ContextMenu(string id, Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L43) | Draws a resolved right-click menu for the most recently submitted ImGui item.
`id`: The stable popup identifier in the current ImGui ID scope.
`interaction`: The interaction area and optional operation target. | while the context popup is open and its items were drawn. | — | +| [`public bool Inno.Editor.ImGui.EditorMenuRenderer.ContextMenu(string id, Inno.Editor.Interactions.EditorInteraction scopeInteraction, Inno.Editor.Interactions.EditorInteraction itemInteraction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L88) | Draws one context popup composed from an item interaction and its containing-scope interaction.
`id`: Stable popup identifier in the current ImGui ID scope.
`scopeInteraction`: Container interaction whose commands are shown first, such as creation commands for a directory.
`itemInteraction`: Selected-item interaction whose commands are shown after the container commands. | while the context popup is open and at least one command group was drawn. | — | +| [`public bool Inno.Editor.ImGui.EditorMenuRenderer.WindowContextMenu(string id, Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L132) | Draws a resolved right-click menu when the current ImGui window's unoccupied background is clicked.
`id`: The stable popup identifier in the current ImGui ID scope.
`interaction`: The interaction area and directory target for the background operation. | while the background context popup is open and its items were drawn. | — | +| [`public void Inno.Editor.ImGui.EditorMenuRenderer.MainMenu(Inno.Editor.Interactions.EditorInteraction interaction)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L215) | Draws the complete editor main menu bar for the supplied menu context.
`interaction`: The main-menu interaction area. | — | — | +| [`public void Inno.Editor.ImGui.EditorMenuRenderer.DrawItems(Inno.Editor.Interactions.EditorInteraction interaction, System.Collections.Generic.IReadOnlyList items)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L309) | Recursively draws resolved menu nodes into the currently open popup or menu.
`interaction`: The interaction used to resolve shortcuts and enqueue selected actions.
`items`: The immutable menu nodes to draw in display order. | — | — | +| [`public bool Inno.Editor.ImGui.EditorMenuRenderer.DrawSearchItems(Inno.Editor.Interactions.EditorInteraction interaction, System.Collections.Generic.IReadOnlyList items, string search)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs#L364) | Draws matching leaf commands as a flat searchable list.
`interaction`: The interaction used to enqueue selected actions.
`items`: The immutable menu tree whose leaves should be searched.
`search`: The case-insensitive text matched against each slash-delimited leaf path. | when a command was selected. | — | +| [`public readonly struct Inno.Editor.ImGui.EditorUiInput`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L8) | Copies frame input facts used by presentation without exposing native input storage. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiInput.alt { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L13) | Gets whether Alt is held. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiInput.control { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L17) | Gets whether the physical Control modifier is held, independently of native shortcut conventions. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiInput.shift { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L21) | Gets whether Shift is held. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiInput.super { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L25) | Gets whether the physical Super modifier is held, independently of native shortcut conventions. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiInput.wantsText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L29) | Gets whether an active control owns text input. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiInput.mouseDelta { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L33) | Gets the current pointer movement in UI units. | — | — | +| [`public float Inno.Editor.ImGui.EditorUiInput.wheel { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L37) | Gets the vertical scroll movement. | — | — | +| [`public float Inno.Editor.ImGui.EditorUiInput.deltaTime { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L41) | Gets the frame duration in seconds. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiInput.framebufferScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs#L45) | Gets the drawable pixel scale relative to logical UI coordinates. | — | — | +| [`public sealed class Inno.Editor.ImGui.EditorUiSession : System.IDisposable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L12) | Owns editor controls and transient presentation state for one host, independently from other ImGui contexts. | — | — | +| [`public Inno.Editor.ImGui.EditorUiSession.EditorUiSession()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L27) | Creates independent controls and style state; the presentation backend retains its native context ownership. | — | — | +| [`public static Inno.Editor.ImGui.EditorUiSession Inno.Editor.ImGui.EditorUiSession.current { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L39) | Gets the current synchronous drawing scope; this accessor never creates or owns a session. | — | `System.InvalidOperationException`: No UI drawing scope is active on the calling thread. | +| [`public Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget Inno.Editor.ImGui.EditorUiSession.widgets { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L43) | Gets the controls whose transient state belongs exclusively to this session. | — | — | +| [`public Inno.Editor.ImGui.EditorMenuRenderer Inno.Editor.ImGui.EditorUiSession.menus { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L49) | Gets the menu presenter whose search state belongs exclusively to this session. | — | — | +| [`public bool Inno.Editor.ImGui.EditorUiSession.isDisposed { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L53) | Gets whether this session has successfully retired its presentation state. | — | — | +| [`public System.IDisposable Inno.Editor.ImGui.EditorUiSession.Enter()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L81) | Activates this session for a synchronous callback while preserving the enclosing scope. | An owner-thread scope that must be released in reverse activation order. | `System.ObjectDisposedException`: The session has retired.
`System.InvalidOperationException`: The caller is not the owner thread. | +| [`public void Inno.Editor.ImGui.EditorUiSession.Dispose()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs#L97) | Releases presentation state after all drawing scopes and domain edit gestures have ended. | — | `System.InvalidOperationException`: A drawing scope is active or the caller is not the owner thread. | +| [`public sealed class Inno.Editor.ImGui.ImGuiEditorRuntime : Inno.Editor.Core.EditorRuntime`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L21) | Presents the backend-independent editor interaction runtime through ImGui. | — | — | +| [`public Inno.Editor.ImGui.ImGuiEditorRuntime.ImGuiEditorRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IEnumerable hostServices)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L52) | Creates an ImGui editor runtime with stable host-owned extension services.
`context`: The shared editor context that owns project settings and frame state.
`types`: The host-owned type catalog that coordinates extension generations.
`logs`: The host-owned logging router used by editor infrastructure.
`hostServices`: Services available to discovered extension constructors. | — | `System.ArgumentNullException`: Thrown when , , or is . | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.ImGui.ImGuiEditorRuntime.interactions { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L75) | Gets the active presentation-independent interaction entry point. | — | — | +| [`public int Inno.Editor.ImGui.ImGuiEditorRuntime.panelCount { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L79) | Gets the number of active dockable panels. | — | — | +| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Start()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L84) | Starts value processing after validating the current state. | — | — | +| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L96) | Recomputes owned state from the current validated inputs.
`frame`: The frame consumed by update; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.SaveState()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L108) | Captures all stateful active modules and panels and flushes their project state to disk. | — | `System.ObjectDisposedException`: Thrown after this runtime has been disposed. | +| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.PrepareShutdown()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L126) | Freezes automatic extension-state persistence and writes the final project state before editor modules begin shutting down. | — | `System.ObjectDisposedException`: Thrown after this runtime has been disposed. | +| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.PrepareFrame()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L143) | Applies pending style inputs with the owned context before the backend creates its native frame. | — | `System.ObjectDisposedException`: This runtime has retired.
`System.InvalidOperationException`: The owning native context is unavailable, incorrect, or already drawing. | +| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.Draw()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L154) | Draws the complete editor frame through ImGui. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiEditorRuntime.HandleKeyPressed(Inno.Core.Events.KeyPressedEvent keyEvent)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L281) | Dispatches contextual shortcuts, leaving editing keys with active text widgets while permitting explicit save.
`keyEvent`: The keyboard event received from the application event stream. | — | — | +| [`public override void Inno.Editor.ImGui.ImGuiEditorRuntime.Dispose()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs#L302) | Releases the resources owned by this implementation. | — | `Inno.Core.Execution.RetirementPendingException`: Editor extensions still own live work. The interaction runtime remains retained for safe shutdown. | +| [`public static class Inno.Editor.ImGui.EditorPalette`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L11) | Defines the complete editor color palette in one theme surface. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityNone = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L16) | Gets zero opacity for invisible surfaces. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityTrace = 0.005`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L21) | Gets trace opacity for barely perceptible alternating surfaces. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityFaint = 0.04`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L26) | Gets faint opacity for quiet guides and separators. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacitySubtle = 0.12`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L31) | Gets subtle opacity for selection washes and low-emphasis overlays. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacitySoft = 0.2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L36) | Gets soft opacity for tinted container surfaces. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityMuted = 0.28`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L41) | Gets muted opacity for shadows and subdued outlines. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityMedium = 0.45`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L46) | Gets medium opacity for secondary content and overlays. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityStrong = 0.55`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L51) | Gets strong opacity for accents that remain translucent. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityProminent = 0.7`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L56) | Gets prominent opacity for navigation emphasis. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityEmphasized = 0.82`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L61) | Gets emphasized opacity for borders and interactive grips. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityNearOpaque = 0.92`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L66) | Gets near-opaque opacity for active overlays. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityPopup = 0.985`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L71) | Gets popup opacity, retaining only enough transparency for depth. | — | — | +| [`public const float Inno.Editor.ImGui.EditorPalette.opacityOpaque = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L76) | Gets full opacity. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.transparent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L81) | Gets fully transparent color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.text { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L86) | Gets the primary text color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.textDisabled { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L91) | Gets disabled text color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.error { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L96) | Gets the standard error color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.warning { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L101) | Gets the standard warning color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.windowBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L106) | Gets the editor window background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.popupBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L111) | Gets the editor popup background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L116) | Gets text color used by editor context menus. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuBackground { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L121) | Gets the background used by editor context menus. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItem { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L126) | Gets the resting background of an editor context-menu item. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItemHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L131) | Gets the hovered background of an editor context-menu item. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuItemActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L136) | Gets the active background of an editor context-menu item. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.menuSeparator { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L141) | Gets the separator color used by editor context menus. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.border { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L146) | Gets the standard border color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.borderShadow { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L151) | Gets the standard border shadow color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frame { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L156) | Gets the standard frame background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frameHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L161) | Gets the hovered frame background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.frameActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L166) | Gets the active frame background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.title { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L171) | Gets the inactive title background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.titleActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L176) | Gets the active title background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.titleCollapsed { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L181) | Gets the collapsed title background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrab { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L186) | Gets the standard scrollbar thumb. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrabHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L191) | Gets the hovered scrollbar thumb. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.scrollbarGrabActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L196) | Gets the active scrollbar thumb. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L201) | Gets the standard accent color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accentHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L206) | Gets the hovered accent color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.accentActive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L211) | Gets the active accent color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tab { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L216) | Gets the standard tab color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L221) | Gets the hovered tab color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabSelected { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L226) | Gets the selected tab color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabDimmed { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L231) | Gets the dimmed tab color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabDimmedSelected { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L236) | Gets the selected dimmed tab color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tabSelectedOverline { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L241) | Gets the selected tab overline color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L246) | Gets the table header background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableBorderStrong { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L251) | Gets the strong table border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableBorderLight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L256) | Gets the light table border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.tableRowAlternate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L261) | Gets the alternate table row background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.dragDropTarget { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L266) | Gets the standard drag target color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.navigationHighlight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L271) | Gets navigation highlight color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.navigationDim { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L276) | Gets navigation dim background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.modalDim { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L281) | Gets modal dim background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.cardHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L286) | Gets card header background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.targetHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L291) | Gets the persistent target header background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.targetHeaderBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L296) | Gets the persistent target header border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.cardBody { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L301) | Gets card body background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.cardBodyBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L306) | Gets card body border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.sectionBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L311) | Gets the outline color of framed sections. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.cardDisabledText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L316) | Gets disabled card text. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.cardDisclosureHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L321) | Gets card disclosure hover background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.compactControlHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L326) | Gets compact control hover color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionHeader { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L331) | Gets the deepest collection background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionRow { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L336) | Gets the primary collection row background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.collectionRowAlternate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L341) | Gets the alternate collection row background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentField { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L346) | Gets the content field background. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentBorder { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L351) | Gets the content border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentBorderSoft { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L356) | Gets the soft content border. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L361) | Gets the content text color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentTextMuted { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L366) | Gets the muted content text color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contextText { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L371) | Gets the subdued contextual text color used by paths and origin hints. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.contentAccent { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L376) | Gets the opaque content accent. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetHovered(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L387) | Gets a hover treatment derived from a base theme color.
`color`: The color consumed by get hovered; ownership remains with the caller unless explicitly stated otherwise. | The base color blended toward the palette text color using the standard hover amount. | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.GetActive(System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L398) | Gets an active treatment derived from a base theme color.
`color`: The color consumed by get active; ownership remains with the caller unless explicitly stated otherwise. | The base color blended toward the palette text color using the standard active amount. | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.treeGuide { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L403) | Gets the guide color of reusable tree controls. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisX { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L408) | Gets X axis color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisY { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L413) | Gets Y axis color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisZ { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L418) | Gets Z axis color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.axisW { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L423) | Gets W axis color. | — | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.Lerp(System.Numerics.Vector4 from, System.Numerics.Vector4 to, float amount)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L440) | Linearly interpolates two palette colors using a clamped amount.
`from`: The color returned when is zero.
`to`: The color returned when is one.
`amount`: The interpolation amount, clamped to the inclusive zero-to-one range. | The component-wise interpolated color. | — | +| [`public static System.Numerics.Vector4 Inno.Editor.ImGui.EditorPalette.WithOpacity(System.Numerics.Vector4 color, float opacity)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs#L523) | Returns a theme color with a caller-selected opacity, normally one of the shared opacity levels.
`color`: Base RGB color.
`opacity`: Replacement alpha in the inclusive zero-to-one range. | The validated vector4 that represents the completed operation. | — | +| [`public sealed class Inno.Editor.ImGui.EditorStyleMetrics`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L14) | Defines reusable layout metrics and separates owned frame values from requested style changes. | — | — | +| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_MIN_ZOOM = 0.75`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L19) | Defines the smallest supported editor UI zoom. | — | — | +| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_MAX_ZOOM = 1.5`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L24) | Defines the largest supported editor UI zoom. | — | — | +| [`public const float Inno.Editor.ImGui.EditorStyleMetrics.C_ZOOM_STEP = 0.1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L29) | Defines one keyboard or menu zoom increment. | — | — | +| [`public Inno.Editor.ImGui.EditorStyleMetrics.EditorStyleMetrics()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L40) | Creates independent metrics whose changes apply immediately without a native context. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.zoom { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L51) | Gets the effective zoom used by the current presentation frame. | — | — | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.isCompact { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L56) | Gets whether compact editor density is active. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.trendHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L61) | Gets the scaled height of a compact sampled-value trend plot. | — | — | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.SetCompactMode(bool value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L78) | Requests comfortable or compact density without changing an active session frame.
`value`: for compact density. | when the requested density changed; owned drawing applies it at the next frame preparation. | `System.ObjectDisposedException`: The owning session has retired or is no longer available.
`System.InvalidOperationException`: The caller is not the owning session's thread. | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.SetZoom(float value)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L106) | Requests a bounded zoom without changing metrics partway through an owned native frame.
`value`: The requested UI zoom multiplier. | when the requested zoom changed; owned drawing applies it at the next frame preparation. | `System.ArgumentOutOfRangeException`: Thrown when is not finite.
`System.ObjectDisposedException`: The owning session has retired or is no longer available.
`System.InvalidOperationException`: The caller is not the owning session's thread. | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ZoomIn()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L125) | Increases editor UI zoom by one bounded increment. | when the requested zoom changed. | — | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ZoomOut()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L133) | Decreases editor UI zoom by one bounded increment. | when the requested zoom changed. | — | +| [`public bool Inno.Editor.ImGui.EditorStyleMetrics.ResetZoom()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L141) | Restores the baseline editor UI zoom. | when the requested zoom changed. | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.fontScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L146) | Gets global content scale. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.disabledAlpha { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L151) | Gets disabled content opacity. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.windowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L156) | Gets standard window padding. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.windowRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L161) | Gets standard window rounding. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.borderSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L166) | Gets standard border thickness. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.windowMinimumSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L171) | Gets minimum window size. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.framePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L176) | Gets standard frame padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.panelTabFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L181) | Gets the frame padding used while docked panel tabs are laid out. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuWindowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L186) | Gets the uniform content padding of editor context menus. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L191) | Gets the uniform item padding of editor context menus. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuSearchFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L197) | Gets the compact padding used by the search field at the top of editor context menus. Its vertical padding matches the half-spacing that menu entries add above and below a line. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.menuItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L202) | Gets the uniform spacing between editor context-menu items. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.menuRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L207) | Gets editor context-menu corner rounding. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.menuBorderSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L212) | Gets editor context-menu border thickness. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.compactFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L217) | Gets compact frame padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.inlineRenameFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L222) | Gets the compact inner padding of inline rename fields. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.toolbarFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L227) | Gets toolbar frame padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.breadcrumbFramePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L232) | Gets breadcrumb frame padding. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.frameRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L237) | Gets standard frame rounding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.itemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L242) | Gets standard item spacing. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.compactItemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L247) | Gets compact vertical item spacing. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.itemInnerSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L252) | Gets standard inner item spacing. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.cellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L257) | Gets standard table cell padding. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.indentSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L262) | Gets tree indentation. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.columnMinimumSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L267) | Gets minimum column spacing. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scrollbarSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L272) | Gets scrollbar width. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scrollbarPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L277) | Gets the inset that keeps an overlay scrollbar grab visually lightweight. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.grabMinimumSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L282) | Gets minimum grab size. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.paneMinimumVisibleWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L287) | Gets the minimum visible width retained for either resizable content pane while its splitter is dragged. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.splitterMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L292) | Gets the minimum draggable resizable content splitter width. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.cardSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L297) | Gets spacing between cards. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.targetHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L302) | Gets the inner padding of the persistent target header. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.targetHeaderRowSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L307) | Gets spacing between the two rows of the persistent target header. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.targetIconScale { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L312) | Gets the font-size multiplier used by the large target icon. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.cardBodyPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L317) | Gets card body padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.cardHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L322) | Gets card header padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.sectionHeaderPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L327) | Gets the shared padding around separator-style section titles. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.sectionPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L332) | Gets the inner padding of a framed Inspector section. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.sectionLegendGap { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L337) | Gets the gap between an Inspector section legend and its interrupted top border. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.collapsedSectionCapLength { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L342) | Gets the length of each centered end cap on a collapsed Inspector section. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.propertyMetadataSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L347) | Gets the spacing around the separator between inline metadata and its value. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.sectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L352) | Gets the vertical distance after a framed Inspector section. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.sectionRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L357) | Gets the subtle corner rounding used by framed content sections inside editor containers. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.disclosureButtonInset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L362) | Gets card disclosure inset. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.propertyLabelRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L367) | Gets property label width ratio. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.searchPopupWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L372) | Gets the default width of searchable editor popups. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.iconLabelSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L377) | Gets spacing between a leading icon and its text. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.compactCheckboxSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L382) | Gets the compact checkbox size used by card headers. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.headerControlSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L387) | Gets spacing between leading controls inside card headers. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.labelChipPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L392) | Gets the inner padding of compact colored label chips. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.labelChipRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L397) | Gets the corner rounding of compact colored label chips. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorStyleMetrics.typeBadgePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L402) | Gets the compact inner padding of outlined type badges. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.typeBadgeRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L407) | Gets the corner rounding of outlined type badges. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.typeBadgeSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L412) | Gets the horizontal gap between a property name and its type badge. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.headerSectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L417) | Gets spacing between distinct control groups in the target header row. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.addButtonTopPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L422) | Gets top padding above trailing add buttons. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.toolbarTightSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L427) | Gets the tight resizable content toolbar spacing. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.toolbarSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L432) | Gets the regular resizable content toolbar spacing. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.toolbarSectionSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L437) | Gets spacing between resizable content toolbar sections. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisValueMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L442) | Gets the minimum width of one axis value field. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisPrefixMinimumWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L447) | Gets the minimum width of an axis prefix. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.axisPrefixWidthRatio { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L452) | Gets the axis prefix share of the complete control. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.interactionOverlayThickness { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L457) | Gets the standard foreground interaction-overlay thickness. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.scriptCompilationWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L462) | Gets script compilation modal width. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.modalWidth { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L467) | Gets the standard centered modal width. | — | — | +| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalFadeInSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L472) | Gets the modal fade-in duration in seconds. | — | — | +| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalMinimumVisibleSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L477) | Gets the minimum modal visibility duration in seconds. | — | — | +| [`public double Inno.Editor.ImGui.EditorStyleMetrics.modalFadeOutSeconds { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L482) | Gets the modal fade-out duration in seconds. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeGuideLeftOffset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L487) | Gets tree guide left offset. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeGuideLineOverlap { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L492) | Gets the overlap used to join adjacent tree guide segments. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.treeFolderConnectorPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L497) | Gets additional connector padding for expandable tree nodes. | — | — | +| [`public float Inno.Editor.ImGui.EditorStyleMetrics.textDecorationOffset { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs#L502) | Gets the vertical offset of text decorations from the baseline. | — | — | +| [`public readonly struct Inno.Editor.ImGui.EditorUiLayout`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L8) | Copies the presentation metrics needed by custom layouts without retaining native style memory. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiLayout.itemSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L13) | Gets spacing between neighboring controls. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiLayout.windowPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L17) | Gets the current window inset. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiLayout.cellPadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L21) | Gets table cell padding. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiLayout.itemInnerSpacing { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L25) | Gets spacing between parts of a compound control. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.EditorUiLayout.framePadding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L29) | Gets padding inside a control. | — | — | +| [`public float Inno.Editor.ImGui.EditorUiLayout.dockingSeparatorSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L33) | Gets dock splitter thickness. | — | — | +| [`public float Inno.Editor.ImGui.EditorUiLayout.scrollbarSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L37) | Gets scrollbar width. | — | — | +| [`public float Inno.Editor.ImGui.EditorUiLayout.frameRounding { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs#L41) | Gets control corner radius. | — | — | +| [`public static class Inno.Editor.ImGui.EditorIconCatalog`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/EditorIconCatalog.cs#L9) | Describes the host's complete standard glyph vocabulary as immutable presentation values. | — | — | +| [`public static System.Collections.Generic.IReadOnlyList<(string Name, string Glyph)> Inno.Editor.ImGui.EditorIconCatalog.standard { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/EditorIconCatalog.cs#L14) | Gets all named glyphs supplied by the installed UI font, in deterministic name order. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiFontStyle : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L11) | Chooses composable font emphasis while leaving actual font ownership with the presentation host. | — | — | +| [`Inno.Editor.ImGui.ImGuiFontStyle.Regular = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L16) | Uses the ordinary face. | — | — | +| [`Inno.Editor.ImGui.ImGuiFontStyle.Bold = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L20) | Emphasizes text with the bold face. | — | — | +| [`Inno.Editor.ImGui.ImGuiFontStyle.Italic = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L24) | Emphasizes text with the italic face. | — | — | +| [`public static class Inno.Editor.ImGui.ImGuiFont`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L30) | Uses the host's context-owned font catalog without exposing backend types to Editor features. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGuiFont.IsAvailable(Inno.Editor.ImGui.ImGuiFontStyle style)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L41) | Checks whether the current host registered an exact face.
`style`: The requested emphasis. | True when the exact face is available. | — | +| [`public static Inno.Editor.ImGui.ImGuiFontScope Inno.Editor.ImGui.ImGuiFont.PushStyle(Inno.Editor.ImGui.ImGuiFontStyle style)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L56) | Pushes a host-owned font face for one synchronous control group.
`style`: The requested emphasis. | An owner-window/frame scope; the host may use a registered nearest face. | — | +| [`public ref struct Inno.Editor.ImGui.ImGuiFontScope : System.IDisposable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L68) | Restores a font push only in its original session, frame and native stack position. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiFontScope.Dispose()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs#L92) | Restores the preceding font after validating scope order; default and retired scopes are inert. | — | `System.InvalidOperationException`: The scope is used in another context, frame or stack position. | +| [`public static class Inno.Editor.ImGui.ImGuiIcon`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L5) | Provides the standard Editor glyph vocabulary; extension controls may also supply their own glyph text. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.AngleLeft = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L10) | Gets the angle left glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.AngleRight = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L14) | Gets the angle right glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.ArrowRotateLeft = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L18) | Gets the arrow rotate left glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.ArrowsUpDownLeftRight = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L22) | Gets the arrows up down left right glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Bug = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L26) | Gets the bug glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Camera = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L30) | Gets the camera glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.CircleInfo = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L34) | Gets the circle info glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.CircleQuestion = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L38) | Gets the circle question glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.CircleXmark = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L42) | Gets the circle xmark glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Cube = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L46) | Gets the cube glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Cubes = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L50) | Gets the cubes glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.DiceD6 = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L54) | Gets the dice d6 glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Eye = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L58) | Gets the eye glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.EyeSlash = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L62) | Gets the eye slash glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.File = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L66) | Gets the file glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.FileCode = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L70) | Gets the file code glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.FileLines = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L74) | Gets the file lines glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Folder = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L78) | Gets the folder glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.ForwardStep = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L82) | Gets the forward step glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Gears = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L86) | Gets the gears glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Globe = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L90) | Gets the globe glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.GripVertical = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L94) | Gets the grip vertical glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.LayerGroup = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L98) | Gets the layer group glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Lightbulb = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L102) | Gets the lightbulb glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Lock = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L106) | Gets the lock glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.LockOpen = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L110) | Gets the lock open glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.MagnifyingGlass = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L114) | Gets the magnifying glass glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Palette = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L118) | Gets the palette glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Pause = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L122) | Gets the pause glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Pen = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L126) | Gets the pen glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Play = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L130) | Gets the play glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Plus = "+"`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L134) | Gets the plus glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Rotate = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L138) | Gets the rotate glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.RupeeSign = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L142) | Gets the rupee sign glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.RupiahSign = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L146) | Gets the rupiah sign glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Shader = "S"`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L150) | Gets the shader glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.SkullCrossbones = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L154) | Gets the skull crossbones glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Stop = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L158) | Gets the stop glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.TriangleExclamation = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L162) | Gets the triangle exclamation glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.UpDownLeftRight = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L166) | Gets the up down left right glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.UpRightAndDownLeftFromCenter = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L170) | Gets the up right and down left from center glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.VectorSquare = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L174) | Gets the vector square glyph used by standard Editor controls. | — | — | +| [`public const string Inno.Editor.ImGui.ImGuiIcon.Xmark = ""`](../../src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs#L178) | Gets the xmark glyph used by standard Editor controls. | — | — | +| [`public sealed class Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L10) | Provides reusable editor controls and rendering helpers built on the native ImGui API. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CollapsingCard(string id, string title, System.Action? drawLeadingControl = null, System.Action? drawTrailingControl = null, bool defaultOpen = true, bool dimmed = false, float trailingControlWidth = 0, System.Action? drawContextMenu = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L42) | Draws a component-style collapsible card header.
`id`: The stable card identifier.
`title`: The visible card title.
`drawLeadingControl`: An optional control drawn before the title.
`drawTrailingControl`: An optional control aligned to the right edge.
`defaultOpen`: Whether the card starts expanded.
`dimmed`: Whether header controls and title use the inactive text color.
`trailingControlWidth`: The optional width reserved for the trailing control group.
`drawContextMenu`: An optional callback that binds a context menu to the complete header item. | when card content should be drawn; otherwise, . | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawDisclosureIndicator(System.Numerics.Vector2 min, System.Numerics.Vector2 max, bool open, bool dimmed = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L195) | Draws a vertically centered disclosure triangle with button-style hover feedback.
`min`: The minimum screen coordinate of the indicator area.
`max`: The maximum screen coordinate of the indicator area.
`open`: Whether the represented content is expanded.
`dimmed`: Whether the indicator uses the inactive text color. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CardBody(string id, System.Action drawContent, bool dimmed = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs#L238) | Draws expanded collapsible-card content inside a framed body.
`id`: The stable card identifier.
`drawContent`: The callback that draws card content.
`dimmed`: Whether content is visually disabled and non-interactive. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClippedRows(int itemCount, float itemHeight, TState state, System.Action drawRange, System.ReadOnlySpan preservedRows = default(System.ReadOnlySpan))`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Clipping.cs#L42) | Draws visible row ranges and preserves native scrolling, navigation and table cursor bookkeeping.
`itemCount`: The nonnegative count in the caller's current filtered and sorted view.
`itemHeight`: The complete fixed row advance in screen pixels, or -1 to measure the first row through the native clipper.
`state`: Data borrowed only for this call; it is never stored by the widget.
`drawRange`: A synchronous drawing callback receiving inclusive start and exclusive end indices.
`preservedRows`: Optional rows that must still submit an active edit, drag source or requested focus.
`TState`: Call-scoped drawing data, passed directly so static callbacks need no captured closure. | — | `System.ArgumentNullException`: The drawing callback is null.
`System.ArgumentOutOfRangeException`: A count, row height or preserved index is invalid.
`System.InvalidOperationException`: No native frame is active in this UI session. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuPopup(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L31) | Begins an explicitly opened popup using the editor context-menu presentation contract. The popup stays in its parent viewport, sizes itself to submitted content, and scrolls when its work-area bound is reached.
`id`: The stable identifier previously passed to ImGui when opening the popup. | when popup content should be submitted; otherwise, . | `System.ArgumentException`: Thrown when is empty. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L47) | Ends a popup opened by and restores the previous style. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuTooltip()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L61) | Begins a parent-viewport tooltip using the same padding, colors, border, and spacing as editor menus. | when tooltip content should be submitted; otherwise, . | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuTooltip()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L75) | Ends a tooltip opened by and restores the previous style. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawItemTooltip(string? text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L88) | Draws a consistently sized, wrapped editor tooltip for the most recently submitted item.
`text`: Tooltip text. Empty values do not draw a tooltip. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DrawTooltip(string? text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L105) | Draws the standard viewport-clamped tooltip when a custom-drawn canvas element is hovered.
`text`: Tooltip contents. The caller owns hit testing; empty text draws nothing. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginContextMenu(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L161) | Begins a parent-viewport styled right-click context menu for the most recently submitted item.
`id`: The stable popup identifier in the current ImGui ID scope. | when context-menu content should be drawn; otherwise, . | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginWindowContextMenu(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L183) | Begins a parent-viewport styled right-click context menu for the current window's unoccupied background.
`id`: The stable popup identifier in the current ImGui ID scope. | when context-menu content should be drawn; otherwise, . | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndContextMenu()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs#L202) | Ends a context menu opened by or . | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetLabelChipSize(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L24) | Gets the layout size of a compact colored label chip using the centralized editor style.
`label`: Visible label text. | The size reserved by for the supplied text. | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.LabelChip(string label, System.Numerics.Vector4 background)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L44) | Draws non-interactive text centered on a compact, softly rounded colored background.
`label`: Visible label text.
`background`: Background color drawn behind the label. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetTypeBadgeSize(string label)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L71) | Gets the layout size of a compact outlined type badge.
`label`: Visible type name. | The size reserved by . | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TypeBadge(string label, System.Numerics.Vector4 accent, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L94) | Draws a compact, non-interactive type badge with a subdued fill and semantic outline.
`label`: Visible type name.
`accent`: Semantic type-family accent.
`tooltip`: Optional explanation shown while the badge is hovered. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactCheckbox(string id, ref bool value, float size = -1, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L134) | Draws a compact checkbox whose checked fill uses the current text color.
`id`: Stable control identifier.
`value`: Mutable checked state.
`size`: Visual square size in pixels.
`tooltip`: Optional explanation displayed while the checkbox is hovered. | when the value changed. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Checkbox(string label, ref bool value, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L202) | Draws a standard checkbox with the shared editor tooltip behavior.
`label`: Visible label and stable ImGui identifier.
`value`: Mutable checked state.
`tooltip`: Optional explanation displayed while the checkbox is hovered. | when the value changed. | `System.ArgumentException`: Thrown when is empty or whitespace. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredButton(string label, float topPadding = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L226) | Draws a horizontally centered button with optional space above it.
`label`: Visible label and optional ImGui identifier suffix.
`topPadding`: Additional vertical space above the button. | when the button is pressed. | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredProgressBar(float fraction, System.Numerics.Vector2 size, string overlay)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs#L263) | Draws a progress bar whose overlay remains centered over the complete bar.
`fraction`: The completed fraction rendered by the progress bar.
`size`: The requested ImGui progress-bar size.
`overlay`: The text drawn at the geometric center of the complete progress bar. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropSource(string payloadType, in TPayload payload, System.Action? drawPreview = null, bool allowHoldToOpenOthers = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L37) | Publishes an unmanaged drag payload for the most recently submitted item.
`payloadType`: Stable ImGui payload type identifier.
`payload`: Payload value.
`drawPreview`: Optional preview drawing callback.
`allowHoldToOpenOthers`: Whether hovering over another openable control may open it after the native drag-hold delay.
`TPayload`: Unmanaged payload type. | while the item is an active drag source. | `System.ArgumentException`: Thrown when is empty or whitespace. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropSource(string payloadType, System.Func payloadFactory, System.Action? drawPreview = null, bool allowHoldToOpenOthers = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L85) | Publishes a lazily created unmanaged drag payload for the most recently submitted item.
`payloadType`: Stable ImGui payload type identifier.
`payloadFactory`: Creates the payload only after dragging starts.
`drawPreview`: Optional preview drawing callback.
`allowHoldToOpenOthers`: Whether hovering over another openable control may open it after the native drag-hold delay.
`TPayload`: Unmanaged payload type. | while the item is an active drag source. | `System.ArgumentException`: Thrown when is empty or whitespace.
`System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L123) | Accepts an unmanaged payload on the most recently submitted item.
`payloadType`: Stable ImGui payload type identifier.
`payload`: Delivered payload value.
`TPayload`: Unmanaged payload type. | only when a compatible payload is delivered. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload, out bool isPreviewing) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L150) | Accepts an unmanaged payload and reports its preview state on the most recently submitted item.
`payloadType`: Stable ImGui payload type identifier.
`payload`: Previewed or delivered payload value.
`isPreviewing`: Whether a compatible payload is hovering over the target.
`TPayload`: Unmanaged payload type. | only when a compatible payload is delivered. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, out TPayload payload, out bool isPreviewing, bool drawDefaultHighlight) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L181) | Accepts an unmanaged payload and controls whether ImGui draws its default target rectangle.
`payloadType`: Stable ImGui payload type identifier.
`payload`: Previewed or delivered payload value.
`isPreviewing`: Whether a compatible payload is hovering over the target.
`drawDefaultHighlight`: Whether ImGui draws its default target rectangle.
`TPayload`: Unmanaged payload type. | only when a compatible payload is delivered. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DragDropTarget(string payloadType, System.Numerics.Vector2 minimum, System.Numerics.Vector2 maximum, uint targetId, out TPayload payload, out bool isPreviewing, bool drawDefaultHighlight = true) where TPayload : unmanaged`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs#L243) | Accepts an unmanaged payload on an explicit screen-space rectangle.
`payloadType`: Stable ImGui payload type identifier.
`minimum`: Minimum screen coordinate of the complete target.
`maximum`: Maximum screen coordinate of the complete target.
`targetId`: ImGui identifier unique within the current window.
`payload`: Previewed or delivered payload value.
`isPreviewing`: Whether a compatible payload is hovering over the target.
`drawDefaultHighlight`: Whether ImGui draws its default target rectangle.
`TPayload`: Unmanaged payload type. | only when a compatible payload is delivered. | `System.ArgumentException`: Thrown when is empty or whitespace. | +| [`public string? Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.editMergeKey { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L20) | Gets the current continuous-edit merge boundary, or null for a discrete edit. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginPropertyEdit(Inno.Editor.Interactions.IEditorHistory history, string path, string name)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L38) | Starts a History boundary only when a changed control is actively producing continuous input.
`history`: The authoritative domain History receiving the control's existing mutation records.
`path`: The stable owner-qualified property path.
`name`: The operation name shown by Undo and Redo. | — | `System.InvalidOperationException`: A previous gesture cannot complete or History rejects the transaction. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginEdit(Inno.Editor.Interactions.IEditorHistory history, string path, string name)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L62) | Begins or touches an explicitly captured continuous edit, such as a viewport manipulation.
`history`: The authoritative domain History.
`path`: The stable owner-qualified edit identity.
`name`: The user-facing Undo name. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.IsEditing(string path)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L92) | Checks whether this owner still owns a particular edit, including after a close or generation boundary.
`path`: The stable edit identity. | True only while the matching owner still has an active transaction. | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompletePropertyEdit(string path)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L100) | Observes the current property after drawing, including release and cancellation without a new value sample.
`path`: The owner-qualified path passed to the property edit boundary. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CommitEdits()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L119) | Completes the current control edit before a document command, reload, Play transition or shutdown. | — | `System.InvalidOperationException`: A nested transaction prevents completion; ownership is retained. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CancelEdits()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs#L135) | Restores the control's initial domain values before discarding its transient presentation state. | — | `System.InvalidOperationException`: Rollback is unavailable; the gesture remains owned for recovery. | +| [`public Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TransformGizmo(Inno.Core.Mathematics.Matrix world, Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions options)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L146) | Draws a transform handle using stack-owned ABI buffers and restores native global style and masks before returning.
`world`: The current world matrix, copied into the native call.
`options`: The explicit viewport and interaction policy. | Copied output with no native pointer or domain object. | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HelpBox(string text, string icon, System.Numerics.Vector4 color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L25) | Draws a wrapped contextual message with a semantic icon and a subdued status surface.
`text`: Literal message shown within the current content width.
`icon`: Icon-font glyph identifying the message severity.
`color`: Semantic accent for the icon, outline, and leading stripe. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CollectionSectionHeader(string title)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L75) | Draws a square collection-section band whose background, separator, and padding are shared with hierarchy and Inspector presentation.
`title`: Literal section title. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SectionLayout(System.Action drawContent)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L110) | Draws content in a scope where consecutive section headers become framed fieldsets.
`drawContent`: Callback that draws all content participating in the section layout. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.isSectionContentVisible { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L135) | Gets whether content belonging to the current Inspector section should be drawn. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EnsureSection(string title = "Properties", string? description = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L153) | Ensures that subsequently drawn default properties belong to a framed section.
`title`: Title used only when the current section layout has not declared a section yet.
`description`: Optional hover explanation for the implicit section title. | when the current section accepts content; otherwise, when that section is collapsed. | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SectionHeader(string title, string? description = null, System.Action? drawLeadingControl = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs#L184) | Draws a section heading with hover-only description using the shared Inspector presentation.
`title`: Section title.
`description`: Optional hover explanation.
`drawLeadingControl`: Optional interactive control drawn before the section title. | when the section content should be drawn; otherwise, when the section is collapsed. | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.InlineRename(string id, ref string text, ref bool requestFocus, float rowHeight, nuint capacity = 512, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L44) | Draws a compact single-line rename editor inside an existing row.
`id`: The stable control identifier.
`text`: The mutable text buffer.
`requestFocus`: Whether keyboard focus and complete-value selection should be requested. The value remains until the input becomes active and its current contents are selected.
`rowHeight`: The height of the row area in which the field is centered.
`capacity`: The maximum UTF-8 buffer capacity.
`width`: The control width, or a negative value to fill the available space. | The current rename outcome. | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentOutOfRangeException`: Thrown when is not finite or is not positive. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.isMixedValue { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.MixedValue.cs#L16) | Gets whether the currently drawn control group represents differing selected values. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.MixedValue(bool mixed, System.Action draw)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.MixedValue.cs#L27) | Draws one control group with the native mixed-value state, restoring the previous item flags on failure.
`mixed`: Whether the current selection contains different values.
`draw`: Synchronous control drawing; never retained. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PanelWindow(string title, ref bool isOpen, System.Action drawBody, Inno.Editor.ImGui.ImGuiWindowFlags flags = Inno.Editor.ImGui.ImGuiWindowFlags.NoCollapse, bool useWindowPadding = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L32) | Opens a standard panel window and executes panel body.
`title`: Window title.
`isOpen`: Visible state.
`drawBody`: Panel body callback.
`flags`: Window flags.
`useWindowPadding`: Whether the panel body should use the current standard window padding. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ConstrainedContent(string id, System.Action drawContent, bool useWindowPadding = true)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L112) | Draws a vertically auto-sized content region that is constrained to the current available width and cannot create an independent scroll range.
`id`: Stable identifier used by ImGui to track the content region.
`drawContent`: Callback that draws the complete region contents.
`useWindowPadding`: Whether the region should apply the centralized standard window padding exactly once. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HeaderSurface(string id, System.Action drawContent, bool spanWindowPadding = false)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L159) | Draws a square-cornered editor header surface using the shared target-header palette, border, and padding.
`id`: Stable identifier used by ImGui to track the header region.
`drawContent`: Callback that draws the complete header content.
`spanWindowPadding`: Whether the header should extend through the current parent window padding to both edges. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Hint(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs#L331) | Draws disabled hint text that wraps to the current content width.
`text`: Hint text. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyRow(string id, string label, System.Action drawValue, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L35) | Draws a two-column property row with a stable internal identifier.
`id`: Stable row identifier.
`label`: Human-readable property label.
`drawValue`: Value control callback.
`tooltip`: Optional hover help displayed from the property label. | — | `System.ArgumentException`: Thrown when is empty or whitespace.
`System.ArgumentNullException`: Thrown when or is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyRow(string id, System.Action drawLabel, System.Action drawValue)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L64) | Draws a two-column property row whose label is supplied by a custom presentation callback.
`id`: Stable row identifier.
`drawLabel`: Label presentation callback. It may wrap or draw badges inside the label column.
`drawValue`: Value control callback. | — | `System.ArgumentException`: Thrown when is empty or whitespace.
`System.ArgumentNullException`: Thrown when or is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetupPropertyColumns()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L100) | Configures the label and input columns of an active table with a shared two-to-three ratio. A following fixed-width action column may be added by the caller. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.PropertyLabel(string label, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L119) | Draws a label within its current table cell, wrapping when the column is narrow.
`label`: Visible label text.
`tooltip`: Optional explanation shown while the label is hovered. | — | `System.ArgumentNullException`: Thrown when is null. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.MetadataValue(string metadata, System.Action drawValue, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L146) | Draws a subdued metadata prefix followed by an interactive value on the same line.
`metadata`: Short contextual text such as a declared value type.
`drawValue`: Callback that draws the value or action following the metadata.
`tooltip`: Optional explanation displayed while the metadata is hovered. | — | `System.ArgumentNullException`: Thrown when or is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.MetadataValue(string metadata, string value, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L180) | Draws one wrapped, subdued value whose metadata and content remain in the value column.
`metadata`: Short contextual text such as a declared value type.
`value`: Literal value description shown after the metadata separator.
`tooltip`: Optional explanation displayed while the combined value is hovered. | — | `System.ArgumentNullException`: Thrown when or is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AxisDragFloat(string id, string axis, ref float value, float width, float speed = 0.1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L221) | Draws a float drag field with a compact colored axis prefix.
`id`: Stable control identifier.
`axis`: Axis label such as X, Y, Z, or W.
`value`: Mutable numeric value.
`width`: Total width including the axis prefix.
`speed`: Drag speed. | when the value changed. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactDragFloat(string label, ref float value, float speed = 0.1, float? minimum = null, float? maximum = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L254) | Draws a floating-point drag field with a compact one-decimal presentation and precise text editing.
`label`: Stable ImGui label and identifier.
`value`: Exact floating-point value to edit. Compact presentation never rounds the stored value.
`speed`: Drag speed.
`minimum`: Optional inclusive lower bound.
`maximum`: Optional inclusive upper bound. | when the exact value changed. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CompactSliderFloat(string label, ref float value, float minimum, float maximum)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L288) | Draws a bounded slider with compact presentation and a double-click precise input mode.
`label`: Stable ImGui label and identifier.
`value`: Exact floating-point value to edit. Compact presentation never rounds the stored value.
`minimum`: Inclusive lower bound.
`maximum`: Inclusive upper bound. | when the exact value changed. | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AxisDragInt(string id, string axis, ref int value, float width, float speed = 1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L328) | Draws an integer drag field with a compact colored axis prefix.
`id`: Stable control identifier.
`axis`: Axis label such as X, Y, Z, or W.
`value`: Mutable numeric value.
`width`: Total width including the axis prefix.
`speed`: Drag speed. | when the value changed. | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Disabled(bool disabled, System.Action draw)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L349) | Draws content inside an ImGui disabled scope when requested.
`disabled`: Whether interaction is disabled.
`draw`: Drawing callback. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.InsertionLine(float fromX, float toX, float y)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L378) | Draws a horizontal insertion marker above normal window content in screen coordinates.
`fromX`: Marker start X coordinate.
`toX`: Marker end X coordinate.
`y`: Marker Y coordinate. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.DropTargetHighlight(System.Numerics.Vector2 min, System.Numerics.Vector2 max)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs#L398) | Draws the standard yellow rectangular drag-and-drop target highlight above all normal window content in screen coordinates.
`min`: Minimum target coordinate.
`max`: Maximum target coordinate. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SearchInput(string id, string hint, ref string query, nuint capacity = 256, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L35) | Draws a single-line search field with a stable identifier.
`id`: The stable control identifier.
`hint`: The hint shown while the query is empty.
`query`: The mutable search query.
`capacity`: The maximum UTF-8 buffer capacity.
`width`: The control width, or a negative value to fill the available space. | when the query changed; otherwise, . | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginSearchPopup(string id, ref string query, string hint, nuint capacity = 256, float width = -1)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L69) | Begins a popup and draws its focused search field.
`id`: The popup identifier previously supplied to ImGui.
`query`: The mutable search query.
`hint`: The hint shown while the query is empty.
`capacity`: The maximum UTF-8 buffer capacity.
`width`: The search field width, or a negative value to use the editor default. | when popup content should be drawn; otherwise, . | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndSearchPopup()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs#L92) | Ends a popup opened by . | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SelectTarget(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Core.Identity.IdentityObject target, System.Collections.Generic.IReadOnlyList visibleTargets)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L28) | Applies ordinary, additive or visible-range selection using the active keyboard modifiers.
`interactions`: The session's authoritative interaction service.
`target`: The freshly resolved clicked target.
`visibleTargets`: Current runtime-identity view order; collapsed or filtered items must be omitted. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.NavigateList(Inno.Editor.Interactions.EditorInteractions interactions, int itemCount, int focusedIndex, int pageSize = 10)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L74) | Reads one focused list's keyboard navigation without retaining its entries or changing domain selection.
`interactions`: The authoritative interaction owner supplying the product modifier policy.
`itemCount`: The number of currently visible, ordered entries.
`focusedIndex`: The current visible focus index, or minus one when absent.
`pageSize`: The positive number of visible rows advanced by Page Up or Page Down. | Neutral focus and selection intents; text editing, active controls and popups produce no intent. | `System.ArgumentNullException`: The interaction owner is null.
`System.ArgumentOutOfRangeException`: The count, focus index or page size is invalid.
`System.InvalidOperationException`: No matching owner-thread drawing session is active. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo(string id, string preview, Inno.Editor.ImGui.ImGuiComboFlags flags = Inno.Editor.ImGui.ImGuiComboFlags.None)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L37) | Begins a combo whose popup opens below the control and stays within its containing window.
`id`: Stable combo identifier in the current ImGui scope.
`preview`: Text displayed by the closed combo.
`flags`: Native combo presentation flags. Only is supported. | when the combo popup is open and its contents should be submitted; otherwise, . | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is null.
`System.ArgumentOutOfRangeException`: Thrown when contains a presentation mode unsupported by the editor selector. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L55) | Ends a combo opened by . | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginMenuSelector(string id, string preview, float width, float minimumPopupWidth)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L84) | Draws a compact selector control and begins a work-area-bounded menu popup.
`id`: Stable selector identifier in the current ImGui scope.
`preview`: Text displayed by the closed selector.
`width`: Width reserved for the selector control.
`minimumPopupWidth`: Minimum outer width of the popup, including its window padding. | when the popup is open and its contents should be submitted; otherwise, . | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is .
`System.ArgumentOutOfRangeException`: Thrown when or is not positive. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndMenuSelector()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs#L122) | Ends a selector popup opened by . | — | — | +| [`public Inno.Editor.ImGui.EditorStyleMetrics Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.style { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs#L17) | Gets the centralized editor layout metrics shared by every widget and feature panel. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetupStyle()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs#L22) | Applies the centralized editor layout metrics and palette to the current ImGui context. | — | — | +| [`public float Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetIconCatalogWidth()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L21) | Measures the standard glyph column once per native frame in this UI session. | The widest visible standard glyph at the current font size, at least one text-line height. | — | +| [`public System.Numerics.Vector4 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetGlyphVisualBounds(float fontSize, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L55) | Measures a glyph using the current font without exposing its native font handle.
`fontSize`: The positive baked font size.
`text`: A nonempty string whose first Unicode scalar identifies the glyph. | The glyph's visible left, top, right and bottom offsets. | `System.ArgumentException`: The glyph text is empty. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.AddGlyphCentered(float fontSize, string text, System.Numerics.Vector2 center, uint color)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L78) | Centers a glyph in the current window without exposing a native draw list.
`fontSize`: The positive baked font size.
`text`: The glyph text.
`center`: The requested visible center in screen coordinates.
`color`: Packed text color. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableText(string id, string text, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L182) | Draws clickable text without a persistent or hovered background.
`id`: Stable identifier used by ImGui to track the interaction.
`text`: Visible text or icon glyph.
`tooltip`: Optional tooltip displayed while the interaction is hovered. | when the text is pressed. | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.HoverText(string id, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L205) | Draws a text link with no button background and highlights it only through text presentation.
`id`: Stable identifier used by ImGui to track the interaction.
`text`: Visible link text. | when the link is pressed. | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableIcon(string id, string icon, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L252) | Draws a clickable icon inside the same square interaction slot used by editor close controls. The icon has no resting background and changes only its glyph color while hovered or active.
`id`: Stable identifier used by ImGui to track the interaction.
`icon`: The visible icon glyph.
`tooltip`: Optional tooltip displayed while the interaction is hovered. | when the icon is pressed. | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ClickableText(string id, string text, System.Numerics.Vector2 controlSize, string? tooltip = null)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L303) | Draws clickable text centered inside an explicitly sized transparent interaction area.
`id`: Stable identifier used by ImGui to track the interaction.
`text`: Visible text or icon glyph.
`controlSize`: Size of the transparent interaction area.
`tooltip`: Optional tooltip displayed while the interaction is hovered. | when the text is pressed. | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is .
`System.ArgumentOutOfRangeException`: Thrown when either component of is not positive. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredText(string text, System.Numerics.Vector2 areaSize)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L347) | Draws non-interactive text centered inside a reserved layout area.
`text`: Visible text.
`areaSize`: Size of the layout area that contains the text. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentOutOfRangeException`: Thrown when either component of is not positive. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.CenteredWrappedText(string text, System.Numerics.Vector2 areaSize, System.Numerics.Vector2 padding)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L382) | Draws wrapped non-interactive text as a centered block inside a padded layout area.
`text`: The literal text to wrap and draw.
`areaSize`: The complete area reserved by the widget.
`padding`: The minimum horizontal and vertical distance retained from the area edges whenever space permits. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentOutOfRangeException`: Thrown when an area component is not positive or a padding component is negative. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.ColoredText(System.Numerics.Vector4 color, string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L433) | Draws unformatted text with a temporary foreground color.
`color`: The foreground color applied only while drawing the text.
`text`: The literal text to draw without format-string interpretation. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.WrappedText(string text)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L460) | Draws literal text wrapped to the remaining content width without using a native variadic formatting entry point.
`text`: The literal text to draw. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetClickableTextSize(string text, System.Numerics.Vector2 padding)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L493) | Calculates a clickable text area from visible text and requested inner padding.
`text`: Visible text whose dimensions determine the content size.
`padding`: Horizontal and vertical padding surrounding the text. | The complete transparent interaction area size. | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentOutOfRangeException`: Thrown when either component of is negative. | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetCompactClickableTextSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L514) | Gets the compact fixed interaction size used by icon-style clickable text. | A fixed-size area that follows the editor icon slot convention. | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.GetCompactIconSize()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L527) | Gets the square icon interaction size shared by dock-header close controls and compact editor icons. | A square size based on the current ImGui font size. | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.IconText(string icon, string text, bool highlight)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs#L560) | Draws icon and text with the icon's visible glyph bounds centered in a slot that expands when the glyph is wider than the normal editor icon slot.
`icon`: Icon text.
`text`: Main text.
`highlight`: Whether to underline and emphasize the drawn icon and text. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.TreeNode(string id, System.Action onDraw, in Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions options)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L42) | Draws a full-width interactive tree row whose non-leaf content toggles expansion when double-clicked, while preserving single-click disclosure-arrow behavior.
`id`: Stable row identifier.
`onDraw`: Frame-local content drawing for visible or explicitly preserved rows; clipped ordinary rows do not invoke it.
`options`: Tree row options. | Interaction and row geometry for the submitted item. | `System.ArgumentOutOfRangeException`: An explicit flattened depth is negative. | +| [`public ulong Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.treeExpansionRevision { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L70) | Gets this window and owner's expansion change counter for invalidating neutral flattened views. | — | `System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.IsTreeNodeOpen(string id)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L94) | Reads a retained expansion value without submitting a native row or retaining domain objects.
`id`: The stable row identifier within this window and owner. | True when the row was explicitly expanded; unknown rows are collapsed. | `System.ArgumentException`: The row identifier is empty or whitespace.
`System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetTreeNodeOpen(string id, bool open)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L116) | Changes retained expansion before a flattened view is computed, without submitting a row.
`id`: The stable row identifier within this window and owner.
`open`: The requested disclosure state. | — | `System.ArgumentException`: The row identifier is empty or whitespace.
`System.InvalidOperationException`: No valid drawing session or native frame is active. | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.SetNextTreeNodeOpen(bool open)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L261) | Overrides the retained expansion state of the next submitted non-leaf tree row.
`open`: Whether the next row should be expanded. | — | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Trend(string label, System.ReadOnlySpan values, int offset = 0)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Trend.cs#L34) | Draws a bounded line plot from borrowed numeric samples without copying their buffer.
`label`: The visible plot title and stable item identifier in its current owner.
`values`: Finite samples borrowed only for this call; an empty span draws an empty plot.
`offset`: The first chronological sample in a circular buffer, or zero for empty input. | — | `System.ArgumentException`: The label is empty or a sample is not finite.
`System.ArgumentOutOfRangeException`: The offset is outside the supplied sample span.
`System.InvalidOperationException`: No matching owner-thread drawing session is active. | +| [`public enum Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L14) | Chooses the transform handles rendered by the viewport presenter. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.Translate = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L19) | Renders translation handles. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.Rotate = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L23) | Renders rotation handles. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleX = 4`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L27) | Renders the X scale handle. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleY = 8`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L31) | Renders the Y scale handle. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleZ = 16`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L35) | Renders the Z scale handle. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.Scale = Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleX \| Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleY \| Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation.ScaleZ`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L39) | Renders all local scale handles. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiWidget.TransformGizmoSpace : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L45) | Chooses how the manipulation axes are oriented. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoSpace.Local = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L50) | Uses the object's local orientation. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.TransformGizmoSpace.World = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L54) | Uses the world's orientation. | — | — | +| [`public readonly record struct Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Supplies frame-local matrices and interaction policy without native handles. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.minimum { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L65) | Gets the viewport's screen-space minimum. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.maximum { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L69) | Gets the viewport's screen-space maximum. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.view { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L73) | Gets the column-vector view matrix. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.projection { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L77) | Gets the projection matrix supplied by the viewport owner. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.orthographic { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L81) | Gets whether this viewport uses an orthographic projection. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.enabled { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L85) | Gets whether manipulation can consume pointer input. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.operation { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L89) | Gets the requested handles. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.TransformGizmoSpace Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.space { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L93) | Gets the handle coordinate space. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.maskX { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L97) | Gets whether the X translation axis is excluded by the viewport plane. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.maskY { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L101) | Gets whether the Y translation axis is excluded by the viewport plane. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.maskZ { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L105) | Gets whether the Z translation axis is excluded by the viewport plane. | — | — | +| [`public override string Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.ToString()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.operator !=(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions left, Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.operator ==(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions left, Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions.Equals(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOptions other)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L60) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly record struct Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Returns copied manipulation output; the domain owner decides how to apply and record it.
`matrix`: The resulting world matrix.
`changed`: Whether the native presenter reported an edited value.
`isUsing`: Whether the handle currently captures the pointer.
`isOver`: Whether the pointer is over a handle. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.TransformGizmoResult(Inno.Core.Mathematics.Matrix matrix, bool changed, bool isUsing, bool isOver)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Returns copied manipulation output; the domain owner decides how to apply and record it.
`matrix`: The resulting world matrix.
`changed`: Whether the native presenter reported an edited value.
`isUsing`: Whether the handle currently captures the pointer.
`isOver`: Whether the pointer is over a handle. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.matrix { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L124) | The resulting world matrix. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.changed { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L125) | Whether the native presenter reported an edited value. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.isUsing { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L126) | Whether the handle currently captures the pointer. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.isOver { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L127) | Whether the pointer is over a handle. | — | — | +| [`public override string Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.ToString()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.operator !=(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult left, Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.operator ==(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult left, Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult right)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.Equals(Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult other)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.ImGui.ImGuiWidget.TransformGizmoResult.Deconstruct(out Inno.Core.Mathematics.Matrix matrix, out bool changed, out bool isUsing, out bool isOver)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs#L123) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L141) | Describes the view-owned geometry used to present an active inline rename action. | — | — | +| [`public Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.InlineRenamePresentation(string id, float width, float rowHeight, nuint bufferSize = 512)`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L165) | Creates inline rename presentation data.
`id`: The stable ImGui identifier used by the input field.
`width`: The requested input width in logical pixels.
`rowHeight`: The height of the row area in which the field is centered.
`bufferSize`: The maximum UTF-8 buffer size accepted by the input. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentOutOfRangeException`: Thrown when or is not finite and positive, or when is zero. | +| [`public string Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.id { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L197) | Gets the stable ImGui identifier used by the input field. | — | — | +| [`public float Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.width { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L201) | Gets the requested input width in logical pixels. | — | — | +| [`public float Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.rowHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L205) | Gets the height of the row area in which the input field is centered. | — | — | +| [`public nuint Inno.Editor.ImGui.ImGuiWidget.InlineRenamePresentation.bufferSize { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L209) | Gets the maximum UTF-8 buffer size accepted by the input. | — | — | +| [`public enum Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult : int`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L215) | Describes the outcome of an inline rename control. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.None = 0`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L220) | Indicates that the interaction remains active. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.Commit = 1`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L224) | Indicates that the edited text should be committed. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.FocusLost = 2`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L228) | Indicates that the input lost focus and its valid value should be committed before closing. | — | — | +| [`Inno.Editor.ImGui.ImGuiWidget.InlineRenameResult.Cancel = 3`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs#L232) | Indicates that the edited text should be discarded. | — | — | +| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L166) | Carries frame-local list navigation intents over visible indices without owning objects or selection state. | — | — | +| [`public int Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.focusedIndex { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L191) | Gets the resulting visible focus index, or minus one for no focus. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.focusChanged { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L195) | Gets whether the visible focus index changed. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.selectionRequested { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L199) | Gets whether the caller should apply a domain selection operation. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.extendRange { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L203) | Gets whether the selection should extend from its existing anchor. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.additive { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L207) | Gets whether the product's primary modifier requests preserving other selected entries. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.toggle { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L211) | Gets whether Space with the primary modifier requests toggling the focused entry. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.activate { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L215) | Gets whether Enter requests activation of the focused entry. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult.cancel { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs#L219) | Gets whether Escape requests canceling the current list workflow. | — | — | +| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeDrawContext`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L464) | Provides the native geometry of a tree row while its custom content is being drawn. | — | — | +| [`public float Inno.Editor.ImGui.ImGuiWidget.TreeNodeDrawContext.rowHeight { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L474) | Gets the actual native row height established for the current tree node. | — | — | +| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L480) | Configures an interactive tree row. | — | — | +| [`public int? Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.depth { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L486) | Gets explicit indentation for a flattened row, or null for a nested native tree. Explicit-depth rows never push a native tree scope; callers must not call TreePop for them. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.requestFocus { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L490) | Gets whether this row should receive keyboard focus during this submission when its tree window is focused. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.keepContentActive { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L495) | Gets whether an active edit requires content submission even while its row is clipped. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.selected { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L500) | Gets whether the row is selected. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.isLeaf { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L504) | Gets whether the row has no expandable children. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.showBackground { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L508) | Gets whether a custom background is drawn behind an unselected row. | — | — | +| [`public System.Numerics.Vector4 Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.backgroundColor { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L512) | Gets the custom background color used when is enabled. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.suppressHoverHighlight { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L516) | Gets whether the row keeps its configured background while hovered. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.hideGuideLines { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L520) | Gets whether ancestor and branch guide lines are omitted for this row. | — | — | +| [`public System.Action? Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions.drawViewportOverlay { get; init; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L525) | Gets the callback that draws controls fixed to the current viewport without extending the tree's horizontal content boundary. | — | — | +| [`public readonly struct Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L531) | Describes interaction state produced by a tree row. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isSubmitted { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L556) | Gets whether row controls were submitted; ordinary clipped rows are false, while preserved active edits remain true. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isOpen { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L561) | Gets whether child content should be rendered. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isClicked { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L565) | Gets whether the content row was clicked. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isDoubleClicked { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L570) | Gets whether the content row was double-clicked. Double-clicking a non-leaf content row also toggles its retained expansion state for the next frame. | — | — | +| [`public bool Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.isHovered { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L574) | Gets whether the full row is hovered. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.min { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L578) | Gets the row minimum screen coordinate. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.max { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L582) | Gets the row maximum screen coordinate. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult.contentMin { get; }`](../../src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs#L586) | Gets the minimum screen coordinate of the row's interactive content, excluding tree indentation. | — | — | ## 项目依赖 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):公开引用边界由实际签名核对。 -- [Inno.Core.Events](../core/Inno.Core.Events.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Native.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Native.ImGuizmo` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Presentation.ImGui.Sdl3` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Core.Events` | 公开签名依赖 | +| `Inno.Core.Identity` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Core.Mathematics` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Inspection.md b/docs/editor/Inno.Editor.Inspection.md index 4f1559567..712327d49 100644 --- a/docs/editor/Inno.Editor.Inspection.md +++ b/docs/editor/Inno.Editor.Inspection.md @@ -54,6 +54,7 @@ public sealed class TextAssetDrawer : InspectionDrawer ```csharp using Inno.Editor.Inspection; +using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; public readonly record struct AuthoringWeight(float value); @@ -64,7 +65,7 @@ public sealed class AuthoringWeightDrawer : IPropertyDrawer public void Draw(PropertyDrawContext context) { float value = context.GetValue() is AuthoringWeight current ? current.value : 0f; - if (ImGuiWidget.CompactSliderFloat($"##{context.path}", ref value, 0f, 1f)) + if (EditorUiSession.current.widgets.CompactSliderFloat($"##{context.path}", ref value, 0f, 1f)) context.SetValue(new AuthoringWeight(value)); } } @@ -72,7 +73,7 @@ public sealed class AuthoringWeightDrawer : IPropertyDrawer PropertyDrawer 通过 declared property type 匹配。`PropertyDrawContext.SetValue` 会把修改交给所属 feature 的 edit service,由它写入中立的 Undo/Redo payload;Drawer 不应该绕过 context 直接修改 serialized owner。`PropertyDrawContext.tooltip` 暴露已经组合完成的 hover help;bool 与 nullable 内建 Drawer 会把它同时绑定到 checkbox 本身,因此 label 和 control 都能解释同一属性。 -`SerializedPropertyRenderer` 本身不依赖 Scene。创建 renderer 的 feature 提供 `IInspectionPropertyEditService`,负责把通用的 owner、root property 与 mutation 转换成自己的 Undo/Redo 协议。Inspector Panel 使用 Scene adapter;未来 Material、Animation 或 RenderGraph 检查器可以使用各自的 history adapter,而不用把 Scene 引入通用 Inspection 项目。 +`SerializedPropertyRenderer` 本身不依赖 Scene。创建 renderer 的 feature 提供 `IInspectionPropertyEditService`,负责把通用的 owner、root property 与 mutation 转换成自己的 Undo/Redo 协议。Inspector Panel 使用 Scene adapter;Material、Pipeline 和插件检查器使用各自的 history adapter,而不用把 Scene 引入通用 Inspection 项目。 ## Inspector presentation attributes @@ -133,7 +134,7 @@ Inspector 的浮点标量以及 `Vector2/3/4`、`Rect`、Quaternion Euler 和 Tr ## Feature 间 presentation 契约 -`IInspectionIconProvider` 用于共享目标所属 feature 的图标规则,而不引入 Panel→Panel 引用。例如 FileBrowser 的 `AssetEditorModule` 实现 `IInspectionIconProvider`,它自己的 `AssetSelectionInspectionDrawer` 因而可以复用同一个 Asset icon registry。Inspector 组合根只依赖该接口,不引用 FileBrowser 项目。Scene/GameObject Drawer 直接注入 `EditorSettings`,通过原始完整路径读取与 Hierarchy、Asset Browser 一致的 icon object;Settings 项目不提供 icon resolver。 +`IInspectionIconProvider` 用于共享目标所属 feature 的图标规则,而不引入 Panel→Panel 引用。例如 FileBrowser 的 `AssetEditingModule` 实现 `IInspectionIconProvider`,它自己的 `AssetSelectionInspectionDrawer` 因而可以复用同一个 Asset icon registry。Inspector 组合根只依赖该接口,不引用 FileBrowser 项目。Scene/GameObject Drawer 直接注入 `EditorSettings`,通过原始完整路径读取与 Hierarchy、Asset Browser 一致的 icon object;Settings 项目不提供 icon resolver。 ## Scripting API @@ -141,76 +142,35 @@ EditorScripts 通过显式 `using InnoEditor.Inspection;` 使用裁剪后的 `In ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Inspection.AssetInspectionSelection` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.AssetInspectionSelection.AssetInspectionSelection(System.Collections.Generic.IEnumerable assetIds)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs#L18) | Captures unique persistent identities in selection order.
`assetIds`:Nonempty identities; the last identity is the primary selection. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Inspection.AssetInspectionSelection.assetIds { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs#L29) | Gets stable asset identities in selection order. | — | — | -| [`public System.Guid Inno.Editor.Inspection.AssetInspectionSelection.primaryAssetId { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs#L33) | Gets the most recently selected asset identity. | — | — | -| [`public sealed class Inno.Editor.Inspection.AssetInspectionSelection`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs#L10) | Represents an ordered multi-asset selection without retaining asset or plugin-generation objects. | — | — | - -### `Inno.Editor.Inspection.IInspectionDrawer` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`(string name, System.Action? setter) Inno.Editor.Inspection.IInspectionDrawer.BindName(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L159) | Binds the target name displayed by the Inspector target header.
返回:The current target name and a callback that accepts a requested replacement name, or null for the callback when the name is read-only.
`context`:The active Inspector drawing context. | — | — | -| [`bool Inno.Editor.Inspection.IInspectionDrawer.CanInspect(object target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L136) | Determines whether a type-compatible selected value can be inspected by this drawer.
返回:Whether this drawer accepts the value; ordinary type-only drawers accept by default.
`target`:Selected value, never mutated by this query. | — | — | -| [`void Inno.Editor.Inspection.IInspectionDrawer.Draw(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L175) | Draws the target-specific Inspector body.
`context`:The active Inspector drawing context. | — | — | -| [`void Inno.Editor.Inspection.IInspectionDrawer.DrawHeader(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L167) | Draws the target-specific second header row.
`context`:The active Inspector drawing context. | — | — | -| [`string Inno.Editor.Inspection.IInspectionDrawer.GetIcon(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L147) | Gets the icon glyph displayed in the Inspector target header.
返回:The icon glyph for the current target.
`context`:The active Inspector drawing context. | — | — | -| [`public interface Inno.Editor.Inspection.IInspectionDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L125) | Defines the runtime bridge used to render a discovered inspection drawer. | — | — | - -### `Inno.Editor.Inspection.IInspectionIconProvider`1` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`string Inno.Editor.Inspection.IInspectionIconProvider.GetIcon(TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/IInspectionIconProvider.cs#L20) | Resolves the presentation icon for one inspected target.
返回:The icon glyph registered by the owning feature.
`target`:The target whose icon should be resolved. | — | — | -| [`public interface Inno.Editor.Inspection.IInspectionIconProvider`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/IInspectionIconProvider.cs#L9) | Resolves a presentation icon for inspected targets owned by another editor feature. | — | — | - -### `Inno.Editor.Inspection.IInspectionPropertyEditService` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`bool Inno.Editor.Inspection.IInspectionPropertyEditService.ChangeProperty(object owner, string propertyName, System.Action mutation, string historyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs#L36) | Applies a property mutation and records it when the owning feature observes a value change.
返回:true when the value changed and the owning feature recorded the edit; otherwise, false.
`owner`:The domain object that owns the root serialized property.
`propertyName`:The exact root serialized member name.
`mutation`:The callback that assigns the requested value.
`historyName`:The user-facing name for the resulting history entry. | — | `System.ArgumentNullException`:Thrown when owner or mutation is null.
`System.ArgumentException`:Thrown when propertyName or historyName is empty. | -| [`public interface Inno.Editor.Inspection.IInspectionPropertyEditService`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs#L8) | Applies one root serialized-property mutation through the history mechanism owned by a feature. | — | — | - -### `Inno.Editor.Inspection.IInspectorAttributeDrawer` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.DrawAfter(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L34) | Draws a decorator immediately after the property row.
`context`:Current presentation state for the annotated property. | — | — | -| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.DrawBefore(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L24) | Draws a decorator immediately before the property row.
`context`:Current presentation state for the annotated property. | — | — | -| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.Update(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L14) | Updates visibility, interactivity, labeling, help, or numeric constraints before layout.
`context`:Mutable presentation state for the annotated property. | — | — | -| [`public interface Inno.Editor.Inspection.IInspectorAttributeDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L6) | Extends serialized-property presentation with reusable attribute-driven behavior. | — | — | - -### `Inno.Editor.Inspection.InspectionDrawContext` - + + + + + + + + + + + + + + + + + + + + + + + @@ -221,22 +181,6 @@ EditorScripts 通过显式 `using InnoEditor.Inspection;` 使用裁剪后的 `In - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Inspection.InspectionDrawContext.DrawProperties()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L128) | Draws every runtime-visible serialized property through the shared attribute and property drawer pipeline. | Properties without an explicit Header attribute are placed in the shared implicit Properties section. Custom drawers may call this method before or after their own sections. | — | -| [`public void Inno.Editor.Inspection.InspectionDrawContext.DrawProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L110) | Draws one named property with its ordinary editor, history, and validation behavior.
`propertyName`:Exact serialized property name. | — | — | -| [`public T Inno.Editor.Inspection.InspectionDrawContext.GetProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L94) | Reads one runtime-visible serialized property from the target.
返回:The current strongly typed property value.
`propertyName`:Exact serialized property name. | — | — | -| [`public bool Inno.Editor.Inspection.InspectionDrawContext.HasProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L75) | Gets whether the target exposes one runtime-visible serialized property.
返回:true when the property is available.
`propertyName`:Exact serialized property name. | — | — | -| [`public bool Inno.Editor.Inspection.InspectionDrawContext.TryDrawInline(object target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L48) | Draws an exact target-specific body inside the caller's existing Inspector card.
返回:true when an exact drawer rendered the target.
`target`:Nested target whose exact registered drawer should render. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Inspection.InspectionDrawContext.editorContext { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L22) | Gets the shared editor context. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Inspection.InspectionDrawContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L27) | Gets the active editor interaction entry point. | — | — | -| [`public Inno.Editor.Inspection.SerializedPropertyRenderer Inno.Editor.Inspection.InspectionDrawContext.properties { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L37) | Gets the serialized property renderer. | — | — | -| [`public object Inno.Editor.Inspection.InspectionDrawContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L32) | Gets the selected target. | — | — | -| [`public sealed class Inno.Editor.Inspection.InspectionDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L15) | Provides services and state to an inspector drawer. | — | — | - -### `Inno.Editor.Inspection.InspectionDrawer`1` - @@ -245,140 +189,32 @@ EditorScripts 通过显式 `using InnoEditor.Inspection;` 使用裁剪后的 `In - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Inspection.InspectionDrawer.InspectionDrawer()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L11) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract (string name, System.Action? setter) Inno.Editor.Inspection.InspectionDrawer.BindName(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L45) | Binds the name displayed in the first row of the Inspector target header.
返回:The current target name and a callback that accepts a requested replacement name, or null for the callback when the name is read-only.
`context`:The active Inspector drawing context.
`target`:The strongly typed target being inspected. | — | — | -| [`protected virtual bool Inno.Editor.Inspection.InspectionDrawer.CanInspect(TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L28) | Determines whether this drawer handles the selected value, in addition to its registered type.
返回:Whether this drawer accepts the target.
`target`:Current target. The check must not mutate it. | — | — | -| [`protected abstract void Inno.Editor.Inspection.InspectionDrawer.Draw(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L95) | Draws the target-specific Inspector body below the shared target header.
`context`:The active Inspector drawing context.
`target`:The strongly typed target being inspected. | — | — | -| [`protected virtual void Inno.Editor.Inspection.InspectionDrawer.DrawHeader(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L80) | Draws target-specific controls in the second row of the Inspector target header.
`context`:The active Inspector drawing context.
`target`:The strongly typed target being inspected. | The callback is clipped to one header row. Implementations should keep all controls on the current line and must not begin another window, child, table, or popup. | — | -| [`protected virtual string Inno.Editor.Inspection.InspectionDrawer.GetIcon(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L62) | Resolves the icon glyph displayed for the current target.
返回:The target-specific icon glyph. The default implementation returns Inno.Editor.Inspection.InspectionDrawer`1.icon.
`context`:The active Inspector drawing context.
`target`:The strongly typed target being inspected. | — | — | -| [`public abstract string Inno.Editor.Inspection.InspectionDrawer.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L17) | Gets the icon glyph displayed in the large leading slot of the Inspector target header. | — | — | -| [`public abstract class Inno.Editor.Inspection.InspectionDrawer where TTarget : class : Inno.Editor.Inspection.IInspectionDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L11) | Provides the target-specific header presentation and body drawing behavior for an inspected target. | — | — | - -### `Inno.Editor.Inspection.InspectionDrawerAttribute` - + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.InspectionDrawerAttribute.InspectionDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0, bool conditional = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L49) | Creates an inspector drawer registration.
`targetType`:The selected object type handled by the drawer.
`useForChildren`:Whether the drawer may handle assignable derived target types.
`priority`:The tie-breaking priority after exactness and inheritance distance.
`conditional`:Whether CanInspect is a target-dependent predicate. Equally specific accepted peers remain an explicit ambiguity error. | — | `System.ArgumentNullException`:Thrown when targetType is null. | -| [`public bool Inno.Editor.Inspection.InspectionDrawerAttribute.conditional { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L29) | Gets whether CanInspect selects a subset of targets, allowing peer conditional registrations at the same priority. | — | — | -| [`public int Inno.Editor.Inspection.InspectionDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L24) | Gets the tie-breaking registration priority. | — | — | -| [`public System.Type Inno.Editor.Inspection.InspectionDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L14) | Gets the selected target type handled by the drawer. | — | — | -| [`public bool Inno.Editor.Inspection.InspectionDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L19) | Gets whether derived target types are accepted. | — | — | -| [`public sealed class Inno.Editor.Inspection.InspectionDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L9) | Associates an inspector drawer with a selected target type. | — | — | - -### `Inno.Editor.Inspection.InspectionDrawerFactory` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.InspectionDrawerFactory.InspectionDrawerFactory(object @object, nint method)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public virtual System.IAsyncResult Inno.Editor.Inspection.InspectionDrawerFactory.BeginInvoke(System.Type drawerType, System.AsyncCallback callback, object @object)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | — | — | — | -| [`public virtual Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory.EndInvoke(System.IAsyncResult result)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | — | — | — | -| [`public virtual Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory.Invoke(System.Type drawerType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Creates one discovered inspection drawer through the owning editor composition root.
返回:A new drawer instance whose runtime type is drawerType.
`drawerType`:The concrete drawer type selected from the active type catalog. | — | `System.ArgumentNullException`:Implementations may throw when drawerType is null. | -| [`public delegate Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory(System.Type drawerType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Creates one discovered inspection drawer through the owning editor composition root.
返回:A new drawer instance whose runtime type is drawerType.
`drawerType`:The concrete drawer type selected from the active type catalog. | — | `System.ArgumentNullException`:Implementations may throw when drawerType is null. | - -### `Inno.Editor.Inspection.InspectionDrawerRegistry` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.InspectionDrawerRegistry.InspectionDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Inspection.InspectionDrawerFactory factory, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L41) | Creates a generation-aware inspection drawer registry.
`interactions`:The active editor interaction entry point exposed to draw contexts.
`factory`:The composition-root factory used to construct discovered drawer types and resolve their module-specific dependencies.
`types`:The host-owned type catalog that coordinates drawer generations.
`serialization`:Serialization registry used to expose the selected target's visible properties to custom drawers. | — | `System.ArgumentNullException`:Thrown when interactions or factory is null. | -| [`public void Inno.Editor.Inspection.InspectionDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L161) | Releases every active drawer snapshot and unregisters the registry from type refreshes. | — | — | -| [`public bool Inno.Editor.Inspection.InspectionDrawerRegistry.TryResolve(Inno.Editor.Core.EditorContext editorContext, object target, Inno.Editor.Inspection.SerializedPropertyRenderer renderer, out Inno.Editor.Inspection.IInspectionDrawer? drawer, out Inno.Editor.Inspection.InspectionDrawContext? context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L76) | Resolves the most specific registered drawer and creates its drawing context.
返回:true when a matching drawer was resolved.
`editorContext`:The shared editor context exposed to the selected drawer.
`target`:The selected object whose runtime type determines the drawer.
`renderer`:The serialized property renderer exposed to the selected drawer.
`drawer`:The resolved drawer when the method succeeds.
`context`:The target-specific drawing context when the method succeeds. | — | `System.ArgumentNullException`:Thrown when editorContext, target, or renderer is null. | -| [`public bool Inno.Editor.Inspection.InspectionDrawerRegistry.TryResolveExact(Inno.Editor.Core.EditorContext editorContext, object target, Inno.Editor.Inspection.SerializedPropertyRenderer renderer, out Inno.Editor.Inspection.IInspectionDrawer? drawer, out Inno.Editor.Inspection.InspectionDrawContext? context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L127) | Resolves only a drawer explicitly registered for the target's exact runtime type.
返回:true when an exact registration exists; inherited and fallback drawers are ignored.
`editorContext`:The shared editor context exposed to the selected drawer.
`target`:The selected object whose exact runtime type determines the drawer.
`renderer`:The serialized property renderer exposed to the selected drawer.
`drawer`:The exact drawer when the method succeeds.
`context`:The target-specific drawing context when the method succeeds. | — | `System.ArgumentNullException`:Thrown when editorContext, target, or renderer is null. | -| [`public sealed class Inno.Editor.Inspection.InspectionDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L16) | Discovers and resolves inspection drawers through the active type catalog. | — | — | - -### `Inno.Editor.Inspection.InspectorAttributeDrawContext` - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public object? Inno.Editor.Inspection.InspectorAttributeDrawContext.GetSiblingValue(string memberName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L99) | Reads a sibling field or property from the current owner, including non-public base members.
返回:The current sibling value.
`memberName`:Exact CLR member name. | — | `System.InvalidOperationException`:Thrown when the sibling is absent, ambiguous, indexed, or unreadable. | -| [`public System.Attribute Inno.Editor.Inspection.InspectorAttributeDrawContext.attribute { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L55) | Gets the attribute currently invoking a drawer. | — | — | -| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawContext.isReadOnly { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L65) | Gets or sets whether the property control is read-only in the Inspector. | — | — | -| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawContext.isVisible { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L60) | Gets or sets whether the complete property row is visible. | — | — | -| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.label { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L70) | Gets or sets the visible property label. | — | — | -| [`public double? Inno.Editor.Inspection.InspectorAttributeDrawContext.maximum { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L85) | Gets or sets an optional inclusive numeric upper bound. | — | — | -| [`public System.Reflection.MemberInfo Inno.Editor.Inspection.InspectorAttributeDrawContext.member { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L40) | Gets the reflected field or property carrying the current attribute. | — | — | -| [`public double? Inno.Editor.Inspection.InspectorAttributeDrawContext.minimum { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L80) | Gets or sets an optional inclusive numeric lower bound. | — | — | -| [`public object Inno.Editor.Inspection.InspectorAttributeDrawContext.owner { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L35) | Gets the object that owns the annotated member. | — | — | -| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L50) | Gets the stable Inspector control path. | — | — | -| [`public Inno.Core.Serialization.SerializedProperty? Inno.Editor.Inspection.InspectorAttributeDrawContext.property { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L45) | Gets the root serialized property when one is available. | — | — | -| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.tooltip { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L75) | Gets or sets optional hover help for the property label. | — | — | -| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L10) | Carries the mutable presentation state of one attribute-annotated Inspector property. | — | — | - -### `Inno.Editor.Inspection.InspectorAttributeDrawerAttribute` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.InspectorAttributeDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L23) | Creates an Inspector attribute drawer registration.
`targetType`:Attribute type handled by the drawer.
`useForChildren`:Whether derived attribute types are accepted.
`priority`:Tie-breaking priority after inheritance distance. | — | — | -| [`public int Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L49) | Gets the registration priority. | — | — | -| [`public System.Type Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L39) | Gets the handled attribute type. | — | — | -| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L44) | Gets whether derived attribute types are accepted. | — | — | -| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L9) | Associates an Inspector attribute drawer with an attribute type. | — | — | - -### `Inno.Editor.Inspection.InspectorAttributeDrawerRegistry` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.InspectorAttributeDrawerRegistry.InspectorAttributeDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, System.Collections.Generic.IEnumerable drawerServices)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L33) | Creates an Inspector attribute drawer registry.
`interactions`:Active editor interaction entry point.
`types`:Host-owned type catalog.
`serialization`:Active serialization registry.
`drawerServices`:Additional constructor-injected drawer services. | — | — | -| [`public void Inno.Editor.Inspection.InspectorAttributeDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L88) | Releases active drawer generations. | — | — | -| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L14) | Discovers generation-aware Inspector attribute drawers and applies them in declaration order. | — | — | - -### `Inno.Editor.Inspection.IPropertyDrawer` - + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Inspection.IPropertyDrawer.Draw(Inno.Editor.Inspection.PropertyDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IPropertyDrawer.cs#L14) | Draws a property through its encapsulated value accessors.
`context`:Property drawing context. | — | — | -| [`public interface Inno.Editor.Inspection.IPropertyDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IPropertyDrawer.cs#L6) | Draws and optionally edits one serialized property value. | — | — | - -### `Inno.Editor.Inspection.PropertyDrawContext` - @@ -395,93 +231,148 @@ EditorScripts 通过显式 `using InnoEditor.Inspection;` 使用裁剪后的 `In - + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.ClearTextState(string key)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L164) | Removes text-edit state for this renderer, inspected owner, and property path.
`key`:Drawer-local state identity within the current property path. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L235) | Draws a nested serialized property.
`property`:Nested property. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(object metadataOwner, Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L246) | Draws a nested serialized property with presentation metadata resolved from its immediate owner.
`metadataOwner`:Object that declares the nested serialized member.
`property`:Nested property. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(string childName, System.Type childType, System.Func getter, System.Action setter, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L203) | Draws a nested value whose setter writes through this property path.
`childName`:The stable child member name appended to this property path.
`childType`:The declared type used to resolve a property drawer.
`getter`:The callback that reads the latest child value.
`setter`:The callback that writes an edited child value.
`readOnly`:Whether the child should be presented without assignment support. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawInlineChild(string childName, System.Type childType, System.Func getter, System.Action setter, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L315) | Draws a nested value inline without adding another label row.
`childName`:The stable child member name appended to this property path.
`childType`:The declared type used to resolve a property drawer.
`getter`:The callback that reads the latest child value.
`setter`:The callback that writes an edited child value.
`readOnly`:Whether the child should be presented without assignment support. | — | — | -| [`public object? Inno.Editor.Inspection.PropertyDrawContext.GetValue()`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L125) | Gets the latest value.
返回:The latest value returned by the serialized property's getter. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.SetTextState(string key, string value)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L153) | Stores text-edit state scoped to this renderer, inspected owner, and property path.
`key`:Drawer-local state identity within the current property path.
`value`:Current neutral text state to retain between frames. | — | — | -| [`public void Inno.Editor.Inspection.PropertyDrawContext.SetValue(object? value)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L172) | Assigns a value when the property is writable.
`value`:New value. | — | — | -| [`public bool Inno.Editor.Inspection.PropertyDrawContext.TryGetTextState(string key, out string? value)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L139) | Tries to read text-edit state scoped to this renderer, inspected owner, and property path.
返回:true when text state exists for the exact owner and property path.
`key`:Drawer-local state identity within the current property path.
`value`:Receives the stored text when the state exists. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Inspection.PropertyDrawContext.editorContext { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L25) | Gets the shared editor context. | — | — | -| [`public bool Inno.Editor.Inspection.PropertyDrawContext.hdrColor { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L75) | Gets whether Color values are unclamped linear floating-point channels. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Inspection.PropertyDrawContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L30) | Gets the active editor interaction entry point. | — | — | -| [`public bool Inno.Editor.Inspection.PropertyDrawContext.isReadOnly { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L55) | Gets whether assignments are disabled. | — | — | -| [`public string Inno.Editor.Inspection.PropertyDrawContext.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L40) | Gets the display label. | — | — | -| [`public double? Inno.Editor.Inspection.PropertyDrawContext.maximum { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L65) | Gets the optional inclusive numeric upper bound supplied by Inspector metadata. | — | — | -| [`public double? Inno.Editor.Inspection.PropertyDrawContext.minimum { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L60) | Gets the optional inclusive numeric lower bound supplied by Inspector metadata. | — | — | -| [`public string Inno.Editor.Inspection.PropertyDrawContext.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L35) | Gets the stable control path. | — | — | -| [`public System.Type Inno.Editor.Inspection.PropertyDrawContext.propertyType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L45) | Gets the declared property type. | — | — | -| [`public string? Inno.Editor.Inspection.PropertyDrawContext.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L70) | Gets optional hover help supplied by Inspector metadata. | — | — | -| [`public Inno.Core.Serialization.PropertyVisibility Inno.Editor.Inspection.PropertyDrawContext.visibility { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L50) | Gets the serialization visibility flags. | — | — | -| [`public sealed class Inno.Editor.Inspection.PropertyDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L12) | Encapsulates one editable property path and its drawing services. | — | — | - -### `Inno.Editor.Inspection.PropertyDrawerAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.PropertyDrawerAttribute.PropertyDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L41) | Creates a property drawer registration.
`targetType`:The declared serialized property type handled by the drawer.
`useForChildren`:Whether the drawer may handle assignable derived property types.
`priority`:The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`:Thrown when targetType is null. | -| [`public int Inno.Editor.Inspection.PropertyDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L24) | Gets the tie-breaking registration priority. | — | — | -| [`public System.Type Inno.Editor.Inspection.PropertyDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L14) | Gets the declared property type handled by the drawer. | — | — | -| [`public bool Inno.Editor.Inspection.PropertyDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L19) | Gets whether assignable derived types are accepted. | — | — | -| [`public sealed class Inno.Editor.Inspection.PropertyDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L9) | Associates a property drawer with a declared property type. | — | — | - -### `Inno.Editor.Inspection.PropertyDrawerRegistry` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.PropertyDrawerRegistry.PropertyDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, System.Collections.Generic.IEnumerable drawerServices)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L37) | Creates a generation-aware property drawer registry.
`interactions`:The active editor interaction entry point available to property drawer constructors.
`types`:The host-owned type catalog that coordinates drawer generations.
`serialization`:The serialization registry available to structured-object drawers.
`drawerServices`:Additional generation-bound services available to feature-specific drawer constructors. | — | `System.ArgumentNullException`:Thrown when interactions is null. | -| [`public void Inno.Editor.Inspection.PropertyDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L67) | Releases every active property drawer snapshot and unregisters the registry from type refreshes. | — | — | -| [`public Inno.Editor.Inspection.IPropertyDrawer Inno.Editor.Inspection.PropertyDrawerRegistry.Resolve(System.Type propertyType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L58) | Resolves the most specific drawer for a declared property type.
返回:The most specific registered drawer, or the unsupported-value fallback.
`propertyType`:The declared serialized property type to resolve. | — | `System.ArgumentNullException`:Thrown when propertyType is null. | -| [`public sealed class Inno.Editor.Inspection.PropertyDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L15) | Discovers and resolves serialized property drawers through the active type catalog. | — | — | - -### `Inno.Editor.Inspection.SerializedPropertyRenderer` - + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Inspection.SerializedPropertyRenderer.SerializedPropertyRenderer(Inno.Editor.Inspection.PropertyDrawerRegistry drawers, Inno.Editor.Inspection.InspectorAttributeDrawerRegistry attributes, Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Inspection.IInspectionPropertyEditService edits, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L54) | Creates a serialized property renderer over one drawer registry and feature-owned edit service.
`drawers`:The property drawer registry used for runtime type resolution.
`attributes`:The generation-aware registry that interprets Inspector presentation attributes.
`interactions`:The active editor interaction entry point.
`edits`:The feature-owned service used to apply and record property changes.
`logs`:The host-owned log router used to report isolated drawer failures. | — | `System.ArgumentNullException`:Thrown when drawers, interactions, or edits is null. | -| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.Draw(Inno.Editor.Core.EditorContext editorContext, object owner, string ownerPath, Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L84) | Draws a root serialized property.
`editorContext`:Shared editor context.
`owner`:The live domain object that owns the root property.
`ownerPath`:Stable owner path.
`property`:Serialized property. | — | — | -| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.DrawDraftProperty(Inno.Editor.Core.EditorContext editorContext, object stateOwner, object valueOwner, string ownerPath, Inno.Core.Serialization.SerializedProperty property, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L195) | Draws a detached serialized property with its attributes and a feature-owned draft transaction.
`editorContext`:Current Editor frame.
`stateOwner`:Stable host-owned presentation state, containing no collectible objects.
`valueOwner`:Invocation-local detached value declaring the property and attributes.
`ownerPath`:Stable document or settings path.
`property`:Detached native serialized property.
`edits`:Draft mutation and shared History boundary.
`readOnly`:Whether source ownership prohibits edits. | — | — | -| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.DrawValue(Inno.Editor.Core.EditorContext editorContext, object owner, string path, string label, System.Type propertyType, System.Func getter, System.Action setter, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly = false, bool hdrColor = false, string? tooltip = null, double? minimum = null, double? maximum = null)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L150) | Draws a declared authoring value with existing property drawers and a feature-owned draft edit service.
`editorContext`:Current Editor frame.
`owner`:Stable owner for text state and edit routing; not necessarily a canonical serialized object.
`path`:Stable unique property path.
`label`:User-facing label.
`propertyType`:Declared value type used to select its registered drawer.
`getter`:Reads the detached value.
`setter`:Changes the detached value within the supplied edit service.
`edits`:Feature-owned transaction boundary, propagated to nested properties.
`readOnly`:Whether assignment is disabled.
`hdrColor`:Whether Color values use unclamped linear floating-point channels.
`tooltip`:Optional authoring description rendered by the shared property tooltip.
`minimum`:Optional inclusive numeric editing bound.
`maximum`:Optional inclusive numeric editing bound. | — | — | -| [`public sealed class Inno.Editor.Inspection.SerializedPropertyRenderer`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L21) | Resolves drawers and renders serialized property paths with isolated error handling. | — | — | +| [`public interface Inno.Editor.Inspection.IInspectorAttributeDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L6) | Extends serialized-property presentation with reusable attribute-driven behavior. | — | — | +| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.Update(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L14) | Updates visibility, interactivity, labeling, help, or numeric constraints before layout.
`context`: Mutable presentation state for the annotated property. | — | — | +| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.DrawBefore(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L24) | Draws a decorator immediately before the property row.
`context`: Current presentation state for the annotated property. | — | — | +| [`void Inno.Editor.Inspection.IInspectorAttributeDrawer.DrawAfter(Inno.Editor.Inspection.InspectorAttributeDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/IInspectorAttributeDrawer.cs#L34) | Draws a decorator immediately after the property row.
`context`: Current presentation state for the annotated property. | — | — | +| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L10) | Carries the mutable presentation state of one attribute-annotated Inspector property. | — | — | +| [`public object Inno.Editor.Inspection.InspectorAttributeDrawContext.owner { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L35) | Gets the object that owns the annotated member. | — | — | +| [`public System.Reflection.MemberInfo Inno.Editor.Inspection.InspectorAttributeDrawContext.member { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L40) | Gets the reflected field or property carrying the current attribute. | — | — | +| [`public Inno.Core.Serialization.SerializedProperty? Inno.Editor.Inspection.InspectorAttributeDrawContext.property { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L45) | Gets the root serialized property when one is available. | — | — | +| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L50) | Gets the stable Inspector control path. | — | — | +| [`public System.Attribute Inno.Editor.Inspection.InspectorAttributeDrawContext.attribute { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L55) | Gets the attribute currently invoking a drawer. | — | — | +| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawContext.isVisible { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L60) | Gets or sets whether the complete property row is visible. | — | — | +| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawContext.isReadOnly { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L65) | Gets or sets whether the property control is read-only in the Inspector. | — | — | +| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.label { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L70) | Gets or sets the visible property label. | — | — | +| [`public string Inno.Editor.Inspection.InspectorAttributeDrawContext.tooltip { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L75) | Gets or sets optional hover help for the property label. | — | — | +| [`public double? Inno.Editor.Inspection.InspectorAttributeDrawContext.minimum { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L80) | Gets or sets an optional inclusive numeric lower bound. | — | — | +| [`public double? Inno.Editor.Inspection.InspectorAttributeDrawContext.maximum { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L85) | Gets or sets an optional inclusive numeric upper bound. | — | — | +| [`public object? Inno.Editor.Inspection.InspectorAttributeDrawContext.GetSiblingValue(string memberName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawContext.cs#L99) | Reads a sibling field or property from the current owner, including non-public base members.
`memberName`: Exact CLR member name. | The current sibling value. | `System.InvalidOperationException`: Thrown when the sibling is absent, ambiguous, indexed, or unreadable. | +| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L9) | Associates an Inspector attribute drawer with an attribute type. | — | — | +| [`public Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.InspectorAttributeDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L23) | Creates an Inspector attribute drawer registration.
`targetType`: Attribute type handled by the drawer.
`useForChildren`: Whether derived attribute types are accepted.
`priority`: Tie-breaking priority after inheritance distance. | — | — | +| [`public System.Type Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L39) | Gets the handled attribute type. | — | — | +| [`public bool Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L44) | Gets whether derived attribute types are accepted. | — | — | +| [`public int Inno.Editor.Inspection.InspectorAttributeDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerAttribute.cs#L49) | Gets the registration priority. | — | — | +| [`public sealed class Inno.Editor.Inspection.InspectorAttributeDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L14) | Discovers generation-aware Inspector attribute drawers and applies them in declaration order. | — | — | +| [`public Inno.Editor.Inspection.InspectorAttributeDrawerRegistry.InspectorAttributeDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, System.Collections.Generic.IEnumerable drawerServices)`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L33) | Creates an Inspector attribute drawer registry.
`interactions`: Active editor interaction entry point.
`types`: Host-owned type catalog.
`serialization`: Active serialization registry.
`drawerServices`: Additional constructor-injected drawer services. | — | — | +| [`public void Inno.Editor.Inspection.InspectorAttributeDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/InspectorAttributeDrawerRegistry.cs#L88) | Releases active drawer generations. | — | — | +| [`public interface Inno.Editor.Inspection.IInspectionIconProvider`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/IInspectionIconProvider.cs#L9) | Resolves a presentation icon for inspected targets owned by another editor feature.
`TTarget`: The target type whose icon can be resolved. | — | — | +| [`string Inno.Editor.Inspection.IInspectionIconProvider.GetIcon(TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/IInspectionIconProvider.cs#L20) | Resolves the presentation icon for one inspected target.
`target`: The target whose icon should be resolved. | The icon glyph registered by the owning feature. | — | +| [`public sealed class Inno.Editor.Inspection.InspectionDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L15) | Provides services and state to an inspector drawer. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Inspection.InspectionDrawContext.editorContext { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L22) | Gets the shared editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Inspection.InspectionDrawContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L27) | Gets the active editor interaction entry point. | — | — | +| [`public object Inno.Editor.Inspection.InspectionDrawContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L32) | Gets the selected target. | — | — | +| [`public Inno.Editor.Inspection.SerializedPropertyRenderer Inno.Editor.Inspection.InspectionDrawContext.properties { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L37) | Gets the serialized property renderer. | — | — | +| [`public bool Inno.Editor.Inspection.InspectionDrawContext.TryDrawInline(object target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L48) | Draws an exact target-specific body inside the caller's existing Inspector card.
`target`: Nested target whose exact registered drawer should render. | when an exact drawer rendered the target. | — | +| [`public bool Inno.Editor.Inspection.InspectionDrawContext.HasProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L75) | Gets whether the target exposes one runtime-visible serialized property.
`propertyName`: Exact serialized property name. | when the property is available. | — | +| [`public T Inno.Editor.Inspection.InspectionDrawContext.GetProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L94) | Reads one runtime-visible serialized property from the target.
`propertyName`: Exact serialized property name.
`T`: Expected property value type. | The current strongly typed property value. | — | +| [`public void Inno.Editor.Inspection.InspectionDrawContext.DrawProperty(string propertyName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L110) | Draws one named property with its ordinary editor, history, and validation behavior.
`propertyName`: Exact serialized property name. | — | — | +| [`public void Inno.Editor.Inspection.InspectionDrawContext.DrawProperties()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawContext.cs#L128) | Draws every runtime-visible serialized property through the shared attribute and property drawer pipeline. | — | — | +| [`public abstract class Inno.Editor.Inspection.InspectionDrawer where TTarget : class : Inno.Editor.Inspection.IInspectionDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L11) | Provides the target-specific header presentation and body drawing behavior for an inspected target.
`TTarget`: The target type inspected by the drawer. | — | — | +| [`public abstract string Inno.Editor.Inspection.InspectionDrawer.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L17) | Gets the icon glyph displayed in the large leading slot of the Inspector target header. | — | — | +| [`protected virtual bool Inno.Editor.Inspection.InspectionDrawer.CanInspect(TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L28) | Determines whether this drawer handles the selected value, in addition to its registered type.
`target`: Current target. The check must not mutate it. | Whether this drawer accepts the target. | — | +| [`protected abstract (string name, System.Action? setter) Inno.Editor.Inspection.InspectionDrawer.BindName(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L45) | Binds the name displayed in the first row of the Inspector target header.
`context`: The active Inspector drawing context.
`target`: The strongly typed target being inspected. | The current target name and a callback that accepts a requested replacement name, or for the callback when the name is read-only. | — | +| [`protected virtual string Inno.Editor.Inspection.InspectionDrawer.GetIcon(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L62) | Resolves the icon glyph displayed for the current target.
`context`: The active Inspector drawing context.
`target`: The strongly typed target being inspected. | The target-specific icon glyph. The default implementation returns . | — | +| [`protected virtual void Inno.Editor.Inspection.InspectionDrawer.DrawHeader(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L80) | Draws target-specific controls in the second row of the Inspector target header.
`context`: The active Inspector drawing context.
`target`: The strongly typed target being inspected. | — | — | +| [`protected abstract void Inno.Editor.Inspection.InspectionDrawer.Draw(Inno.Editor.Inspection.InspectionDrawContext context, TTarget target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L95) | Draws the target-specific Inspector body below the shared target header.
`context`: The active Inspector drawing context.
`target`: The strongly typed target being inspected. | — | — | +| [`protected Inno.Editor.Inspection.InspectionDrawer.InspectionDrawer()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public interface Inno.Editor.Inspection.IInspectionDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L125) | Defines the runtime bridge used to render a discovered inspection drawer. | — | — | +| [`bool Inno.Editor.Inspection.IInspectionDrawer.CanInspect(object target)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L136) | Determines whether a type-compatible selected value can be inspected by this drawer.
`target`: Selected value, never mutated by this query. | Whether this drawer accepts the value; ordinary type-only drawers accept by default. | — | +| [`string Inno.Editor.Inspection.IInspectionDrawer.GetIcon(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L147) | Gets the icon glyph displayed in the Inspector target header.
`context`: The active Inspector drawing context. | The icon glyph for the current target. | — | +| [`(string name, System.Action? setter) Inno.Editor.Inspection.IInspectionDrawer.BindName(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L159) | Binds the target name displayed by the Inspector target header.
`context`: The active Inspector drawing context. | The current target name and a callback that accepts a requested replacement name, or for the callback when the name is read-only. | — | +| [`void Inno.Editor.Inspection.IInspectionDrawer.DrawHeader(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L167) | Draws the target-specific second header row.
`context`: The active Inspector drawing context. | — | — | +| [`void Inno.Editor.Inspection.IInspectionDrawer.Draw(Inno.Editor.Inspection.InspectionDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawer.cs#L175) | Draws the target-specific Inspector body.
`context`: The active Inspector drawing context. | — | — | +| [`public sealed class Inno.Editor.Inspection.InspectionDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L9) | Associates an inspector drawer with a selected target type. | — | — | +| [`public System.Type Inno.Editor.Inspection.InspectionDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L14) | Gets the selected target type handled by the drawer. | — | — | +| [`public bool Inno.Editor.Inspection.InspectionDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L19) | Gets whether derived target types are accepted. | — | — | +| [`public int Inno.Editor.Inspection.InspectionDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L24) | Gets the tie-breaking registration priority. | — | — | +| [`public bool Inno.Editor.Inspection.InspectionDrawerAttribute.conditional { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L29) | Gets whether CanInspect selects a subset of targets, allowing peer conditional registrations at the same priority. | — | — | +| [`public Inno.Editor.Inspection.InspectionDrawerAttribute.InspectionDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0, bool conditional = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerAttribute.cs#L49) | Creates an inspector drawer registration.
`targetType`: The selected object type handled by the drawer.
`useForChildren`: Whether the drawer may handle assignable derived target types.
`priority`: The tie-breaking priority after exactness and inheritance distance.
`conditional`: Whether CanInspect is a target-dependent predicate. Equally specific accepted peers remain an explicit ambiguity error. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public delegate Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory(System.Type drawerType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Creates one discovered inspection drawer through the owning editor composition root.
`drawerType`: The concrete drawer type selected from the active type catalog. | A new drawer instance whose runtime type is . | `System.ArgumentNullException`: Implementations may throw when is . | +| [`public Inno.Editor.Inspection.InspectionDrawerFactory.InspectionDrawerFactory(object @object, nint method)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public virtual Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory.Invoke(System.Type drawerType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Creates one discovered inspection drawer through the owning editor composition root.
`drawerType`: The concrete drawer type selected from the active type catalog. | A new drawer instance whose runtime type is . | `System.ArgumentNullException`: Implementations may throw when is . | +| [`public virtual System.IAsyncResult Inno.Editor.Inspection.InspectionDrawerFactory.BeginInvoke(System.Type drawerType, System.AsyncCallback callback, object @object)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public virtual Inno.Editor.Inspection.IInspectionDrawer Inno.Editor.Inspection.InspectionDrawerFactory.EndInvoke(System.IAsyncResult result)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerFactory.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Inspection.InspectionDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L16) | Discovers and resolves inspection drawers through the active type catalog. | — | — | +| [`public Inno.Editor.Inspection.InspectionDrawerRegistry.InspectionDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Inspection.InspectionDrawerFactory factory, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L41) | Creates a generation-aware inspection drawer registry.
`interactions`: The active editor interaction entry point exposed to draw contexts.
`factory`: The composition-root factory used to construct discovered drawer types and resolve their module-specific dependencies.
`types`: The host-owned type catalog that coordinates drawer generations.
`serialization`: Serialization registry used to expose the selected target's visible properties to custom drawers. | — | `System.ArgumentNullException`: Thrown when or is . | +| [`public bool Inno.Editor.Inspection.InspectionDrawerRegistry.TryResolve(Inno.Editor.Core.EditorContext editorContext, object target, Inno.Editor.Inspection.SerializedPropertyRenderer renderer, out Inno.Editor.Inspection.IInspectionDrawer? drawer, out Inno.Editor.Inspection.InspectionDrawContext? context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L76) | Resolves the most specific registered drawer and creates its drawing context.
`editorContext`: The shared editor context exposed to the selected drawer.
`target`: The selected object whose runtime type determines the drawer.
`renderer`: The serialized property renderer exposed to the selected drawer.
`drawer`: The resolved drawer when the method succeeds.
`context`: The target-specific drawing context when the method succeeds. | when a matching drawer was resolved. | `System.ArgumentNullException`: Thrown when , , or is . | +| [`public bool Inno.Editor.Inspection.InspectionDrawerRegistry.TryResolveExact(Inno.Editor.Core.EditorContext editorContext, object target, Inno.Editor.Inspection.SerializedPropertyRenderer renderer, out Inno.Editor.Inspection.IInspectionDrawer? drawer, out Inno.Editor.Inspection.InspectionDrawContext? context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L127) | Resolves only a drawer explicitly registered for the target's exact runtime type.
`editorContext`: The shared editor context exposed to the selected drawer.
`target`: The selected object whose exact runtime type determines the drawer.
`renderer`: The serialized property renderer exposed to the selected drawer.
`drawer`: The exact drawer when the method succeeds.
`context`: The target-specific drawing context when the method succeeds. | when an exact registration exists; inherited and fallback drawers are ignored. | `System.ArgumentNullException`: Thrown when , , or is . | +| [`public void Inno.Editor.Inspection.InspectionDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/InspectionDrawerRegistry.cs#L161) | Releases every active drawer snapshot and unregisters the registry from type refreshes. | — | — | +| [`public interface Inno.Editor.Inspection.IInspectionPropertyEditService`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs#L7) | Applies one root serialized-property mutation through the history mechanism owned by a feature. | — | — | +| [`bool Inno.Editor.Inspection.IInspectionPropertyEditService.ChangeProperty(object owner, string propertyName, System.Action mutation, string historyName, string? mergeKey, string? componentName)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs#L41) | Applies a property mutation and records it when the owning feature observes a value change.
`owner`: The domain object that owns the root serialized property.
`propertyName`: The exact root serialized member name.
`mutation`: The callback that assigns the requested value.
`historyName`: The user-facing name for the resulting history entry.
`mergeKey`: The current shared gesture key, or null for a discrete edit.
`componentName`: An optional public value component; null replaces the complete root value. | when the value changed and the owning feature recorded the edit; otherwise, . | `System.ArgumentNullException`: Thrown when or is .
`System.ArgumentException`: Thrown when or is empty. | +| [`public interface Inno.Editor.Inspection.IPropertyDrawer`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IPropertyDrawer.cs#L6) | Draws and optionally edits one serialized property value. | — | — | +| [`void Inno.Editor.Inspection.IPropertyDrawer.Draw(Inno.Editor.Inspection.PropertyDrawContext context)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IPropertyDrawer.cs#L14) | Draws a property through its encapsulated value accessors.
`context`: Property drawing context. | — | — | +| [`public sealed class Inno.Editor.Inspection.PropertyDrawContext`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L12) | Encapsulates one editable property path and its drawing services. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Inspection.PropertyDrawContext.editorContext { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L24) | Gets the shared editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Inspection.PropertyDrawContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L28) | Gets the active editor interaction entry point. | — | — | +| [`public string Inno.Editor.Inspection.PropertyDrawContext.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L32) | Gets the stable control path. | — | — | +| [`public string Inno.Editor.Inspection.PropertyDrawContext.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L36) | Gets the display label. | — | — | +| [`public System.Type Inno.Editor.Inspection.PropertyDrawContext.propertyType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L40) | Gets the declared property type. | — | — | +| [`public Inno.Core.Serialization.PropertyVisibility Inno.Editor.Inspection.PropertyDrawContext.visibility { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L44) | Gets the serialization visibility flags. | — | — | +| [`public bool Inno.Editor.Inspection.PropertyDrawContext.isReadOnly { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L48) | Gets whether assignments are disabled. | — | — | +| [`public double? Inno.Editor.Inspection.PropertyDrawContext.minimum { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L52) | Gets the optional inclusive numeric lower bound supplied by Inspector metadata. | — | — | +| [`public double? Inno.Editor.Inspection.PropertyDrawContext.maximum { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L56) | Gets the optional inclusive numeric upper bound supplied by Inspector metadata. | — | — | +| [`public string? Inno.Editor.Inspection.PropertyDrawContext.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L60) | Gets optional hover help supplied by Inspector metadata. | — | — | +| [`public bool Inno.Editor.Inspection.PropertyDrawContext.hdrColor { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L64) | Gets whether Color values are unclamped linear floating-point channels. | — | — | +| [`public object? Inno.Editor.Inspection.PropertyDrawContext.GetValue()`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L114) | Gets the latest value. | The latest value returned by the serialized property's getter. | — | +| [`public bool Inno.Editor.Inspection.PropertyDrawContext.TryGetTextState(string key, out string? value)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L127) | Tries to read text-edit state scoped to this renderer, inspected owner, and property path.
`key`: Drawer-local state identity within the current property path.
`value`: Receives the stored text when the state exists. | when text state exists for the exact owner and property path. | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.SetTextState(string key, string value)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L140) | Stores text-edit state scoped to this renderer, inspected owner, and property path.
`key`: Drawer-local state identity within the current property path.
`value`: Current neutral text state to retain between frames. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.ClearTextState(string key)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L150) | Removes text-edit state for this renderer, inspected owner, and property path.
`key`: Drawer-local state identity within the current property path. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.SetValue(object? value, string? componentName = null)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L160) | Assigns a value when the property is writable.
`value`: New value.
`componentName`: An optional public scalar component, or an Euler axis such as euler.x, for precise batch value edits. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(string childName, System.Type childType, System.Func getter, System.Action setter, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L199) | Draws a nested value whose setter writes through this property path.
`childName`: The stable child member name appended to this property path.
`childType`: The declared type used to resolve a property drawer.
`getter`: The callback that reads the latest child value.
`setter`: The callback that writes an edited child value.
`readOnly`: Whether the child should be presented without assignment support. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L216) | Draws a nested serialized property.
`property`: Nested property. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawChild(object metadataOwner, Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L226) | Draws a nested serialized property with presentation metadata resolved from its immediate owner.
`metadataOwner`: Object that declares the nested serialized member.
`property`: Nested property. | — | — | +| [`public void Inno.Editor.Inspection.PropertyDrawContext.DrawInlineChild(string childName, System.Type childType, System.Func getter, System.Action setter, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs#L295) | Draws a nested value inline without adding another label row.
`childName`: The stable child member name appended to this property path.
`childType`: The declared type used to resolve a property drawer.
`getter`: The callback that reads the latest child value.
`setter`: The callback that writes an edited child value.
`readOnly`: Whether the child should be presented without assignment support. | — | — | +| [`public sealed class Inno.Editor.Inspection.PropertyDrawerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L9) | Associates a property drawer with a declared property type. | — | — | +| [`public System.Type Inno.Editor.Inspection.PropertyDrawerAttribute.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L14) | Gets the declared property type handled by the drawer. | — | — | +| [`public bool Inno.Editor.Inspection.PropertyDrawerAttribute.useForChildren { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L19) | Gets whether assignable derived types are accepted. | — | — | +| [`public int Inno.Editor.Inspection.PropertyDrawerAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L24) | Gets the tie-breaking registration priority. | — | — | +| [`public Inno.Editor.Inspection.PropertyDrawerAttribute.PropertyDrawerAttribute(System.Type targetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerAttribute.cs#L41) | Creates a property drawer registration.
`targetType`: The declared serialized property type handled by the drawer.
`useForChildren`: Whether the drawer may handle assignable derived property types.
`priority`: The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public sealed class Inno.Editor.Inspection.PropertyDrawerRegistry : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L15) | Discovers and resolves serialized property drawers through the active type catalog. | — | — | +| [`public Inno.Editor.Inspection.PropertyDrawerRegistry.PropertyDrawerRegistry(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, System.Collections.Generic.IEnumerable drawerServices)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L37) | Creates a generation-aware property drawer registry.
`interactions`: The active editor interaction entry point available to property drawer constructors.
`types`: The host-owned type catalog that coordinates drawer generations.
`serialization`: The serialization registry available to structured-object drawers.
`drawerServices`: Additional generation-bound services available to feature-specific drawer constructors. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Editor.Inspection.IPropertyDrawer Inno.Editor.Inspection.PropertyDrawerRegistry.Resolve(System.Type propertyType)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L58) | Resolves the most specific drawer for a declared property type.
`propertyType`: The declared serialized property type to resolve. | The most specific registered drawer, or the unsupported-value fallback. | `System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.Inspection.PropertyDrawerRegistry.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawerRegistry.cs#L67) | Releases every active property drawer snapshot and unregisters the registry from type refreshes. | — | — | +| [`public sealed class Inno.Editor.Inspection.SerializedPropertyRenderer`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L19) | Resolves drawers and renders serialized property paths with isolated error handling. | — | — | +| [`public Inno.Editor.Inspection.SerializedPropertyRenderer.SerializedPropertyRenderer(Inno.Editor.Inspection.PropertyDrawerRegistry drawers, Inno.Editor.Inspection.InspectorAttributeDrawerRegistry attributes, Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Inspection.IInspectionPropertyEditService edits, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L51) | Creates a serialized property renderer over one drawer registry and feature-owned edit service.
`drawers`: The property drawer registry used for runtime type resolution.
`attributes`: The generation-aware registry that interprets Inspector presentation attributes.
`interactions`: The active editor interaction entry point.
`edits`: The feature-owned service used to apply and record property changes.
`logs`: The host-owned log router used to report isolated drawer failures. | — | `System.ArgumentNullException`: Thrown when , , or is . | +| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.Draw(Inno.Editor.Core.EditorContext editorContext, object owner, string ownerPath, Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L81) | Draws a root serialized property.
`editorContext`: Shared editor context.
`owner`: The live domain object that owns the root property.
`ownerPath`: Stable owner path.
`property`: Serialized property. | — | — | +| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.DrawValue(Inno.Editor.Core.EditorContext editorContext, object owner, string path, string label, System.Type propertyType, System.Func getter, System.Action setter, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly = false, bool hdrColor = false, string? tooltip = null, double? minimum = null, double? maximum = null)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L148) | Draws a declared authoring value with existing property drawers and a feature-owned draft edit service.
`editorContext`: Current Editor frame.
`owner`: Stable owner for text state and edit routing; not necessarily a canonical serialized object.
`path`: Stable unique property path.
`label`: User-facing label.
`propertyType`: Declared value type used to select its registered drawer.
`getter`: Reads the detached value.
`setter`: Changes the detached value within the supplied edit service.
`edits`: Feature-owned transaction boundary, propagated to nested properties.
`readOnly`: Whether assignment is disabled.
`hdrColor`: Whether Color values use unclamped linear floating-point channels.
`tooltip`: Optional authoring description rendered by the shared property tooltip.
`minimum`: Optional inclusive numeric editing bound.
`maximum`: Optional inclusive numeric editing bound. | — | — | +| [`public void Inno.Editor.Inspection.SerializedPropertyRenderer.DrawDraftProperty(Inno.Editor.Core.EditorContext editorContext, object stateOwner, object valueOwner, string ownerPath, Inno.Core.Serialization.SerializedProperty property, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly = false)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L210) | Draws a detached serialized property with its attributes and a feature-owned draft transaction.
`editorContext`: Current Editor frame.
`stateOwner`: Stable host-owned presentation state, containing no collectible objects.
`valueOwner`: Invocation-local detached value declaring the property and attributes.
`ownerPath`: Stable document or settings path.
`property`: Detached native serialized property.
`edits`: Draft mutation and shared History boundary.
`readOnly`: Whether source ownership prohibits edits. | — | — | +| [`public (bool isVisible, bool isReadOnly) Inno.Editor.Inspection.SerializedPropertyRenderer.GetPropertyAvailability(object owner, Inno.Core.Serialization.SerializedProperty property)`](../../src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs#L253) | Evaluates the same current-generation presentation attributes used by drawing without creating any UI state.
`owner`: The current object declaring the property's conditional metadata.
`property`: The root serialized property to inspect. | Visibility and editability for this exact owner; callers must combine all selected owners before a batch edit. | — | ## 项目依赖 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Mathematics](../core/Inno.Core.Mathematics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Annotations](Inno.Editor.Annotations.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Core.Mathematics` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Annotations` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Interactions.md b/docs/editor/Inno.Editor.Interactions.md index a0bc5d3c2..c16af315b 100644 --- a/docs/editor/Inno.Editor.Interactions.md +++ b/docs/editor/Inno.Editor.Interactions.md @@ -392,15 +392,31 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 脚本只能看到明确导出的契约,不能看到 Router、Catalog、TypeCache snapshot 或实现侧 `Inno.*`。完全禁止 global using。 -## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 -### `Inno.Editor.Interactions.EditorAction` +## 有序多选与连续编辑 + +EditorSelectionState 保存有序身份、primary、anchor 和变化计数;范围选择使用当前可见排序,Ctrl 切换单项,Shift 扩展范围。EditorSelectionSnapshot 不持有 live objects,CopyTargets 在原身份 domain 重新解析。Missing 仍保留在 snapshot 的 identities 中,不能被当作空选择跳过。 + +EditorEditGesture 是共享 History transaction 的所有者。每次 sample 立即 RecordApplied;Commit 完成一个 Undo,Cancel 回滚。拒绝非 LIFO Commit/Cancel 时不丢失事务。文档 Save/Revert/Apply/Close、命令和扩展切换发出 Core Events 的 EditorOperationPreparingEvent,presentation 在新操作开始前完成手势。该通知不是第二条事件总线。 + +Action 的延迟 target、focus 和选择均使用 Core Identity;执行前重新解析并拒绝 stale generation。纯值参数可以使用不可变值类型,避免无 identity 的临时 command wrapper 被 GC 回收。 + + + +## Host 临时 Identity domain + +`RegisterIdentityDomain` 借用 Core allocator 并返回 owner-thread 路由 lease。它让同一套 Selection、focus、queue 和 DragDrop 解析独立的 Play 世界;不复制对象表,也不取得 allocator 的销毁权。重复登记、错误线程或退休后登记明确失败。Scene workspace 在恢复 Edit 选择成功后释放路由;失败保留 owner 供重试。`IEditorSelectionCoordinator` 是 Host-only SPI,不再作为项目脚本导出;脚本继续使用 `EditorInteractions`。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + @@ -412,51 +428,7 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected void Inno.Editor.Interactions.EditorAction.Activate(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L72) | Activates this action for a target and cancels any operation it previously owned.
`context`:The contextual request that starts the operation. | — | `System.ArgumentNullException`:Thrown when context is null. | -| [`protected void Inno.Editor.Interactions.EditorAction.Cancel()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L95) | Cancels the current operation and returns the action to its idle state. | — | — | -| [`protected void Inno.Editor.Interactions.EditorAction.Complete()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L83) | Completes the current operation and returns the action to its idle state. | — | — | -| [`public void Inno.Editor.Interactions.EditorAction.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L161) | Cancels active work and releases this action instance. | — | — | -| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L121) | Executes the action for the supplied context.
`context`:The editor, area, target, and optional argument for the operation. | — | — | -| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnCancelled()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L144) | Runs after an active operation is cancelled. | — | — | -| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnCompleted()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L137) | Runs after an active operation completes successfully. | — | — | -| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnPresentationLost()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L156) | Runs when this action's active target loses editor presentation focus. | The default behavior cancels the active operation. An action that owns an editable value may override this method to validate and commit that value. The operation is cancelled automatically when an override returns without calling Inno.Editor.Interactions.EditorAction.Complete or Inno.Editor.Interactions.EditorAction.Cancel. | — | -| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L132) | Presents an active action at the current target location.
返回:true when the action replaced the target's normal content; otherwise, false.
`context`:The current presentation area and target. | — | — | -| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L113) | Evaluates the action for the supplied context.
返回:The current presentation and availability state.
`context`:The editor, area, target, and optional argument for the query. | — | — | -| [`public bool Inno.Editor.Interactions.EditorAction.isActive { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L17) | Gets whether the action currently owns an active multi-frame operation. | — | — | -| [`public virtual System.Type? Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L22) | Gets the required target type, or null for a targetless action. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorAction : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L8) | Defines one automatically discovered editor operation and owns its complete multi-frame lifecycle. | — | — | - -### `Inno.Editor.Interactions.EditorAction`1` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L380) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected override sealed void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L411) | Applies the editor action to the supplied interaction context.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L455) | Executes the action for a strongly typed target.
`context`:The contextual request containing the typed target. | — | — | -| [`protected override sealed bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L431) | Presents this action through the current editor interaction surface.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L466) | Presents an active action for a strongly typed target.
返回:true when content was presented; otherwise, false.
`context`:The current presentation request containing the typed target. | — | — | -| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L397) | Evaluates the operation's current availability and presentation state.
返回:The validated editor action state that represents the completed operation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L447) | Evaluates the action for a strongly typed target.
返回:The current presentation and availability state.
`context`:The contextual request containing the typed target. | — | — | -| [`public override sealed System.Type Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L386) | Gets the concrete type handled by this extension implementation. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorAction where TTarget : class : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L380) | Defines a discoverable action whose target must be assignable to a specific reference type. | — | — | - -### `Inno.Editor.Interactions.EditorAction`2` - - + @@ -465,95 +437,51 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L180) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected override sealed void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L210) | Applies the editor action to the supplied interaction context.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L250) | Executes the action for a typed target and argument.
`context`:The typed contextual request. | — | — | -| [`protected override sealed bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L230) | Presents this action through the current editor interaction surface.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L261) | Presents an active action for a typed target and argument.
返回:true when content was presented.
`context`:The typed presentation request. | — | — | -| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L199) | Evaluates the operation's current availability and presentation state.
返回:The validated editor action state that represents the completed operation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L242) | Evaluates the action for a typed target and argument.
返回:The current presentation and availability state.
`context`:The typed contextual request. | — | — | -| [`public override sealed System.Type Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L186) | Gets the concrete type handled by this extension implementation. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorAction where TTarget : class : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L180) | Defines a target action that requires one strongly typed command argument. | — | — | - -### `Inno.Editor.Interactions.EditorActionArgumentContext`1` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public TArgument Inno.Editor.Interactions.EditorActionArgumentContext.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L160) | Gets the strongly typed action argument. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorActionArgumentContext : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L146) | Provides a strongly typed action argument to a targetless editor action. | — | — | - -### `Inno.Editor.Interactions.EditorActionAttribute` - + + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorActionAttribute.EditorActionAttribute(string action, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L23) | Creates an action registration available from every area.
`action`:The stable semantic action name.
`priority`:The tie-breaking priority used after target specificity. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public Inno.Editor.Interactions.EditorActionAttribute.EditorActionAttribute(string action, string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L46) | Creates an action registration restricted to one exact area.
`action`:The stable semantic action name.
`area`:The exact area, or an empty string for every area.
`priority`:The tie-breaking priority. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public string Inno.Editor.Interactions.EditorActionAttribute.action { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L61) | Gets the stable action name. | — | — | -| [`public string Inno.Editor.Interactions.EditorActionAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L66) | Gets the optional exact interaction area. | — | — | -| [`public int Inno.Editor.Interactions.EditorActionAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L71) | Gets the tie-breaking priority. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorActionAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L9) | Registers an editor action for automatic discovery and dispatch. | — | — | - -### `Inno.Editor.Interactions.EditorActionContext` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Editor.Interactions.EditorActionContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L69) | Gets the stable interaction area. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorActionContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L54) | Gets the shared passive editor context. | — | — | -| [`public Inno.Editor.Interactions.IEditorHistory Inno.Editor.Interactions.EditorActionContext.history { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L64) | Gets the transactional history used to record reversible mutations performed by this action. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorActionContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L59) | Gets the active interaction entry point. | — | — | -| [`public object? Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L74) | Gets the contextual action target. | — | — | -| [`public class Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L10) | Provides contextual state to an editor action. | — | — | - -### `Inno.Editor.Interactions.EditorActionContext`1` - - + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public TTarget Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L103) | Gets the strongly typed action target. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorActionContext where TTarget : class : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L88) | Provides a strongly typed target to an editor action implementation. | — | — | - -### `Inno.Editor.Interactions.EditorActionContext`2` - - + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public TArgument Inno.Editor.Interactions.EditorActionContext.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L137) | Gets the strongly typed action argument. | — | — | -| [`public TTarget Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L132) | Gets the strongly typed action target. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorActionContext where TTarget : class : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L115) | Provides a strongly typed target and action argument to an editor action. | — | — | - -### `Inno.Editor.Interactions.EditorActionState` - + + - + + @@ -563,59 +491,37 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorActionState.EditorActionState(bool isVisible, bool isEnabled, bool isChecked = false, string? displayName = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L23) | Creates the contextual presentation state of an editor action.
`isVisible`:Whether the action should be included in the current presentation.
`isEnabled`:Whether the action can execute in the current context.
`isChecked`:Whether the action is currently presented as checked.
`displayName`:An optional contextual label that replaces the static placement label. | — | — | -| [`public bool Inno.Editor.Interactions.EditorActionState.Equals(Inno.Editor.Interactions.EditorActionState other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorActionState.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorActionState.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorActionState.operator ==(Inno.Editor.Interactions.EditorActionState left, Inno.Editor.Interactions.EditorActionState right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorActionState.operator !=(Inno.Editor.Interactions.EditorActionState left, Inno.Editor.Interactions.EditorActionState right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorActionState.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.disabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L63) | Gets a visible but disabled action state. | — | — | -| [`public string? Inno.Editor.Interactions.EditorActionState.displayName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L53) | Gets an optional contextual display name. | — | — | -| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.enabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L58) | Gets a visible and enabled action state. | — | — | -| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.hidden { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L68) | Gets a hidden action state. | — | — | -| [`public bool Inno.Editor.Interactions.EditorActionState.isChecked { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L48) | Gets whether the action is currently checked. | — | — | -| [`public bool Inno.Editor.Interactions.EditorActionState.isEnabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L43) | Gets whether the action can execute. | — | — | -| [`public bool Inno.Editor.Interactions.EditorActionState.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L38) | Gets whether the action should be displayed. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorActionState : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L6) | Describes the current presentation and availability of an editor action. | — | — | - -### `Inno.Editor.Interactions.EditorArgumentAction`1` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorArgumentAction.EditorArgumentAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L283) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract void Inno.Editor.Interactions.EditorArgumentAction.Execute(Inno.Editor.Interactions.EditorActionArgumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L334) | Executes the action for a typed argument.
`context`:The typed contextual request. | — | — | -| [`protected override sealed void Inno.Editor.Interactions.EditorArgumentAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L307) | Applies the editor action to the supplied interaction context.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorArgumentAction.Query(Inno.Editor.Interactions.EditorActionArgumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L326) | Evaluates the action for a typed argument.
返回:The current presentation and availability state.
`context`:The typed contextual request. | — | — | -| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorArgumentAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L296) | Evaluates the operation's current availability and presentation state.
返回:The validated editor action state that represents the completed operation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorArgumentAction : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L283) | Defines a targetless editor action that requires one strongly typed command argument. | — | — | - -### `Inno.Editor.Interactions.EditorDocumentCloseMode` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Cancel = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L14) | Keeps the document open. | — | — | -| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Discard = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L22) | Closes the document and discards unsaved state. | — | — | -| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Save = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L18) | Closes the document after saving. | — | — | -| [`public enum Inno.Editor.Interactions.EditorDocumentCloseMode : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L9) | Selects how a dirty document responds to a close request. | — | — | - -### `Inno.Editor.Interactions.EditorDocumentContext` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -627,23 +533,6 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Interactions.EditorDocumentContext.SetViewParameter(string key, string value)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L76) | Adds or replaces one stable scalar or JSON-formatted view parameter.
`key`:Stable provider-local parameter name.
`value`:Persistent scalar or JSON-formatted value. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDocumentContext.TryGetViewParameter(string key, out string value)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L97) | Tries to read one stable view parameter.
返回:true when the parameter exists.
`key`:Stable provider-local parameter name.
`value`:Receives the stored value. | — | — | -| [`public System.Guid Inno.Editor.Interactions.EditorDocumentContext.assetId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L35) | Gets the persistent asset identity, or an empty value before the source has one. | — | — | -| [`public string Inno.Editor.Interactions.EditorDocumentContext.assetPath { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L40) | Gets the normalized project asset path. | — | — | -| [`public System.Guid Inno.Editor.Interactions.EditorDocumentContext.documentId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L30) | Gets the stable identity of this open source document. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDocumentContext.isDirty { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L55) | Gets whether unsaved source or staged changes exist. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDocumentContext.isProviderAvailable { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L60) | Gets whether a current-generation provider is available. | — | — | -| [`public string Inno.Editor.Interactions.EditorDocumentContext.providerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L45) | Gets the stable provider identity used to recover across extension reload. | — | — | -| [`public string Inno.Editor.Interactions.EditorDocumentContext.title { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L50) | Gets or sets the author-facing source title used by diagnostics and dedicated editors. | — | — | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Editor.Interactions.EditorDocumentContext.viewParameters { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L65) | Gets an immutable snapshot of stable view parameters. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDocumentContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L10) | Stores only stable, reload-safe document identity and provider-owned draft parameters. | — | — | - -### `Inno.Editor.Interactions.EditorDocumentProvider` - @@ -653,71 +542,37 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorDocumentProvider.EditorDocumentProvider()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public virtual bool Inno.Editor.Interactions.EditorDocumentProvider.Apply(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L54) | Applies staged authoring changes and saves their asset source.
返回:true when staged changes are applied.
`context`:Stable document context owned by the host. | — | — | -| [`public abstract bool Inno.Editor.Interactions.EditorDocumentProvider.CanOpen(string assetPath)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L24) | Returns whether this provider can open the supplied project asset path.
返回:true when the path is supported.
`assetPath`:Normalized project asset path. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorDocumentProvider.Close(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L77) | Releases transient provider state after a document closes.
`context`:Stable document context owned by the host. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorDocumentProvider.Open(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L32) | Initializes transient provider state for an opened or restored document.
`context`:Stable document context owned by the host. | — | — | -| [`public virtual bool Inno.Editor.Interactions.EditorDocumentProvider.Revert(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L65) | Discards staged authoring changes and reloads the last saved source.
返回:true when the source is restored.
`context`:Stable document context owned by the host. | — | — | -| [`public abstract bool Inno.Editor.Interactions.EditorDocumentProvider.Save(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L43) | Saves all document changes to its asset source.
返回:true when the source is saved.
`context`:Stable document context owned by the host. | — | — | -| [`public abstract string Inno.Editor.Interactions.EditorDocumentProvider.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L13) | Gets the globally stable provider identifier. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorDocumentProvider`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L8) | Implements one reloadable asset-document kind without owning presentation or source identity. | — | — | - -### `Inno.Editor.Interactions.EditorDragContext` - + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDragContext.EditorDragContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, Inno.Editor.Interactions.EditorDragData data)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L33) | Creates a managed editor drag request.
`editor`:The shared passive editor context.
`interactions`:The active interaction entry point.
`area`:The interaction area that produced the source.
`data`:The transient source identity and preview label. | — | `System.ArgumentNullException`:Thrown when editor, interactions, or data is null.
`System.ArgumentException`:Thrown when area is empty. | -| [`public string Inno.Editor.Interactions.EditorDragContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L59) | Gets the source interaction area. | — | — | -| [`public Inno.Editor.Interactions.EditorDragData Inno.Editor.Interactions.EditorDragContext.data { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L64) | Gets the managed drag data. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDragContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L49) | Gets the shared passive editor context. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDragContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L54) | Gets the active interaction entry point. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDragContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L10) | Provides contextual state while beginning an editor drag. | — | — | - -### `Inno.Editor.Interactions.EditorDragData` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDragData.EditorDragData(Inno.Core.Identity.IdentityObject source, string label)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L27) | Creates drag data for a registered identity object.
`source`:The registered source object resolved again for each drop query and delivery.
`label`:The human-readable label used by drag previews. | — | `System.ArgumentNullException`:Thrown when source is null.
`System.InvalidOperationException`:Thrown when source is not registered in a live identity domain. | -| [`public string Inno.Editor.Interactions.EditorDragData.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L46) | Gets the drag preview label. | — | — | -| [`public Inno.Core.Identity.RuntimeIdentity Inno.Editor.Interactions.EditorDragData.sourceIdentity { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L41) | Gets the domain-qualified transient identity written to the native drag protocol. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDragData`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L10) | Contains the transient identity and preview label for one editor drag operation. | — | — | - -### `Inno.Editor.Interactions.EditorDrop` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorDrop.EditorDrop()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L11) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L43) | Executes a delivered drop after a successful compatibility query.
返回:The observable selection and reveal result of the operation.
`context`:The untyped source, target, surface, and requested placement. | — | — | -| [`public abstract Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L32) | Evaluates whether the current managed source may be dropped on the supplied target.
返回:The compatibility state and standard target visual.
`context`:The untyped source, target, surface, and requested placement. | — | — | -| [`public abstract System.Type Inno.Editor.Interactions.EditorDrop.sourceType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L16) | Gets the accepted drag source type. | — | — | -| [`public abstract System.Type Inno.Editor.Interactions.EditorDrop.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L21) | Gets the accepted drop target type. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorDrop`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L11) | Defines one discoverable typed editor drop operation. | — | — | - -### `Inno.Editor.Interactions.EditorDrop`2` - @@ -726,36 +581,19 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorDrop.EditorDrop()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L55) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public override sealed Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L95) | Validates and applies the current editor drag-and-drop interaction atomically.
返回:The validated editor drop result that represents the completed operation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected abstract Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L123) | Executes a delivered drop for the strongly typed source and target.
返回:The observable selection and reveal result of the operation.
`context`:The typed source, target, surface, and requested placement. | — | — | -| [`public override sealed Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L78) | Evaluates the operation's current availability and presentation state.
返回:The validated editor drop status that represents the completed operation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected abstract Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L112) | Evaluates whether the strongly typed source may be dropped on the strongly typed target.
返回:The compatibility state and standard target visual.
`context`:The typed source, target, surface, and requested placement. | — | — | -| [`public override sealed System.Type Inno.Editor.Interactions.EditorDrop.sourceType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L62) | Gets the concrete type handled by this extension implementation. | — | — | -| [`public override sealed System.Type Inno.Editor.Interactions.EditorDrop.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L67) | Gets the concrete type handled by this extension implementation. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorDrop where TSource : Inno.Core.Identity.IdentityObject where TTarget : class : Inno.Editor.Interactions.EditorDrop`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L55) | Defines a typed drop operation for one managed source and target pair. | — | — | - -### `Inno.Editor.Interactions.EditorDropAttribute` - + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDropAttribute.EditorDropAttribute(int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L17) | Creates a drop registration that can participate on any interaction surface.
`priority`:The tie-breaking priority used after source and target specificity. | — | — | -| [`public Inno.Editor.Interactions.EditorDropAttribute.EditorDropAttribute(string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L31) | Creates a drop registration scoped to an exact interaction area.
`area`:The exact target area handled by this registration, or an empty string for any area.
`priority`:The tie-breaking priority used after source and target specificity. | — | — | -| [`public string Inno.Editor.Interactions.EditorDropAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L42) | Gets the optional exact interaction area. | — | — | -| [`public int Inno.Editor.Interactions.EditorDropAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L47) | Gets the tie-breaking priority. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDropAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L9) | Registers a typed editor drop handler for an optional exact area. | — | — | - -### `Inno.Editor.Interactions.EditorDropContext` - @@ -765,59 +603,11 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDropContext.EditorDropContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, Inno.Editor.Interactions.EditorDragData data, Inno.Core.Identity.IdentityObject source, object target, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L44) | Creates a managed drop request.
`editor`:The shared passive editor context.
`interactions`:The active interaction entry point.
`area`:The target interaction area.
`data`:The active identity-based drag data.
`source`:The live source resolved from data for this operation only.
`target`:The managed target object exposed to typed handlers.
`placement`:The requested position relative to the target. | — | `System.ArgumentNullException`:Thrown when editor, interactions, data, source, or target is null.
`System.ArgumentException`:Thrown when area is empty. | -| [`public string Inno.Editor.Interactions.EditorDropContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L76) | Gets the target interaction area. | — | — | -| [`public Inno.Editor.Interactions.EditorDragData Inno.Editor.Interactions.EditorDropContext.data { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L81) | Gets the active managed drag data. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDropContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L66) | Gets the shared passive editor context. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDropContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L71) | Gets the active interaction entry point. | — | — | -| [`public Inno.Editor.Interactions.EditorDropPlacement Inno.Editor.Interactions.EditorDropContext.placement { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L96) | Gets the requested placement relative to the target. | — | — | -| [`public Inno.Core.Identity.IdentityObject Inno.Editor.Interactions.EditorDropContext.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L86) | Gets the live source resolved for this query or delivery operation. | — | — | -| [`public object Inno.Editor.Interactions.EditorDropContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L91) | Gets the managed drop target. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDropContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L11) | Provides contextual state to an editor drop handler. | — | — | - -### `Inno.Editor.Interactions.EditorDropContext`2` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Editor.Interactions.EditorDropContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L167) | Gets the interaction area. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDropContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L157) | Gets the active editor context. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDropContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L162) | Gets the active interaction entry point. | — | — | -| [`public Inno.Editor.Interactions.EditorDropPlacement Inno.Editor.Interactions.EditorDropContext.placement { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L182) | Gets the requested placement. | — | — | -| [`public TSource Inno.Editor.Interactions.EditorDropContext.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L172) | Gets the typed drag source. | — | — | -| [`public TTarget Inno.Editor.Interactions.EditorDropContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L177) | Gets the typed drop target. | — | — | -| [`public Inno.Editor.Interactions.EditorDropContext Inno.Editor.Interactions.EditorDropContext.untyped { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L152) | Gets the untyped drop context. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorDropContext where TSource : class where TTarget : class`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L135) | Provides strongly typed source and target values to a drop operation. | — | — | - -### `Inno.Editor.Interactions.EditorDropPlacement` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorDropPlacement.After = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L26) | Insert after the target. | — | — | -| [`Inno.Editor.Interactions.EditorDropPlacement.Before = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L16) | Insert before the target. | — | — | -| [`Inno.Editor.Interactions.EditorDropPlacement.Into = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L21) | Drop into the target. | — | — | -| [`Inno.Editor.Interactions.EditorDropPlacement.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L11) | No positional placement is requested. | — | — | -| [`public enum Inno.Editor.Interactions.EditorDropPlacement : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L6) | Describes the requested placement relative to an editor drop target. | — | — | - -### `Inno.Editor.Interactions.EditorDropResult` - @@ -831,25 +621,6 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDropResult.EditorDropResult(bool accepted, object? selectionTarget = null, object? revealTarget = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L20) | Creates the observable result of a completed drop operation.
`accepted`:Whether the handler accepted and completed the drop.
`selectionTarget`:An optional object that the originating view should select.
`revealTarget`:An optional object that the originating view should reveal or expand. | — | — | -| [`public static Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDropResult.Accepted(object? selectionTarget = null, object? revealTarget = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L62) | Creates an accepted drop result with optional presentation requests.
返回:An accepted drop result containing the supplied presentation requests.
`selectionTarget`:An optional object that the originating view should select.
`revealTarget`:An optional object that the originating view should reveal or expand. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDropResult.Equals(Inno.Editor.Interactions.EditorDropResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorDropResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorDropResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorDropResult.operator ==(Inno.Editor.Interactions.EditorDropResult left, Inno.Editor.Interactions.EditorDropResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorDropResult.operator !=(Inno.Editor.Interactions.EditorDropResult left, Inno.Editor.Interactions.EditorDropResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorDropResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDropResult.accepted { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L33) | Gets whether the drop was accepted. | — | — | -| [`public static Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDropResult.rejected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L48) | Gets a rejected drop result. | — | — | -| [`public object? Inno.Editor.Interactions.EditorDropResult.revealTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L43) | Gets the optional hierarchy target that should be revealed. | — | — | -| [`public object? Inno.Editor.Interactions.EditorDropResult.selectionTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L38) | Gets the optional target that should become selected. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorDropResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Describes the observable result of a completed editor drop. | — | — | - -### `Inno.Editor.Interactions.EditorDropStatus` - @@ -862,42 +633,21 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorDropStatus.EditorDropStatus(bool canDrop, Inno.Editor.Interactions.EditorDropVisual visual)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L17) | Creates the compatibility and presentation state of a potential drop target.
`canDrop`:Whether the active source may be delivered to the target.
`visual`:The standard visual requested for the target. | — | — | -| [`public static Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDropStatus.Accept(Inno.Editor.Interactions.EditorDropVisual visual = Inno.Editor.Interactions.EditorDropVisual.Highlight)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L49) | Creates an accepted drop status with the requested standard target visual.
返回:An accepted drop status.
`visual`:The standard visual drawn while the compatible source hovers the target. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDropStatus.Equals(Inno.Editor.Interactions.EditorDropStatus other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorDropStatus.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorDropStatus.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorDropStatus.operator ==(Inno.Editor.Interactions.EditorDropStatus left, Inno.Editor.Interactions.EditorDropStatus right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorDropStatus.operator !=(Inno.Editor.Interactions.EditorDropStatus left, Inno.Editor.Interactions.EditorDropStatus right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorDropStatus.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Interactions.EditorDropStatus.canDrop { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L28) | Gets whether the active source may be dropped. | — | — | -| [`public static Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDropStatus.rejected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L38) | Gets an incompatible drop status. | — | — | -| [`public Inno.Editor.Interactions.EditorDropVisual Inno.Editor.Interactions.EditorDropStatus.visual { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L33) | Gets the standard target visual. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorDropStatus : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Describes whether and how a target accepts the active drag. | — | — | - -### `Inno.Editor.Interactions.EditorDropVisual` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorDropVisual.Disabled = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L31) | Draw a disabled target visual. | — | — | -| [`Inno.Editor.Interactions.EditorDropVisual.Highlight = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L16) | Draw a content highlight. | — | — | -| [`Inno.Editor.Interactions.EditorDropVisual.InsertAfter = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L26) | Draw an insertion line after the target. | — | — | -| [`Inno.Editor.Interactions.EditorDropVisual.InsertBefore = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L21) | Draw an insertion line before the target. | — | — | -| [`Inno.Editor.Interactions.EditorDropVisual.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L11) | No visual is drawn. | — | — | -| [`public enum Inno.Editor.Interactions.EditorDropVisual : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L6) | Defines the standard visual used for a compatible drop target. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryAvailability` - + + + + + + + + + @@ -909,95 +659,27 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryAvailability.Available()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L34) | Creates an available transition result.
返回:An availability value that permits the transition. | — | — | -| [`public bool Inno.Editor.Interactions.EditorHistoryAvailability.Equals(Inno.Editor.Interactions.EditorHistoryAvailability other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorHistoryAvailability.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorHistoryAvailability.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorHistoryAvailability.operator ==(Inno.Editor.Interactions.EditorHistoryAvailability left, Inno.Editor.Interactions.EditorHistoryAvailability right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorHistoryAvailability.operator !=(Inno.Editor.Interactions.EditorHistoryAvailability left, Inno.Editor.Interactions.EditorHistoryAvailability right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorHistoryAvailability.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public static Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryAvailability.Unavailable(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L48) | Creates an unavailable transition result.
返回:An availability value that rejects the transition.
`message`:The non-empty diagnostic explaining why the transition is unavailable. | — | `System.ArgumentException`:Thrown when message is empty. | -| [`public bool Inno.Editor.Interactions.EditorHistoryAvailability.isAvailable { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L21) | Gets whether the requested transition is currently available. | — | — | -| [`public string Inno.Editor.Interactions.EditorHistoryAvailability.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L26) | Gets the diagnostic explaining why the transition is unavailable, or an empty string when available. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorHistoryAvailability : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Describes whether a history change can currently move in one direction. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryChange` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorHistoryChange.EditorHistoryChange(string kind, Inno.Editor.Interactions.EditorHistoryPayload payload, string? mergeKey = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L30) | Creates a neutral history change.
`kind`:The stable globally unique handler protocol identifier.
`payload`:The immutable neutral payload interpreted by the handler.
`mergeKey`:An optional stable key used by the handler to merge adjacent changes. | — | `System.ArgumentException`:Thrown when kind is empty.
`System.ArgumentNullException`:Thrown when payload is null. | -| [`public void Inno.Editor.Interactions.EditorHistoryChange.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L73) | Releases the payload storage owned by this history change. | — | — | -| [`public string Inno.Editor.Interactions.EditorHistoryChange.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L44) | Gets the stable globally unique handler protocol identifier. | — | — | -| [`public string? Inno.Editor.Interactions.EditorHistoryChange.mergeKey { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L54) | Gets the optional stable key used to merge adjacent changes to the same logical value. | — | — | -| [`public Inno.Editor.Interactions.EditorHistoryPayload Inno.Editor.Interactions.EditorHistoryChange.payload { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L49) | Gets the immutable neutral payload interpreted by the active handler generation. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryChange : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L8) | Describes one neutral reload-safe mutation interpreted by an attribute-discovered history handler. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryContext` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorHistoryContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L23) | Gets the passive editor context for the active project. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorHistoryContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L28) | Gets the active interaction entry point used for selection and feature coordination. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L10) | Provides current-generation editor services to a history change handler. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryDirection` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorHistoryDirection.Redo = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L16) | Restores the state produced by the committed change. | — | — | -| [`Inno.Editor.Interactions.EditorHistoryDirection.Undo = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L11) | Restores the state that existed before the change was committed. | — | — | -| [`public enum Inno.Editor.Interactions.EditorHistoryDirection : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L6) | Identifies the direction in which an editor history change is being applied. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryHandler` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorHistoryHandler.EditorHistoryHandler()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryHandler.Apply(Inno.Editor.Interactions.EditorHistoryContext context, Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L46) | Atomically applies a neutral history change in the requested direction.
返回:The transition result. A failure must leave the feature in its original state.
`context`:The current-generation editor services.
`change`:The neutral history change.
`direction`:The requested Undo or Redo direction. | — | — | -| [`protected abstract Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryHandler.Query(Inno.Editor.Interactions.EditorHistoryContext context, Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L25) | Determines whether a neutral change can currently transition in the requested direction.
返回:The current transition availability.
`context`:The current-generation editor services.
`change`:The neutral history change.
`direction`:The requested Undo or Redo direction. | — | — | -| [`protected static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryHandler.StateIntegrityFailure(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L85) | Creates a failed result for a transition whose compensation also failed.
返回:A failure that faults the owning history.
`message`:The diagnostic containing both the transition and compensation failures. | — | — | -| [`protected virtual bool Inno.Editor.Interactions.EditorHistoryHandler.TryMerge(Inno.Editor.Interactions.EditorHistoryChange older, Inno.Editor.Interactions.EditorHistoryChange newer, out Inno.Editor.Interactions.EditorHistoryChange? merged)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L67) | Attempts to merge two adjacent changes to the same logical value.
返回:true when merged replaces both input changes.
`older`:The existing older history change.
`newer`:The newly recorded adjacent history change.
`merged`:The independently owned merged change when successful. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorHistoryHandler`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L8) | Interprets neutral editor history payloads for one current-generation feature protocol. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryHandlerAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorHistoryHandlerAttribute.EditorHistoryHandlerAttribute(string kind)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L20) | Creates a history handler registration.
`kind`:The stable globally unique change protocol identifier. | — | `System.ArgumentException`:Thrown when kind is empty. | -| [`public string Inno.Editor.Interactions.EditorHistoryHandlerAttribute.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L29) | Gets the stable globally unique change protocol identifier. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryHandlerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L9) | Registers a stateless editor history handler for one stable change protocol. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryOptions` - @@ -1005,37 +687,12 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorHistoryOptions.EditorHistoryOptions()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public string? Inno.Editor.Interactions.EditorHistoryOptions.cacheDirectory { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L33) | Gets or initializes the optional directory that owns temporary history payloads for this editor session. | — | — | -| [`public int Inno.Editor.Interactions.EditorHistoryOptions.inlinePayloadThreshold { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L28) | Gets or initializes the payload size at which immutable bytes are moved to the temporary disk store. | — | — | -| [`public long Inno.Editor.Interactions.EditorHistoryOptions.maxDiskBytes { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L23) | Gets or initializes the maximum payload bytes retained in the temporary disk store. | — | — | -| [`public int Inno.Editor.Interactions.EditorHistoryOptions.maxEntries { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L13) | Gets or initializes the maximum number of committed top-level entries retained in memory. | — | — | -| [`public long Inno.Editor.Interactions.EditorHistoryOptions.maxResidentBytes { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L18) | Gets or initializes the maximum estimated resident payload bytes retained by the history. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryOptions`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L8) | Configures retention and payload storage for one editor history. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryPayload` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Interactions.EditorHistoryPayload.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L90) | Releases the resident or temporary disk storage owned by this payload. | — | — | -| [`public static Inno.Editor.Interactions.EditorHistoryPayload Inno.Editor.Interactions.EditorHistoryPayload.FromBytes(System.ReadOnlySpan bytes)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L55) | Creates an immutable history payload by copying the supplied bytes.
返回:A new independently owned payload.
`bytes`:The neutral bytes to retain for future Undo and Redo transitions. | — | — | -| [`public byte[] Inno.Editor.Interactions.EditorHistoryPayload.ReadBytes()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L66) | Reads the complete immutable payload into a newly allocated byte array.
返回:A copy of the payload bytes. | — | `System.ObjectDisposedException`:Thrown after the payload has been disposed. | -| [`public bool Inno.Editor.Interactions.EditorHistoryPayload.isStoredOnDisk { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L40) | Gets whether the payload is retained in the temporary disk store instead of resident memory. | — | — | -| [`public long Inno.Editor.Interactions.EditorHistoryPayload.length { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L35) | Gets the number of immutable bytes represented by this payload. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryPayload : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L9) | Owns immutable neutral bytes used by a reload-safe editor history change. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryResult` - @@ -1048,40 +705,63 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public bool Inno.Editor.Interactions.EditorHistoryResult.Equals(Inno.Editor.Interactions.EditorHistoryResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorHistoryResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryResult.Failure(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L59) | Creates a failed history result without changing the owning history stack.
返回:A result representing a rejected or failed transition.
`message`:The non-empty diagnostic explaining why the transition could not complete. | — | `System.ArgumentException`:Thrown when message is empty. | -| [`public override int Inno.Editor.Interactions.EditorHistoryResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorHistoryResult.operator ==(Inno.Editor.Interactions.EditorHistoryResult left, Inno.Editor.Interactions.EditorHistoryResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorHistoryResult.operator !=(Inno.Editor.Interactions.EditorHistoryResult left, Inno.Editor.Interactions.EditorHistoryResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryResult.Success()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L45) | Creates a successful history result.
返回:A result representing a completed transition. | — | — | -| [`public override string Inno.Editor.Interactions.EditorHistoryResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public string Inno.Editor.Interactions.EditorHistoryResult.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L37) | Gets the diagnostic message associated with a failed transition, or an empty string after success. | — | — | -| [`public bool Inno.Editor.Interactions.EditorHistoryResult.statePreserved { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L32) | Gets whether a failed transition restored the domain state that existed before the attempt. | Successful results always preserve a valid state. A failed result with this value set to false faults the owning history because the transition can no longer be retried safely. | — | -| [`public bool Inno.Editor.Interactions.EditorHistoryResult.succeeded { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L23) | Gets whether the requested history transition completed successfully. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorHistoryResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Describes the outcome of applying, undoing, or redoing an editor history operation. | — | — | - -### `Inno.Editor.Interactions.EditorHistoryTransaction` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Interactions.EditorHistoryTransaction.Commit()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L36) | Commits all recorded child operations as one atomic history entry. | — | `System.InvalidOperationException`:Thrown when the transaction is no longer active. | -| [`public void Inno.Editor.Interactions.EditorHistoryTransaction.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L66) | Rolls back an uncommitted transaction before releasing it. | — | — | -| [`public Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryTransaction.Rollback()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L52) | Reverts every operation recorded by the transaction and does not add a history entry.
返回:The result of the atomic rollback attempt. | — | `System.InvalidOperationException`:Thrown when the transaction is no longer active. | -| [`public string Inno.Editor.Interactions.EditorHistoryTransaction.name { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L26) | Gets the user-facing name assigned to the transaction. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorHistoryTransaction : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L8) | Collects several history operations into one atomic undo and redo entry. | — | — | - -### `Inno.Editor.Interactions.EditorInteraction` - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1094,36 +774,13 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Core.Identity.RuntimeIdentity Inno.Editor.Interactions.EditorInteraction.BeginDrag(Inno.Editor.Interactions.EditorDragData data)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L205) | Begins a managed drag originating from this area.
返回:The source object's domain-qualified transient identity.
`data`:The managed source data. | — | — | -| [`public Inno.Editor.Interactions.EditorMenuModel Inno.Editor.Interactions.EditorInteraction.BuildMenu()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L154) | Builds the complete contextual menu for this area and target.
返回:The immutable resolved menu model. | — | — | -| [`public Inno.Editor.Interactions.EditorToolbarModel Inno.Editor.Interactions.EditorInteraction.BuildToolbar()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L162) | Builds the complete compact toolbar for this area and target.
返回:The immutable resolved toolbar model. | — | — | -| [`public Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorInteraction.Drop(Inno.Core.Identity.RuntimeIdentity identity, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L237) | Delivers a managed drop to this target.
返回:The completed drop result.
`identity`:The active domain-qualified runtime identity.
`placement`:The requested placement relative to the target. | — | — | -| [`public void Inno.Editor.Interactions.EditorInteraction.Enqueue(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L109) | Queues an action until the current UI traversal completes.
`action`:The stable action name.
`argument`:An optional action argument. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public bool Inno.Editor.Interactions.EditorInteraction.Execute(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L92) | Executes an action for this area and target.
返回:true when a visible and enabled action executed; otherwise, false.
`action`:The stable action name.
`argument`:An optional action argument. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public void Inno.Editor.Interactions.EditorInteraction.Focus()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L44) | Marks this area and target as the active keyboard context. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteraction.IsActive(string action)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L146) | Gets whether an action owns an active multi-frame operation for this target.
返回:true when the action is active for this exact target; otherwise, false.
`action`:The stable action name. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public bool Inno.Editor.Interactions.EditorInteraction.Present(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L129) | Presents an active action in place of this target's normal content.
返回:true when the action presented content; otherwise, false.
`action`:The stable action name.
`argument`:Optional presentation data supplied by the current view. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorInteraction.Query(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L72) | Queries an action for this area and target.
返回:The current action state.
`action`:The stable action name.
`argument`:An optional action argument. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorInteraction.QueryDrop(Inno.Core.Identity.RuntimeIdentity identity, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L219) | Queries this handle as a drop target.
返回:The drop compatibility and visual state.
`identity`:The active domain-qualified runtime identity.
`placement`:The requested placement relative to the target. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteraction.Select()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L52) | Selects this target, or clears selection when the target is null.
返回:true when the built-in selection action executed; otherwise, false. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteraction.TryGetActiveDragIdentity(out Inno.Core.Identity.RuntimeIdentity identity)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L194) | Gets the currently active drag identity, if its source remains registered.
返回:true when a live drag source exists; otherwise, false.
`identity`:The active domain-qualified runtime identity when available. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteraction.TryGetShortcut(string action, out Inno.Editor.Interactions.HotKeyGesture gesture)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L179) | Resolves the shortcut displayed for an action in this area.
返回:true when a compatible shortcut exists; otherwise, false.
`action`:The stable action name.
`gesture`:The resolved gesture when successful. | — | `System.ArgumentException`:Thrown when action is empty. | -| [`public string Inno.Editor.Interactions.EditorInteraction.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L28) | Gets the stable interaction area. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteraction.isSelected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L38) | Gets whether this handle's target is the current editor selection. | — | — | -| [`public object? Inno.Editor.Interactions.EditorInteraction.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L33) | Gets the optional target represented by this handle. | — | — | -| [`public readonly struct Inno.Editor.Interactions.EditorInteraction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L10) | Provides a lightweight fluent handle for one interaction area and optional target. | — | — | - -### `Inno.Editor.Interactions.EditorInteractionRuntime` - @@ -1139,195 +796,37 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IEnumerable hostServices)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L98) | Creates an interaction runtime with stable host-owned extension services.
`context`:The passive editor context shared with the presentation backend.
`hostServices`:Stable host-owned services that editor extension constructors may request by assignable contract.
`logs`:The host-owned logging router used by editor infrastructure.
`types`:The type catalog that owns editor extension generations. | — | `System.ArgumentNullException`:Thrown when context or hostServices is null. | -| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L71) | Creates an interaction runtime for an existing passive editor context.
`context`:The passive editor context shared with the presentation backend.
`types`:The type catalog that owns editor extension generations.
`logs`:The host-owned logging router used by editor infrastructure. | — | `System.ArgumentNullException`:Thrown when context or logs is null. | -| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(string projectDirectory, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, Inno.Editor.Core.EditorKeyboardPolicy keyboard)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L46) | Creates an interaction runtime for one project.
`projectDirectory`:The project root containing Assets and Library.
`types`:The type catalog that owns editor extension generations.
`logs`:The host-owned logging router used by editor infrastructure.
`keyboard`:The explicit immutable product keyboard conventions. | — | — | -| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L248) | Releases the resources owned by this implementation. | — | `System.AggregateException`:Thrown after all shutdown stages have been attempted when one or more stages failed.
`Inno.Core.Execution.RetirementPendingException`:A stage still owns live work. This runtime retains all remaining stages and rejects new updates. | -| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.Flush()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L194) | Flushes actions queued during the current presentation traversal. | — | — | -| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.HandleKeyPressed(Inno.Core.Events.KeyPressedEvent keyEvent)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L232) | Dispatches an unhandled keyboard event through contextual shortcuts.
`keyEvent`:The keyboard event received from the application event stream. | — | — | -| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.PrepareShutdown()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L220) | Freezes automatic extension-state persistence and writes the final state before modules begin shutting down. | This operation is idempotent. After it succeeds, later disposal cannot recapture transient teardown state such as an empty scene workspace. | `System.ObjectDisposedException`:Thrown after this runtime has been disposed. | -| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.SaveState()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L203) | Captures every stateful active module and panel and atomically flushes changed project state to disk. | — | `System.ObjectDisposedException`:Thrown after this runtime has been disposed. | -| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Start()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L162) | Starts value processing after validating the current state. | — | — | -| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L179) | Recomputes owned state from the current validated inputs.
`frame`:The frame consumed by update; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorInteractionRuntime.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L128) | Gets the active presentation-independent interaction entry point. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorInteractionRuntime.modals { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L145) | Gets active modal extensions in deterministic order. | — | — | -| [`public int Inno.Editor.Interactions.EditorInteractionRuntime.panelCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L157) | Gets the number of active dockable panels. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorInteractionRuntime.panels { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L133) | Gets active dockable panel extensions in deterministic order. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorInteractionRuntime : Inno.Editor.Core.EditorRuntime`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L17) | Hosts attribute-discovered editor extensions without depending on a presentation backend. | — | — | - -### `Inno.Editor.Interactions.EditorInteractions` - + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.IDisposable Inno.Editor.Interactions.EditorInteractions.BeginHistoryIsolation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L108) | Starts an isolated temporary Undo and Redo branch while retaining the current editing branch.
返回:A scope that restores the retained history branch when disposed. | Disposing the returned scope releases every temporary operation and restores the retained branch. This host-level boundary is intended for transient editor sessions such as Play Mode. | `System.InvalidOperationException`:Thrown during an Undo, Redo, transaction, or another isolated branch. | -| [`public bool Inno.Editor.Interactions.EditorInteractions.ClosePanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L284) | Closes one panel in the active extension generation without toggling its current state.
返回:true when an available panel was found.
`panelId`:The stable panel identifier to resolve. | — | `System.ArgumentException`:Thrown when panelId is empty. | -| [`public Inno.Editor.Interactions.EditorInteraction Inno.Editor.Interactions.EditorInteractions.For(string area, object? target = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L137) | Creates a lightweight interaction handle for one area and optional target.
返回:A lightweight interaction handle.
`area`:The stable interaction area.
`target`:The optional object represented by the area. | — | `System.ArgumentException`:Thrown when area is empty. | -| [`public bool Inno.Editor.Interactions.EditorInteractions.OpenPanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L266) | Opens and requests presentation focus for one panel in the active extension generation.
返回:true when an available panel was found.
`panelId`:The stable panel identifier to resolve. | — | `System.ArgumentException`:Thrown when panelId is empty. | -| [`public void Inno.Editor.Interactions.EditorInteractions.SetSelection(object? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L148) | Replaces the editor selection after closing presentations owned by other targets.
`target`:The target to select, or null to clear the selection. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TogglePanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L197) | Toggles one panel in the currently active extension generation.
返回:true when an available panel was found and toggled.
`panelId`:The stable panel identifier to resolve. | — | `System.ArgumentException`:Thrown when panelId is empty. | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetActiveDragIdentity(out Inno.Core.Identity.RuntimeIdentity identity)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L183) | Gets the active drag identity used to select the matching native payload domain.
返回:true when a live drag source exists; otherwise, false.
`identity`:The active runtime identity when one remains valid. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetDragSource(Inno.Core.Identity.RuntimeIdentity identity, out Inno.Core.Identity.IdentityObject? source)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L169) | Resolves the live source for an active runtime identity.
返回:true when the identity is active and resolves to a live source; otherwise, false.
`identity`:The domain-qualified runtime identity carried by the drag protocol.
`source`:The currently registered drag source when resolution succeeds. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetModule(out TModule? module) where TModule : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L86) | Resolves an active feature module for immediate use in the current Editor callback.
返回:True when the active generation supplies the requested module.
`module`:The started, non-quarantined module, or null when unavailable. | Do not retain the result across callbacks or generation changes. Candidate modules are never exposed. | — | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TryResolveIdentity(Inno.Core.Identity.IdentityDomainId domainId, System.Guid persistentId, out Inno.Core.Identity.IdentityObject? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L242) | Resolves a persistent identity through one explicitly selected Editor identity domain.
返回:true when the stable identity resolves in the requested domain.
`domainId`:Identity domain that owns the object and any replacement generation.
`persistentId`:Stable object identity to resolve inside that domain.
`target`:Receives the current live object, or null when the domain or object is unavailable. | — | — | -| [`public bool Inno.Editor.Interactions.EditorInteractions.TryResolveIdentity(Inno.Core.Identity.RuntimeIdentity identity, out Inno.Core.Identity.IdentityObject? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L216) | Resolves a domain-qualified runtime identity through the Editor's complete identity-domain set.
返回:true when the exact identity resolves in its owning domain.
`identity`:Runtime identity captured from a live Editor target.
`target`:Receives the current live object, or null when its domain or generation is unavailable. | — | — | -| [`public Inno.Editor.Interactions.IEditorDocumentService Inno.Editor.Interactions.EditorInteractions.documents { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L69) | Gets the headless reload-safe document lifetime used by dedicated asset editors and Inspectors. | — | — | -| [`public string Inno.Editor.Interactions.EditorInteractions.focusedArea { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L115) | Gets the area that most recently received keyboard focus. | — | — | -| [`public object? Inno.Editor.Interactions.EditorInteractions.focusedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L120) | Gets the target associated with the focused area. | — | — | -| [`public Inno.Editor.Interactions.IEditorHistory Inno.Editor.Interactions.EditorInteractions.history { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L64) | Gets the transactional Undo and Redo history owned by this editor runtime. | — | — | -| [`public Inno.Editor.Interactions.EditorSelectionState Inno.Editor.Interactions.EditorInteractions.selection { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L57) | Gets the shared read-only editor selection state. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorInteractions : Inno.Editor.Interactions.IEditorSelectionCoordinator, Inno.Editor.Interactions.IEditorHistoryIsolation`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L18) | Provides the single presentation-independent entry point for editor actions, menus, selection, focus, and drag-and-drop. | — | — | - -### `Inno.Editor.Interactions.EditorMenuAttribute` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorMenuAttribute.EditorMenuAttribute(string area, string path, int order = 0, bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L29) | Creates a static placement for the annotated action on an exact menu surface.
`area`:The exact interaction area whose menu receives the action.
`path`:The slash-delimited path used to create parent menus and the leaf label.
`order`:The stable ordering value among sibling placements.
`separatorBefore`:Whether a visual separator should precede the leaf placement. | — | `System.ArgumentException`:Thrown when area or path is empty. | -| [`public string Inno.Editor.Interactions.EditorMenuAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L48) | Gets the menu area. | — | — | -| [`public int Inno.Editor.Interactions.EditorMenuAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L58) | Gets the stable menu ordering value. | — | — | -| [`public string Inno.Editor.Interactions.EditorMenuAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L53) | Gets the slash-delimited menu path. | — | — | -| [`public bool Inno.Editor.Interactions.EditorMenuAttribute.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L63) | Gets whether a separator is rendered before the item. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L9) | Places an editor action at an arbitrary path on a menu surface. | — | — | - -### `Inno.Editor.Interactions.EditorMenuBuilder` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorMenuBuilder.EditorMenuBuilder()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Editor.Interactions.EditorMenuBuilder.Add(string path, string actionId, int order = 0, bool separatorBefore = false, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L57) | Adds a dynamic action placement to the menu currently being constructed.
`path`:The slash-delimited path used to create parent menus and the leaf label.
`actionId`:The stable action identifier invoked by the leaf.
`order`:The stable ordering value among sibling placements.
`separatorBefore`:Whether a visual separator should precede the leaf placement.
`argument`:An optional argument captured for this menu generation. | — | `System.ArgumentException`:Thrown when path or actionId is empty. | -| [`public void Inno.Editor.Interactions.EditorMenuBuilder.AddGroup(string path, int order = 0, bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L26) | Declares presentation for an intermediate menu group without inventing a no-op action.
`path`:The slash-delimited path of the group.
`order`:The stable ordering value among sibling groups and items.
`separatorBefore`:Whether a separator precedes this group at its own level. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuBuilder`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L9) | Collects dynamic menu item placements. | — | — | - -### `Inno.Editor.Interactions.EditorMenuContext` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorMenuContext.EditorMenuContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, object? target = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L33) | Creates a contextual menu request.
`editor`:The shared passive editor context.
`interactions`:The active interaction entry point.
`area`:The exact menu area being requested.
`target`:The optional object the menu operates on. | — | `System.ArgumentNullException`:Thrown when editor or interactions is null.
`System.ArgumentException`:Thrown when area is empty. | -| [`public string Inno.Editor.Interactions.EditorMenuContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L59) | Gets the requested menu area. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorMenuContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L49) | Gets the shared passive editor context. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorMenuContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L54) | Gets the active interaction entry point. | — | — | -| [`public object? Inno.Editor.Interactions.EditorMenuContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L64) | Gets the contextual menu target. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L10) | Provides contextual state while constructing an editor menu. | — | — | - -### `Inno.Editor.Interactions.EditorMenuItem` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Editor.Interactions.EditorMenuItem.actionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L64) | Gets the stable action identifier consumed by a presentation backend. | — | — | -| [`public object? Inno.Editor.Interactions.EditorMenuItem.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L90) | Gets the generation-local action argument consumed by a presentation backend. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorMenuItem.children { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L84) | Gets child menu nodes. | — | — | -| [`public string Inno.Editor.Interactions.EditorMenuItem.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L58) | Gets the visible node label. | — | — | -| [`public int Inno.Editor.Interactions.EditorMenuItem.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L69) | Gets the stable ordering value. | — | — | -| [`public bool Inno.Editor.Interactions.EditorMenuItem.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L74) | Gets whether a separator precedes this node. | — | — | -| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorMenuItem.status { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L79) | Gets the current command presentation state. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuItem`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L11) | Represents one immutable contextual menu node. | — | — | - -### `Inno.Editor.Interactions.EditorMenuModel` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorMenuModel.EditorMenuModel(System.Collections.Generic.IReadOnlyList? items)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L17) | Creates a complete immutable menu tree from resolved root nodes.
`items`:The root nodes in stable display order, or null for an empty menu. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorMenuModel.items { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L25) | Gets root menu nodes in display order. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuModel`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L9) | Contains a complete immutable menu tree. | — | — | - -### `Inno.Editor.Interactions.EditorMenuSource` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorMenuSource.EditorMenuSource()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract void Inno.Editor.Interactions.EditorMenuSource.Build(Inno.Editor.Interactions.EditorMenuContext context, Inno.Editor.Interactions.EditorMenuBuilder builder)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L19) | Adds context-dependent placements for the supplied menu request.
`context`:The requested menu surface, target, and shared editor context.
`builder`:The collector that receives dynamic action placements. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorMenuSource`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L8) | Contributes dynamic entries to one or more editor menu surfaces. | — | — | - -### `Inno.Editor.Interactions.EditorMenuSourceAttribute` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorMenuSourceAttribute.EditorMenuSourceAttribute(string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L23) | Creates a dynamic menu-source registration for an exact interaction surface.
`area`:The exact menu area that receives placements from the source.
`priority`:The ordering priority used when multiple dynamic sources contribute to the surface. | — | `System.ArgumentException`:Thrown when area is empty. | -| [`public string Inno.Editor.Interactions.EditorMenuSourceAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L36) | Gets the contributed menu area. | — | — | -| [`public int Inno.Editor.Interactions.EditorMenuSourceAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L41) | Gets the provider ordering priority. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorMenuSourceAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L9) | Registers a dynamic editor menu source. | — | — | - -### `Inno.Editor.Interactions.EditorModalExtension` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L95) | Safely draws the modal body and quarantines a failing extension instance.
返回:true when the modal completed drawing without being quarantined.
`context`:The active editor context supplied by the presentation backend. | — | `System.ArgumentNullException`:Thrown when context is null. | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.TryGetPresentation(out Inno.Editor.Interactions.EditorModalExtension.Presentation presentation)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L55) | Safely captures generation-local modal presentation values.
返回:true when every presentation value was read successfully.
`presentation`:The captured immutable presentation, or its default value when the modal is quarantined. | — | — | -| [`public string Inno.Editor.Interactions.EditorModalExtension.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L34) | Gets the stable modal identifier. | — | — | -| [`public int Inno.Editor.Interactions.EditorModalExtension.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L44) | Gets the stable modal ordering value. | — | — | -| [`public string Inno.Editor.Interactions.EditorModalExtension.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L39) | Gets the visible modal title. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorModalExtension`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L11) | Describes one active modal extension. | — | — | - -### `Inno.Editor.Interactions.EditorModalExtension.Presentation` - @@ -1343,27 +842,6 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorModalExtension.Presentation.Presentation(bool isVisible, bool blocksInteraction, bool canMove, bool canResize, bool allowScrolling, System.Numerics.Vector2 initialSize, System.Numerics.Vector2 minimumSize)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L142) | Creates an immutable modal presentation snapshot.
`isVisible`:Whether the modal should currently be visible.
`blocksInteraction`:Whether the modal blocks regular editor interaction.
`canMove`:Whether the modal window can be moved.
`canResize`:Whether the modal window can be resized.
`allowScrolling`:Whether the modal window owns overflow scrolling.
`initialSize`:The initial size in unscaled editor units.
`minimumSize`:The minimum size in unscaled editor units. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.Equals(Inno.Editor.Interactions.EditorModalExtension.Presentation other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorModalExtension.Presentation.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorModalExtension.Presentation.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorModalExtension.Presentation.operator ==(Inno.Editor.Interactions.EditorModalExtension.Presentation left, Inno.Editor.Interactions.EditorModalExtension.Presentation right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorModalExtension.Presentation.operator !=(Inno.Editor.Interactions.EditorModalExtension.Presentation left, Inno.Editor.Interactions.EditorModalExtension.Presentation right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorModalExtension.Presentation.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L183) | Gets whether overflow scrolling belongs to the modal window. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.blocksInteraction { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L168) | Gets whether the modal blocks regular editor interaction. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.canMove { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L173) | Gets whether the modal window can be moved. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.canResize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L178) | Gets whether the modal window can be resized. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.Interactions.EditorModalExtension.Presentation.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L188) | Gets the initial size in unscaled editor units. | — | — | -| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L163) | Gets whether the modal should currently be visible. | — | — | -| [`public System.Numerics.Vector2 Inno.Editor.Interactions.EditorModalExtension.Presentation.minimumSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L193) | Gets the minimum size in unscaled editor units. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorModalExtension.Presentation : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Stores immutable modal window policy values read from one extension generation. | — | — | - -### `Inno.Editor.Interactions.EditorPanelExtension` - @@ -1372,90 +850,24 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public bool Inno.Editor.Interactions.EditorPanelExtension.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L118) | Safely draws the panel body and quarantines a failing extension instance.
返回:true when the panel completed drawing without being quarantined.
`context`:The active editor context supplied by the presentation backend. | — | `System.ArgumentNullException`:Thrown when context is null. | -| [`public bool Inno.Editor.Interactions.EditorPanelExtension.TakeFocusRequest()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L64) | Consumes a pending request to focus this panel window.
返回:true once for each accepted focus request. | — | — | -| [`public bool Inno.Editor.Interactions.EditorPanelExtension.TryGetWindowPresentation(out bool useWindowPadding, out bool allowScrolling, out System.Numerics.Vector2 initialSize)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L81) | Safely reads the panel window-presentation policy through the active extension boundary.
返回:true when the policy was read without quarantining the panel.
`useWindowPadding`:The requested padding policy, or the safe default when the panel is quarantined.
`allowScrolling`:The requested scrolling policy, or the safe default when the panel is quarantined.
`initialSize`:The preferred first-use floating size, or zero when native sizing is requested. | — | — | -| [`public string Inno.Editor.Interactions.EditorPanelExtension.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L37) | Gets the stable panel identifier. | — | — | -| [`public bool Inno.Editor.Interactions.EditorPanelExtension.isOpen { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L52) | Gets or sets whether this panel is open in the current extension generation. | — | — | -| [`public int Inno.Editor.Interactions.EditorPanelExtension.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L47) | Gets the stable panel ordering value. | — | — | -| [`public string Inno.Editor.Interactions.EditorPanelExtension.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L42) | Gets the visible panel title. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorPanelExtension`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L11) | Describes one active dockable panel extension. | — | — | - -### `Inno.Editor.Interactions.EditorPlanarNavigation` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorPlanarNavigation.EditorPlanarNavigation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Editor.Interactions.EditorPlanarNavigation.Cancel()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L67) | Releases capture when a canvas closes or a gesture is cancelled. | — | — | -| [`public bool Inno.Editor.Interactions.EditorPlanarNavigation.Update(bool hovered, bool primaryPressed, bool middlePressed, bool primaryDown, bool middleDown, bool alt, bool allowAltPrimary = true)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L45) | Advances pointer capture using logical button state rather than window-local release events.
返回:Whether this canvas owns the current pointer gesture.
`hovered`:Whether a new gesture may start in this canvas.
`primaryPressed`:Whether the primary button was pressed this frame.
`middlePressed`:Whether the middle button was pressed this frame.
`primaryDown`:Current global primary button state.
`middleDown`:Current global middle button state.
`alt`:Whether Alt is held when the gesture starts.
`allowAltPrimary`:Whether Alt-primary belongs to pan rather than another navigation mode. | — | — | -| [`public static float Inno.Editor.Interactions.EditorPlanarNavigation.WheelFactor(float wheel, float sensitivity = 0.16)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L84) | Converts wheel input to the common exponential magnification used by Editor canvases.
返回:A finite positive magnification factor; positive wheel magnifies content.
`wheel`:Logical wheel units, independent of framebuffer DPI.
`sensitivity`:Positive exponential sensitivity. | — | `System.ArgumentOutOfRangeException`:Input is non-finite or sensitivity is not positive. | -| [`public static System.Numerics.Vector2 Inno.Editor.Interactions.EditorPlanarNavigation.ZoomOrigin(System.Numerics.Vector2 origin, System.Numerics.Vector2 pivot, float previousScale, float nextScale)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L116) | Changes the screen-space origin so zoom preserves the same content point beneath the cursor.
返回:The origin preserving the pivot's content coordinate.
`origin`:Current logical-pixel content origin.
`pivot`:Logical-pixel cursor position relative to the canvas.
`previousScale`:Previous positive content magnification.
`nextScale`:New positive content magnification after clamping. | — | `System.ArgumentOutOfRangeException`:An input is non-finite or a scale is not positive. | -| [`public bool Inno.Editor.Interactions.EditorPlanarNavigation.isPanning { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L16) | Gets whether this canvas owns a pan gesture, including while the pointer is outside its bounds. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorPlanarNavigation`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L9) | Shares captured planar gestures and mouse-anchored zoom math between scene and graph canvases. | — | — | - -### `Inno.Editor.Interactions.EditorPresentationAction`2` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorPresentationAction.EditorPresentationAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L346) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract bool Inno.Editor.Interactions.EditorPresentationAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L371) | Presents active content using strongly typed presentation data.
返回:true when content was presented.
`context`:The typed presentation request. | — | — | -| [`protected override sealed bool Inno.Editor.Interactions.EditorPresentationAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L358) | Presents this action through the current editor interaction surface.
返回:true when the operation succeeds or its condition is satisfied; otherwise, false.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorPresentationAction where TTarget : class : Inno.Editor.Interactions.EditorAction where TTarget : class`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L346) | Defines a target action with a typed presentation-only argument. | — | — | - -### `Inno.Editor.Interactions.EditorSelectionState` - + + + + + + + + + + + + + + - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorSelectionState.EditorSelectionState()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public bool Inno.Editor.Interactions.EditorSelectionState.TryGet(out TTarget? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs#L67) | Tries to read the current target as a requested type.
返回:true when the current target is compatible.
`target`:Typed target when successful. | — | — | -| [`public object? Inno.Editor.Interactions.EditorSelectionState.selectedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs#L16) | Gets the selected target, or null when nothing is selected. | — | — | -| [`public ulong Inno.Editor.Interactions.EditorSelectionState.version { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs#L21) | Gets a monotonically increasing selection change version. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorSelectionState`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs#L9) | Stores editor-wide object selection state. | — | — | - -### `Inno.Editor.Interactions.EditorShortcutAttribute` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorShortcutAttribute.EditorShortcutAttribute(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool primary = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L25) | Creates a shortcut that can be dispatched from any focused interaction surface.
`key`:The non-modifier key that triggers the action.
`modifiers`:The exact additional modifier keys required by the gesture.
`primary`:Whether the platform primary modifier, Command on macOS or Control elsewhere, is required. | — | — | -| [`public Inno.Editor.Interactions.EditorShortcutAttribute.EditorShortcutAttribute(string area, Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool primary = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L49) | Creates a shortcut scoped to an exact interaction area.
`area`:The exact focused area that accepts the shortcut, or an empty string for any area.
`key`:The non-modifier key that triggers the action.
`modifiers`:The exact additional modifier keys required by the gesture.
`primary`:Whether the platform primary modifier, Command on macOS or Control elsewhere, is required. | — | — | -| [`public string Inno.Editor.Interactions.EditorShortcutAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L64) | Gets the optional exact interaction area. | — | — | -| [`public Inno.Core.Input.KeyCode Inno.Editor.Interactions.EditorShortcutAttribute.key { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L69) | Gets the shortcut key. | — | — | -| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorShortcutAttribute.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L74) | Gets additional shortcut modifiers. | — | — | -| [`public bool Inno.Editor.Interactions.EditorShortcutAttribute.primary { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L79) | Gets whether the platform primary modifier is required. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorShortcutAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L11) | Associates an editor action with a keyboard shortcut. | — | — | - -### `Inno.Editor.Interactions.EditorToolbarIcon` - @@ -1463,37 +875,12 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorToolbarIcon.Edit = 5`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L36) | Returns to an editing state. | — | — | -| [`Inno.Editor.Interactions.EditorToolbarIcon.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L11) | No symbol is requested. | — | — | -| [`Inno.Editor.Interactions.EditorToolbarIcon.Pause = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L26) | Pauses an operation or simulation. | — | — | -| [`Inno.Editor.Interactions.EditorToolbarIcon.Play = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L16) | Starts an operation or simulation. | — | — | -| [`Inno.Editor.Interactions.EditorToolbarIcon.Step = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L31) | Advances a paused operation by one step. | — | — | -| [`Inno.Editor.Interactions.EditorToolbarIcon.Stop = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L21) | Stops an operation or simulation. | — | — | -| [`public enum Inno.Editor.Interactions.EditorToolbarIcon : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L6) | Identifies a presentation-independent symbol for an editor toolbar command. | — | — | - -### `Inno.Editor.Interactions.EditorToolbarItem` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Editor.Interactions.EditorToolbarItem.actionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L25) | Gets the stable action dispatched when this item is pressed. | — | — | -| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItem.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L30) | Gets the resolved presentation-independent symbol. | — | — | -| [`public int Inno.Editor.Interactions.EditorToolbarItem.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L40) | Gets the stable ordering value. | — | — | -| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorToolbarItem.status { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L45) | Gets the current action availability and checked state. | — | — | -| [`public string Inno.Editor.Interactions.EditorToolbarItem.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L35) | Gets the resolved contextual tooltip. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorToolbarItem`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L6) | Describes one resolved toolbar command. | — | — | - -### `Inno.Editor.Interactions.EditorToolbarItemAttribute` - @@ -1501,61 +888,20 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorToolbarItemAttribute.EditorToolbarItemAttribute(string area, Inno.Editor.Interactions.EditorToolbarIcon icon, string tooltip, int order = 0, Inno.Editor.Interactions.EditorToolbarIcon activeIcon = Inno.Editor.Interactions.EditorToolbarIcon.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L37) | Creates a toolbar placement for the annotated targetless action.
`area`:The exact interaction area whose toolbar receives the action.
`icon`:The symbol shown while the action is not checked.
`tooltip`:The fallback tooltip shown when the action has no contextual display name.
`order`:The stable ordering value within the toolbar.
`activeIcon`:The symbol shown while the action is checked, or Inno.Editor.Interactions.EditorToolbarIcon.None to retain icon. | — | `System.ArgumentException`:Thrown when area or tooltip is empty, or when icon is Inno.Editor.Interactions.EditorToolbarIcon.None.
`System.ArgumentOutOfRangeException`:Thrown when an icon value is not defined. | -| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItemAttribute.activeIcon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L75) | Gets the optional replacement symbol shown while the action is checked. | — | — | -| [`public string Inno.Editor.Interactions.EditorToolbarItemAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L65) | Gets the exact toolbar interaction area. | — | — | -| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItemAttribute.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L70) | Gets the symbol shown while the action is not checked. | — | — | -| [`public int Inno.Editor.Interactions.EditorToolbarItemAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L85) | Gets the stable ordering value. | — | — | -| [`public string Inno.Editor.Interactions.EditorToolbarItemAttribute.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L80) | Gets the fallback tooltip. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorToolbarItemAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L9) | Places an editor action on a compact toolbar surface. | — | — | - -### `Inno.Editor.Interactions.EditorToolbarModel` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorToolbarModel.items { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarModel.cs#L18) | Gets visible toolbar commands in deterministic display order. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorToolbarModel`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarModel.cs#L8) | Contains an immutable resolved toolbar for one interaction area. | — | — | - -### `Inno.Editor.Interactions.EditorValidationResult` - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorValidationResult.EditorValidationResult(bool isValid, string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Describes whether a requested editor operation is valid and carries a diagnostic when it is rejected.
`isValid`:Whether the requested operation may proceed.
`message`:The user-facing validation diagnostic, or an empty string for a valid result. | — | — | -| [`public void Inno.Editor.Interactions.EditorValidationResult.Deconstruct(out bool isValid, out string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Interactions.EditorValidationResult.Equals(Inno.Editor.Interactions.EditorValidationResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorValidationResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorValidationResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static Inno.Editor.Interactions.EditorValidationResult Inno.Editor.Interactions.EditorValidationResult.Invalid(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L30) | Creates a failed validation result with a user-facing diagnostic.
返回:An invalid result containing the supplied diagnostic.
`message`:The diagnostic that explains why the operation cannot proceed. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorValidationResult.operator ==(Inno.Editor.Interactions.EditorValidationResult left, Inno.Editor.Interactions.EditorValidationResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorValidationResult.operator !=(Inno.Editor.Interactions.EditorValidationResult left, Inno.Editor.Interactions.EditorValidationResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorValidationResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Interactions.EditorValidationResult.isValid { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L13) | Whether the requested operation may proceed. | — | — | -| [`public string Inno.Editor.Interactions.EditorValidationResult.message { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L14) | The user-facing validation diagnostic, or an empty string for a valid result. | — | — | -| [`public static Inno.Editor.Interactions.EditorValidationResult Inno.Editor.Interactions.EditorValidationResult.valid { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L19) | Gets a successful validation result. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorValidationResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Describes whether a requested editor operation is valid and carries a diagnostic when it is rejected.
`isValid`:Whether the requested operation may proceed.
`message`:The user-facing validation diagnostic, or an empty string for a valid result. | — | — | - -### `Inno.Editor.Interactions.EditorViewportCursor` - + + + + + + + + + + + + @@ -1564,20 +910,17 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorViewportCursor.Arrow = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L38) | Uses the normal arrow cursor. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.Crosshair = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L42) | Uses a precise crosshair. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.Hand = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L46) | Uses a hand suitable for panning. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.Hidden = 6`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L62) | Hides the pointer while the tool owns it. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.Move = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L50) | Uses four-direction movement arrows. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.ResizeHorizontal = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L54) | Uses horizontal resizing arrows. | — | — | -| [`Inno.Editor.Interactions.EditorViewportCursor.ResizeVertical = 5`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L58) | Uses vertical resizing arrows. | — | — | -| [`public enum Inno.Editor.Interactions.EditorViewportCursor : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L33) | Selects a presentation-independent cursor requested by a viewport tool. | — | — | - -### `Inno.Editor.Interactions.EditorViewportPointerEvent` - + + + + + + + + + + + @@ -1592,70 +935,9 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorViewportPointerEvent.EditorViewportPointerEvent(int pointerId, Inno.Editor.Interactions.EditorViewportPointerPhase phase, Inno.Core.Mathematics.Vector2 screenPosition, Inno.Core.Mathematics.Vector2 worldPosition, int button, Inno.Core.Input.KeyModifier modifiers)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L134) | Creates one viewport pointer sample.
`pointerId`:Platform pointer identity.
`phase`:Gesture lifecycle phase.
`screenPosition`:Viewport-local pixel position.
`worldPosition`:Corresponding world position.
`button`:Platform button number.
`modifiers`:Active keyboard modifiers. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportPointerEvent.Equals(Inno.Editor.Interactions.EditorViewportPointerEvent other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorViewportPointerEvent.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorViewportPointerEvent.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorViewportPointerEvent.operator ==(Inno.Editor.Interactions.EditorViewportPointerEvent left, Inno.Editor.Interactions.EditorViewportPointerEvent right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorViewportPointerEvent.operator !=(Inno.Editor.Interactions.EditorViewportPointerEvent left, Inno.Editor.Interactions.EditorViewportPointerEvent right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorViewportPointerEvent.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public int Inno.Editor.Interactions.EditorViewportPointerEvent.button { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L173) | Gets the platform button number. | — | — | -| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorViewportPointerEvent.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L178) | Gets active keyboard modifiers. | — | — | -| [`public Inno.Editor.Interactions.EditorViewportPointerPhase Inno.Editor.Interactions.EditorViewportPointerEvent.phase { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L158) | Gets the gesture lifecycle phase. | — | — | -| [`public int Inno.Editor.Interactions.EditorViewportPointerEvent.pointerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L153) | Gets the platform pointer identity. | — | — | -| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportPointerEvent.screenPosition { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L163) | Gets the viewport-local pixel position. | — | — | -| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportPointerEvent.worldPosition { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L168) | Gets the corresponding world position. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorViewportPointerEvent : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Contains one immutable viewport pointer sample in screen and world coordinates. | — | — | - -### `Inno.Editor.Interactions.EditorViewportPointerPhase` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Cancel = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L27) | The platform cancelled the gesture. | — | — | -| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Down = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L15) | The pointer button began a gesture. | — | — | -| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Move = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L19) | The pointer moved or changed hover position. | — | — | -| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Up = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L23) | The pointer button ended a gesture. | — | — | -| [`public enum Inno.Editor.Interactions.EditorViewportPointerPhase : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L10) | Selects the lifecycle phase of one viewport pointer sample. | — | — | - -### `Inno.Editor.Interactions.EditorViewportShortcut` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorViewportShortcut.EditorViewportShortcut(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool repeat = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L82) | Creates one viewport shortcut sample.
`key`:Pressed key.
`modifiers`:Active keyboard modifiers.
`repeat`:Whether the platform generated an auto-repeat press. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportShortcut.Equals(Inno.Editor.Interactions.EditorViewportShortcut other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.EditorViewportShortcut.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.EditorViewportShortcut.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.EditorViewportShortcut.operator ==(Inno.Editor.Interactions.EditorViewportShortcut left, Inno.Editor.Interactions.EditorViewportShortcut right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.EditorViewportShortcut.operator !=(Inno.Editor.Interactions.EditorViewportShortcut left, Inno.Editor.Interactions.EditorViewportShortcut right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Interactions.EditorViewportShortcut.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public Inno.Core.Input.KeyCode Inno.Editor.Interactions.EditorViewportShortcut.key { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L95) | Gets the pressed key. | — | — | -| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorViewportShortcut.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L100) | Gets active keyboard modifiers. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportShortcut.repeat { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L105) | Gets whether the shortcut is an auto-repeat press. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.EditorViewportShortcut : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Contains one presentation-independent shortcut sample for a focused viewport tool. | — | — | - -### `Inno.Editor.Interactions.EditorViewportTool` - + + + @@ -1666,22 +948,6 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Interactions.EditorViewportTool.EditorViewportTool()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L212) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.DrawOverlay(Inno.Editor.Interactions.EditorViewportToolContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L316) | Draws transient gizmos and diagnostics over the viewport.
`context`:Gesture and coordinate context. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerCancel(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L281) | Handles platform cancellation of an active gesture.
`context`:Gesture and coordinate context.
`pointer`:Immutable pointer sample. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerDown(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L233) | Handles a pointer press.
`context`:Gesture and coordinate context.
`pointer`:Immutable pointer sample. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerMove(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L249) | Handles pointer movement or hover.
`context`:Gesture and coordinate context.
`pointer`:Immutable pointer sample. | — | — | -| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerUp(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L265) | Handles a pointer release.
`context`:Gesture and coordinate context.
`pointer`:Immutable pointer sample. | — | — | -| [`public virtual bool Inno.Editor.Interactions.EditorViewportTool.OnShortcut(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportShortcut shortcut)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L302) | Handles a focused viewport shortcut before global action routing.
返回:true when the tool consumed the key.
`context`:Gesture and coordinate context.
`shortcut`:Pressed key sample. | — | — | -| [`public virtual Inno.Editor.Interactions.EditorViewportCursor Inno.Editor.Interactions.EditorViewportTool.cursor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L222) | Gets the cursor requested while this tool is active. | — | — | -| [`public abstract string Inno.Editor.Interactions.EditorViewportTool.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L217) | Gets the globally stable tool identity. | — | — | -| [`public abstract class Inno.Editor.Interactions.EditorViewportTool`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L212) | Defines one presentation-independent, reloadable viewport editing tool. | — | — | - -### `Inno.Editor.Interactions.EditorViewportToolContext` - @@ -1692,22 +958,6 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Interactions.EditorViewportToolContext.BeginHistoryGesture(string name)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L68) | Begins the only history transaction allowed for the current captured gesture.
`name`:User-facing Undo operation name. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolContext.CapturePointer(int pointerId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L45) | Captures one pointer until explicit release or cancellation.
`pointerId`:Platform pointer identity. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolContext.CompleteHistoryGesture(bool commit)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L84) | Commits or rolls back the current gesture transaction exactly once.
`commit`:true to commit; otherwise, roll back. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolContext.ReleasePointer()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L55) | Releases the currently captured pointer. | — | — | -| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportToolContext.ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L106) | Converts viewport-local pixel coordinates to world coordinates.
返回:World coordinates.
`screenPosition`:Viewport-local pixel coordinates. | — | — | -| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportToolContext.WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L117) | Converts world coordinates to viewport-local pixel coordinates.
返回:Viewport-local pixel coordinates.
`worldPosition`:World coordinates. | — | — | -| [`public int? Inno.Editor.Interactions.EditorViewportToolContext.capturedPointerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L32) | Gets the captured pointer identity, or null. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportToolContext.hasHistoryGesture { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L37) | Gets whether a history transaction is active for the current gesture. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportToolContext.hasPointerCapture { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L27) | Gets whether this tool owns a pointer. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorViewportToolContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L9) | Coordinates pointer capture, transforms, and one history transaction for a viewport gesture. | — | — | - -### `Inno.Editor.Interactions.EditorViewportToolSession` - @@ -1718,157 +968,577 @@ Scene 内容历史由独立的 [Inno.Editor.Scene](Inno.Editor.Scene.md) 实现 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.EditorViewportToolSession.EditorViewportToolSession(Inno.Editor.Interactions.IEditorHistory history, Inno.Editor.Interactions.IEditorViewportCoordinateConverter coordinates)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L23) | Creates a viewport tool session.
`history`:History stack used by gesture transactions.
`coordinates`:Current viewport coordinate converter. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolSession.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L115) | Cancels active gesture state and deactivates the tool. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolSession.DrawOverlay()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L106) | Draws the active tool overlay. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportToolSession.HandlePointer(Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L64) | Routes one immutable pointer sample.
返回:true when an active tool accepted the sample.
`pointer`:Pointer sample with precomputed world coordinates. | — | — | -| [`public bool Inno.Editor.Interactions.EditorViewportToolSession.HandleShortcut(Inno.Editor.Interactions.EditorViewportShortcut shortcut)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L97) | Routes one focused keyboard shortcut.
返回:true when consumed.
`shortcut`:Pressed key sample. | — | — | -| [`public void Inno.Editor.Interactions.EditorViewportToolSession.SetTool(Inno.Editor.Interactions.EditorViewportTool? tool)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L46) | Activates a tool after cancelling any current gesture.
`tool`:Tool to activate, or null to deactivate. | — | — | -| [`public Inno.Editor.Interactions.EditorViewportCursor Inno.Editor.Interactions.EditorViewportToolSession.cursor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L38) | Gets the requested cursor for the active tool. | — | — | -| [`public Inno.Editor.Interactions.EditorViewportTool? Inno.Editor.Interactions.EditorViewportToolSession.tool { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L33) | Gets the active tool, or null. | — | — | -| [`public sealed class Inno.Editor.Interactions.EditorViewportToolSession : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L8) | Routes focused viewport input to one active tool while enforcing pointer-capture ownership. | — | — | - -### `Inno.Editor.Interactions.HotKeyGesture` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Interactions.HotKeyGesture.HotKeyGesture(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, string superModifierLabel = "Super")`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L31) | Creates a keyboard gesture with an exact set of modifier keys after symbolic-key normalization.
`key`:The non-modifier key in the gesture.
`modifiers`:The modifier keys required by the gesture.
`superModifierLabel`:The display name of the Super modifier; the neutral name is used for explicitly constructed gestures. | — | `System.ArgumentException`:The modifier label is empty. | -| [`public bool Inno.Editor.Interactions.HotKeyGesture.Equals(Inno.Editor.Interactions.HotKeyGesture other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Interactions.HotKeyGesture.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Interactions.HotKeyGesture.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Interactions.HotKeyGesture.operator ==(Inno.Editor.Interactions.HotKeyGesture left, Inno.Editor.Interactions.HotKeyGesture right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Interactions.HotKeyGesture.operator !=(Inno.Editor.Interactions.HotKeyGesture left, Inno.Editor.Interactions.HotKeyGesture right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static Inno.Editor.Interactions.HotKeyGesture Inno.Editor.Interactions.HotKeyGesture.Primary(Inno.Core.Input.KeyCode key, Inno.Editor.Core.EditorKeyboardPolicy keyboard, Inno.Core.Input.KeyModifier additionalModifiers = Inno.Core.Input.KeyModifier.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L67) | Creates a gesture that includes the product-selected primary modifier.
返回:A product-resolved keyboard gesture.
`key`:The non-modifier key in the gesture.
`keyboard`:The product-selected immutable keyboard policy.
`additionalModifiers`:Additional modifiers combined with the product-selected primary modifier. | — | — | -| [`public override string Inno.Editor.Interactions.HotKeyGesture.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L82) | Formats the gesture as a human-readable editor menu shortcut label.
返回:A product-resolved textual representation of the gesture. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorAction : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L8) | Defines one automatically discovered editor operation and owns its complete multi-frame lifecycle. | — | — | +| [`public bool Inno.Editor.Interactions.EditorAction.isActive { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L17) | Gets whether the action currently owns an active multi-frame operation. | — | — | +| [`public virtual System.Type? Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L21) | Gets the required target type, or for a targetless action. | — | — | +| [`protected object? Inno.Editor.Interactions.EditorAction.activeTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L71) | Resolves the active target afresh for this callback; null means the identity is unavailable. | — | — | +| [`protected Inno.Editor.Core.EditorContext? Inno.Editor.Interactions.EditorAction.activeEditorContext { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L75) | Gets the host-owned passive context of the active operation, or null after completion. | — | — | +| [`protected void Inno.Editor.Interactions.EditorAction.Activate(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L86) | Activates this action for a target and cancels any operation it previously owned.
`context`: The contextual request that starts the operation. | — | `System.ArgumentNullException`: Thrown when is . | +| [`protected void Inno.Editor.Interactions.EditorAction.Complete()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L99) | Completes the current operation and returns the action to its idle state. | — | — | +| [`protected void Inno.Editor.Interactions.EditorAction.Cancel()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L113) | Cancels the current operation and returns the action to its idle state. | — | — | +| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L133) | Evaluates the action for the supplied context.
`context`: The editor, area, target, and optional argument for the query. | The current presentation and availability state. | — | +| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L140) | Executes the action for the supplied context.
`context`: The editor, area, target, and optional argument for the operation. | — | — | +| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L150) | Presents an active action at the current target location.
`context`: The current presentation area and target. | when the action replaced the target's normal content; otherwise, . | — | +| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnCompleted()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L154) | Runs after an active operation completes successfully. | — | — | +| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnCancelled()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L161) | Runs after an active operation is cancelled. | — | — | +| [`protected virtual void Inno.Editor.Interactions.EditorAction.OnPresentationLost()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L173) | Runs when this action's active target loses editor presentation focus. | — | — | +| [`public void Inno.Editor.Interactions.EditorAction.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L177) | Cancels active work and releases this action instance. | — | — | +| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorAction : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L196) | Defines a target action that requires one strongly typed command argument.
`TTarget`: The target type accepted by the action.
`TArgument`: The command argument type accepted by the action. | — | — | +| [`public override sealed System.Type Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L201) | Gets the concrete type handled by this extension implementation. | — | — | +| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L213) | Evaluates the operation's current availability and presentation state.
`context`: The context that supplies state and services for this operation. | The validated editor action state that represents the completed operation. | — | +| [`protected override sealed void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L223) | Applies the editor action to the supplied interaction context.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override sealed bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L242) | Presents this action through the current editor interaction surface.
`context`: The context that supplies state and services for this operation. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L252) | Evaluates the action for a typed target and argument.
`context`: The typed contextual request. | The current presentation and availability state. | — | +| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L259) | Executes the action for a typed target and argument.
`context`: The typed contextual request. | — | — | +| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L269) | Presents an active action for a typed target and argument.
`context`: The typed presentation request. | when content was presented. | — | +| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L196) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorArgumentAction : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L291) | Defines a targetless editor action that requires one strongly typed command argument.
`TArgument`: The command argument type accepted by the action. | — | — | +| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorArgumentAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L304) | Evaluates the operation's current availability and presentation state.
`context`: The context that supplies state and services for this operation. | The validated editor action state that represents the completed operation. | — | +| [`protected override sealed void Inno.Editor.Interactions.EditorArgumentAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L312) | Applies the editor action to the supplied interaction context.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorArgumentAction.Query(Inno.Editor.Interactions.EditorActionArgumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L331) | Evaluates the action for a typed argument.
`context`: The typed contextual request. | The current presentation and availability state. | — | +| [`protected abstract void Inno.Editor.Interactions.EditorArgumentAction.Execute(Inno.Editor.Interactions.EditorActionArgumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L338) | Executes the action for a typed argument.
`context`: The typed contextual request. | — | — | +| [`protected Inno.Editor.Interactions.EditorArgumentAction.EditorArgumentAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L291) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorPresentationAction : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L350) | Defines a target action with a typed presentation-only argument.
`TTarget`: The target type accepted by the action.
`TPresentation`: The presentation data type. | — | — | +| [`protected override sealed bool Inno.Editor.Interactions.EditorPresentationAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L361) | Presents this action through the current editor interaction surface.
`context`: The context that supplies state and services for this operation. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`protected abstract bool Inno.Editor.Interactions.EditorPresentationAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L372) | Presents active content using strongly typed presentation data.
`context`: The typed presentation request. | when content was presented. | — | +| [`protected Inno.Editor.Interactions.EditorPresentationAction.EditorPresentationAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L350) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorAction : Inno.Editor.Interactions.EditorAction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L381) | Defines a discoverable action whose target must be assignable to a specific reference type.
`TTarget`: The target type accepted by the action. | — | — | +| [`public override sealed System.Type Inno.Editor.Interactions.EditorAction.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L386) | Gets the concrete type handled by this extension implementation. | — | — | +| [`protected override sealed Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L397) | Evaluates the operation's current availability and presentation state.
`context`: The context that supplies state and services for this operation. | The validated editor action state that represents the completed operation. | — | +| [`protected override sealed void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L409) | Applies the editor action to the supplied interaction context.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override sealed bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L429) | Presents this action through the current editor interaction surface.
`context`: The context that supplies state and services for this operation. | when the operation succeeds or its condition is satisfied; otherwise, . | — | +| [`protected virtual Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorAction.Query(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L444) | Evaluates the action for a strongly typed target.
`context`: The contextual request containing the typed target. | The current presentation and availability state. | — | +| [`protected abstract void Inno.Editor.Interactions.EditorAction.Execute(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L451) | Executes the action for a strongly typed target.
`context`: The contextual request containing the typed target. | — | — | +| [`protected virtual bool Inno.Editor.Interactions.EditorAction.Present(Inno.Editor.Interactions.EditorActionContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L461) | Presents an active action for a strongly typed target.
`context`: The current presentation request containing the typed target. | when content was presented; otherwise, . | — | +| [`protected Inno.Editor.Interactions.EditorAction.EditorAction()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs#L381) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorActionAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L9) | Registers an editor action for automatic discovery and dispatch. | — | — | +| [`public Inno.Editor.Interactions.EditorActionAttribute.EditorActionAttribute(string action, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L23) | Creates an action registration available from every area.
`action`: The stable semantic action name.
`priority`: The tie-breaking priority used after target specificity. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Interactions.EditorActionAttribute.EditorActionAttribute(string action, string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L46) | Creates an action registration restricted to one exact area.
`action`: The stable semantic action name.
`area`: The exact area, or an empty string for every area.
`priority`: The tie-breaking priority. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Interactions.EditorActionAttribute.action { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L61) | Gets the stable action name. | — | — | +| [`public string Inno.Editor.Interactions.EditorActionAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L66) | Gets the optional exact interaction area. | — | — | +| [`public int Inno.Editor.Interactions.EditorActionAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionAttribute.cs#L71) | Gets the tie-breaking priority. | — | — | +| [`public class Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L8) | Provides contextual state to an editor action. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorActionContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L52) | Gets the shared passive editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorActionContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L56) | Gets the active interaction entry point. | — | — | +| [`public Inno.Editor.Interactions.IEditorHistory Inno.Editor.Interactions.EditorActionContext.history { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L60) | Gets the transactional history used to record reversible mutations performed by this action. | — | — | +| [`public string Inno.Editor.Interactions.EditorActionContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L64) | Gets the stable interaction area. | — | — | +| [`public object? Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L68) | Gets the contextual action target. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorActionContext : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L81) | Provides a strongly typed target to an editor action implementation.
`TTarget`: The target type required by the action. | — | — | +| [`public TTarget Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L94) | Gets the strongly typed action target. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorActionContext : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L106) | Provides a strongly typed target and action argument to an editor action.
`TTarget`: The target type required by the action.
`TArgument`: The action argument type required by the action. | — | — | +| [`public TTarget Inno.Editor.Interactions.EditorActionContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L121) | Gets the strongly typed action target. | — | — | +| [`public TArgument Inno.Editor.Interactions.EditorActionContext.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L125) | Gets the strongly typed action argument. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorActionArgumentContext : Inno.Editor.Interactions.EditorActionContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L134) | Provides a strongly typed action argument to a targetless editor action.
`TArgument`: The action argument type required by the action. | — | — | +| [`public TArgument Inno.Editor.Interactions.EditorActionArgumentContext.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs#L147) | Gets the strongly typed action argument. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorActionState : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Describes the current presentation and availability of an editor action. | — | — | +| [`public Inno.Editor.Interactions.EditorActionState.EditorActionState(bool isVisible, bool isEnabled, bool isChecked = false, string? displayName = null, string? unavailableReason = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L25) | Creates the contextual presentation state of an editor action.
`isVisible`: Whether the action should be included in the current presentation.
`isEnabled`: Whether the action can execute in the current context.
`isChecked`: Whether the action is currently presented as checked.
`displayName`: An optional contextual label that replaces the static placement label.
`unavailableReason`: A concise explanation shown when an otherwise visible action cannot execute. | — | — | +| [`public bool Inno.Editor.Interactions.EditorActionState.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L42) | Gets whether the action should be displayed. | — | — | +| [`public bool Inno.Editor.Interactions.EditorActionState.isEnabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L46) | Gets whether the action can execute. | — | — | +| [`public bool Inno.Editor.Interactions.EditorActionState.isChecked { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L50) | Gets whether the action is currently checked. | — | — | +| [`public string? Inno.Editor.Interactions.EditorActionState.displayName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L54) | Gets an optional contextual display name. | — | — | +| [`public string? Inno.Editor.Interactions.EditorActionState.unavailableReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L58) | Gets the current execution barrier, or null when no specific explanation is available. | — | — | +| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.enabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L62) | Gets a visible and enabled action state. | — | — | +| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.disabled { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L66) | Gets a visible but disabled action state. | — | — | +| [`public static Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorActionState.hidden { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L70) | Gets a hidden action state. | — | — | +| [`public override string Inno.Editor.Interactions.EditorActionState.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorActionState.operator !=(Inno.Editor.Interactions.EditorActionState left, Inno.Editor.Interactions.EditorActionState right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorActionState.operator ==(Inno.Editor.Interactions.EditorActionState left, Inno.Editor.Interactions.EditorActionState right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorActionState.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorActionState.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorActionState.Equals(Inno.Editor.Interactions.EditorActionState other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs#L5) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Interactions.EditorShortcutAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L11) | Associates an editor action with a keyboard shortcut. | — | — | +| [`public Inno.Editor.Interactions.EditorShortcutAttribute.EditorShortcutAttribute(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool primary = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L25) | Creates a shortcut that can be dispatched from any focused interaction surface.
`key`: The non-modifier key that triggers the action.
`modifiers`: The exact additional modifier keys required by the gesture.
`primary`: Whether the platform primary modifier, Command on macOS or Control elsewhere, is required. | — | — | +| [`public Inno.Editor.Interactions.EditorShortcutAttribute.EditorShortcutAttribute(string area, Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool primary = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L49) | Creates a shortcut scoped to an exact interaction area.
`area`: The exact focused area that accepts the shortcut, or an empty string for any area.
`key`: The non-modifier key that triggers the action.
`modifiers`: The exact additional modifier keys required by the gesture.
`primary`: Whether the platform primary modifier, Command on macOS or Control elsewhere, is required. | — | — | +| [`public string Inno.Editor.Interactions.EditorShortcutAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L64) | Gets the optional exact interaction area. | — | — | +| [`public Inno.Core.Input.KeyCode Inno.Editor.Interactions.EditorShortcutAttribute.key { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L69) | Gets the shortcut key. | — | — | +| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorShortcutAttribute.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L74) | Gets additional shortcut modifiers. | — | — | +| [`public bool Inno.Editor.Interactions.EditorShortcutAttribute.primary { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorShortcutAttribute.cs#L79) | Gets whether the platform primary modifier is required. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorValidationResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Describes whether a requested editor operation is valid and carries a diagnostic when it is rejected.
`isValid`: Whether the requested operation may proceed.
`message`: The user-facing validation diagnostic, or an empty string for a valid result. | — | — | +| [`public Inno.Editor.Interactions.EditorValidationResult.EditorValidationResult(bool isValid, string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Describes whether a requested editor operation is valid and carries a diagnostic when it is rejected.
`isValid`: Whether the requested operation may proceed.
`message`: The user-facing validation diagnostic, or an empty string for a valid result. | — | — | +| [`public bool Inno.Editor.Interactions.EditorValidationResult.isValid { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L13) | Whether the requested operation may proceed. | — | — | +| [`public string Inno.Editor.Interactions.EditorValidationResult.message { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L14) | The user-facing validation diagnostic, or an empty string for a valid result. | — | — | +| [`public static Inno.Editor.Interactions.EditorValidationResult Inno.Editor.Interactions.EditorValidationResult.valid { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L19) | Gets a successful validation result. | — | — | +| [`public static Inno.Editor.Interactions.EditorValidationResult Inno.Editor.Interactions.EditorValidationResult.Invalid(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L30) | Creates a failed validation result with a user-facing diagnostic.
`message`: The diagnostic that explains why the operation cannot proceed. | An invalid result containing the supplied diagnostic. | — | +| [`public override string Inno.Editor.Interactions.EditorValidationResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorValidationResult.operator !=(Inno.Editor.Interactions.EditorValidationResult left, Inno.Editor.Interactions.EditorValidationResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorValidationResult.operator ==(Inno.Editor.Interactions.EditorValidationResult left, Inno.Editor.Interactions.EditorValidationResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorValidationResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorValidationResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorValidationResult.Equals(Inno.Editor.Interactions.EditorValidationResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Interactions.EditorValidationResult.Deconstruct(out bool isValid, out string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorValidationResult.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.HotKeyGesture : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Describes one keyboard gesture used to invoke an editor command. | — | — | +| [`public Inno.Editor.Interactions.HotKeyGesture.HotKeyGesture(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, string superModifierLabel = "Super")`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L31) | Creates a keyboard gesture with an exact set of modifier keys after symbolic-key normalization.
`key`: The non-modifier key in the gesture.
`modifiers`: The modifier keys required by the gesture.
`superModifierLabel`: The display name of the Super modifier; the neutral name is used for explicitly constructed gestures. | — | `System.ArgumentException`: The modifier label is empty. | | [`public Inno.Core.Input.KeyCode Inno.Editor.Interactions.HotKeyGesture.key { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L45) | Gets the main key. | — | — | | [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.HotKeyGesture.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L50) | Gets the required modifiers. | — | — | -| [`public readonly record struct Inno.Editor.Interactions.HotKeyGesture : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Describes one keyboard gesture used to invoke an editor command. | — | — | - -### `Inno.Editor.Interactions.IEditorDocumentService` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.Apply(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L122) | Applies staged changes through the current provider.
返回:true when changes are applied.
`documentId`:Stable tab identity. | — | — | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.Close(System.Guid documentId, Inno.Editor.Interactions.EditorDocumentCloseMode mode)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L147) | Closes one document using an explicit dirty-document decision.
返回:true when the document closed.
`documentId`:Stable tab identity.
`mode`:Explicit close behavior. | — | — | -| [`Inno.Editor.Interactions.EditorDocumentContext Inno.Editor.Interactions.IEditorDocumentService.Open(string assetPath, System.Guid assetId = default(System.Guid))`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L58) | Opens a document or returns its existing single instance.
返回:The stable document context.
`assetPath`:Project asset path.
`assetId`:Persistent asset identity when available. | — | — | -| [`System.IDisposable Inno.Editor.Interactions.IEditorDocumentService.RegisterProvider(Inno.Editor.Interactions.EditorDocumentProvider provider)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L44) | Registers one current-generation document provider.
返回:A lease that detaches only this provider instance.
`provider`:Provider instance to register. | — | — | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.Revert(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L133) | Reverts staged changes through the current provider.
返回:true when changes are reverted.
`documentId`:Stable tab identity. | — | — | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.Save(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L103) | Saves one open document through its current provider.
返回:true when the document is saved.
`documentId`:Stable tab identity. | — | — | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.SaveAll()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L111) | Saves every dirty open document.
返回:true when every dirty document is saved. | — | — | -| [`void Inno.Editor.Interactions.IEditorDocumentService.SetDirty(System.Guid documentId, bool isDirty = true)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L89) | Updates a document's unsaved state without saving, discarding or changing its History.
`documentId`:Stable tab identity.
`isDirty`:Whether the provider's current draft differs from its saved baseline. | — | — | -| [`bool Inno.Editor.Interactions.IEditorDocumentService.UpdateAssetPath(System.Guid documentId, string assetPath)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L75) | Updates an open document's source location after an identity-preserving asset move, without changing history or focus.
返回:Whether the document exists.
`documentId`:Existing document identity.
`assetPath`:Current authoritative source location for the same persistent asset. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.IEditorDocumentService.documents { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L33) | Gets the currently owned source documents. | — | — | +| [`public static Inno.Editor.Interactions.HotKeyGesture Inno.Editor.Interactions.HotKeyGesture.Primary(Inno.Core.Input.KeyCode key, Inno.Editor.Core.EditorKeyboardPolicy keyboard, Inno.Core.Input.KeyModifier additionalModifiers = Inno.Core.Input.KeyModifier.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L67) | Creates a gesture that includes the product-selected primary modifier.
`key`: The non-modifier key in the gesture.
`keyboard`: The product-selected immutable keyboard policy.
`additionalModifiers`: Additional modifiers combined with the product-selected primary modifier. | A product-resolved keyboard gesture. | — | +| [`public override string Inno.Editor.Interactions.HotKeyGesture.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L82) | Formats the gesture as a human-readable editor menu shortcut label. | A product-resolved textual representation of the gesture. | — | +| [`public static bool Inno.Editor.Interactions.HotKeyGesture.operator !=(Inno.Editor.Interactions.HotKeyGesture left, Inno.Editor.Interactions.HotKeyGesture right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.HotKeyGesture.operator ==(Inno.Editor.Interactions.HotKeyGesture left, Inno.Editor.Interactions.HotKeyGesture right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.HotKeyGesture.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.HotKeyGesture.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.HotKeyGesture.Equals(Inno.Editor.Interactions.HotKeyGesture other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs#L12) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Interactions.EditorDocumentContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L10) | Stores only stable, reload-safe document identity and provider-owned draft parameters. | — | — | +| [`public System.Guid Inno.Editor.Interactions.EditorDocumentContext.documentId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L30) | Gets the stable identity of this open source document. | — | — | +| [`public System.Guid Inno.Editor.Interactions.EditorDocumentContext.assetId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L35) | Gets the persistent asset identity, or an empty value before the source has one. | — | — | +| [`public string Inno.Editor.Interactions.EditorDocumentContext.assetPath { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L40) | Gets the normalized project asset path. | — | — | +| [`public string Inno.Editor.Interactions.EditorDocumentContext.providerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L45) | Gets the stable provider identity used to recover across extension reload. | — | — | +| [`public string Inno.Editor.Interactions.EditorDocumentContext.title { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L50) | Gets or sets the author-facing source title used by diagnostics and dedicated editors. | — | — | +| [`public bool Inno.Editor.Interactions.EditorDocumentContext.isDirty { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L55) | Gets whether unsaved source or staged changes exist. | — | — | +| [`public bool Inno.Editor.Interactions.EditorDocumentContext.isProviderAvailable { get; internal set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L60) | Gets whether a current-generation provider is available. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Editor.Interactions.EditorDocumentContext.viewParameters { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L65) | Gets an immutable snapshot of stable view parameters. | — | — | +| [`public void Inno.Editor.Interactions.EditorDocumentContext.SetViewParameter(string key, string value)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L76) | Adds or replaces one stable scalar or JSON-formatted view parameter.
`key`: Stable provider-local parameter name.
`value`: Persistent scalar or JSON-formatted value. | — | — | +| [`public bool Inno.Editor.Interactions.EditorDocumentContext.TryGetViewParameter(string key, out string value)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentContext.cs#L97) | Tries to read one stable view parameter.
`key`: Stable provider-local parameter name.
`value`: Receives the stored value. | when the parameter exists. | — | +| [`public abstract class Inno.Editor.Interactions.EditorDocumentProvider`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L8) | Implements one reloadable asset-document kind without owning presentation or source identity. | — | — | +| [`public abstract string Inno.Editor.Interactions.EditorDocumentProvider.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L13) | Gets the globally stable provider identifier. | — | — | +| [`public abstract bool Inno.Editor.Interactions.EditorDocumentProvider.CanOpen(string assetPath)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L24) | Returns whether this provider can open the supplied project asset path.
`assetPath`: Normalized project asset path. | when the path is supported. | — | +| [`public virtual void Inno.Editor.Interactions.EditorDocumentProvider.Open(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L32) | Initializes transient provider state for an opened or restored document.
`context`: Stable document context owned by the host. | — | — | +| [`public abstract bool Inno.Editor.Interactions.EditorDocumentProvider.Save(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L43) | Saves all document changes to its asset source.
`context`: Stable document context owned by the host. | when the source is saved. | — | +| [`public virtual bool Inno.Editor.Interactions.EditorDocumentProvider.Apply(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L54) | Applies staged authoring changes and saves their asset source.
`context`: Stable document context owned by the host. | when staged changes are applied. | — | +| [`public virtual bool Inno.Editor.Interactions.EditorDocumentProvider.Revert(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L65) | Discards staged authoring changes and reloads the last saved source.
`context`: Stable document context owned by the host. | when the source is restored. | — | +| [`public virtual void Inno.Editor.Interactions.EditorDocumentProvider.Close(Inno.Editor.Interactions.EditorDocumentContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L77) | Releases transient provider state after a document closes.
`context`: Stable document context owned by the host. | — | — | +| [`protected Inno.Editor.Interactions.EditorDocumentProvider.EditorDocumentProvider()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentProvider.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public enum Inno.Editor.Interactions.EditorDocumentCloseMode : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L9) | Selects how a dirty document responds to a close request. | — | — | +| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Cancel = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L14) | Keeps the document open. | — | — | +| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Save = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L18) | Closes the document after saving. | — | — | +| [`Inno.Editor.Interactions.EditorDocumentCloseMode.Discard = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L22) | Closes the document and discards unsaved state. | — | — | | [`public interface Inno.Editor.Interactions.IEditorDocumentService`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L28) | Owns single-instance editor documents independently from reloadable providers and presentation. | — | — | - -### `Inno.Editor.Interactions.IEditorHistory` - - - - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Interactions.EditorHistoryTransaction Inno.Editor.Interactions.IEditorHistory.BeginTransaction(string name)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L67) | Begins an atomic group of neutral history operations.
返回:A transaction that must be committed or rolled back.
`name`:The user-facing grouped operation name. | — | — | -| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Execute(string name, Inno.Editor.Interactions.EditorHistoryChange change)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L81) | Applies and records a neutral change through its current-generation handler.
返回:The result of applying the change in the Redo direction.
`name`:The user-facing operation name.
`change`:The independently owned neutral change. | — | — | -| [`void Inno.Editor.Interactions.IEditorHistory.RecordApplied(string name, Inno.Editor.Interactions.EditorHistoryChange change)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L95) | Records a neutral change whose domain mutation is already applied.
`name`:The user-facing operation name.
`change`:The independently owned neutral change. | — | — | -| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Redo()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L114) | Attempts to reapply the newest reverted operation.
返回:The transition result. A state-preserving failure remains available for retry. | — | — | -| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Undo()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L106) | Attempts to restore the state preceding the newest committed operation.
返回:The transition result. A state-preserving failure remains available for retry. | — | — | -| [`bool Inno.Editor.Interactions.IEditorHistory.canRedo { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L16) | Gets whether a Redo transition is currently available. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.IEditorDocumentService.documents { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L33) | Gets the currently owned source documents. | — | — | +| [`System.IDisposable Inno.Editor.Interactions.IEditorDocumentService.RegisterProvider(Inno.Editor.Interactions.EditorDocumentProvider provider)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L44) | Registers one current-generation document provider.
`provider`: Provider instance to register. | A lease that detaches only this provider instance. | — | +| [`Inno.Editor.Interactions.EditorDocumentContext Inno.Editor.Interactions.IEditorDocumentService.Open(string assetPath, System.Guid assetId = default(System.Guid))`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L58) | Opens a document or returns its existing single instance.
`assetPath`: Project asset path.
`assetId`: Persistent asset identity when available. | The stable document context. | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.UpdateAssetPath(System.Guid documentId, string assetPath)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L75) | Updates an open document's source location after an identity-preserving asset move, without changing history or focus.
`documentId`: Existing document identity.
`assetPath`: Current authoritative source location for the same persistent asset. | Whether the document exists. | — | +| [`void Inno.Editor.Interactions.IEditorDocumentService.SetDirty(System.Guid documentId, bool isDirty = true)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L89) | Updates a document's unsaved state without saving, discarding or changing its History.
`documentId`: Stable tab identity.
`isDirty`: Whether the provider's current draft differs from its saved baseline. | — | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.Save(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L103) | Saves one open document through its current provider.
`documentId`: Stable tab identity. | when the document is saved. | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.SaveAll()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L111) | Saves every dirty open document. | when every dirty document is saved. | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.Apply(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L122) | Applies staged changes through the current provider.
`documentId`: Stable tab identity. | when changes are applied. | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.Revert(System.Guid documentId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L133) | Reverts staged changes through the current provider.
`documentId`: Stable tab identity. | when changes are reverted. | — | +| [`bool Inno.Editor.Interactions.IEditorDocumentService.Close(System.Guid documentId, Inno.Editor.Interactions.EditorDocumentCloseMode mode)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Documents/IEditorDocumentService.cs#L147) | Closes one document using an explicit dirty-document decision.
`documentId`: Stable tab identity.
`mode`: Explicit close behavior. | when the document closed. | — | +| [`public sealed class Inno.Editor.Interactions.EditorDragContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L10) | Provides contextual state while beginning an editor drag. | — | — | +| [`public Inno.Editor.Interactions.EditorDragContext.EditorDragContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, Inno.Editor.Interactions.EditorDragData data)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L33) | Creates a managed editor drag request.
`editor`: The shared passive editor context.
`interactions`: The active interaction entry point.
`area`: The interaction area that produced the source.
`data`: The transient source identity and preview label. | — | `System.ArgumentNullException`: Thrown when , , or is .
`System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDragContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L49) | Gets the shared passive editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDragContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L54) | Gets the active interaction entry point. | — | — | +| [`public string Inno.Editor.Interactions.EditorDragContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L59) | Gets the source interaction area. | — | — | +| [`public Inno.Editor.Interactions.EditorDragData Inno.Editor.Interactions.EditorDragContext.data { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragContext.cs#L64) | Gets the managed drag data. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorDragData`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L10) | Contains the transient identity and preview label for one editor drag operation. | — | — | +| [`public Inno.Editor.Interactions.EditorDragData.EditorDragData(Inno.Core.Identity.IdentityObject source, string label)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L27) | Creates drag data for a registered identity object.
`source`: The registered source object resolved again for each drop query and delivery.
`label`: The human-readable label used by drag previews. | — | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when is not registered in a live identity domain. | +| [`public Inno.Core.Identity.RuntimeIdentity Inno.Editor.Interactions.EditorDragData.sourceIdentity { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L41) | Gets the domain-qualified transient identity written to the native drag protocol. | — | — | +| [`public string Inno.Editor.Interactions.EditorDragData.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDragData.cs#L46) | Gets the drag preview label. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorDrop`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L11) | Defines one discoverable typed editor drop operation. | — | — | +| [`public abstract System.Type Inno.Editor.Interactions.EditorDrop.sourceType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L16) | Gets the accepted drag source type. | — | — | +| [`public abstract System.Type Inno.Editor.Interactions.EditorDrop.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L21) | Gets the accepted drop target type. | — | — | +| [`public abstract Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L32) | Evaluates whether the current managed source may be dropped on the supplied target.
`context`: The untyped source, target, surface, and requested placement. | The compatibility state and standard target visual. | — | +| [`public abstract Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L43) | Executes a delivered drop after a successful compatibility query.
`context`: The untyped source, target, surface, and requested placement. | The observable selection and reveal result of the operation. | — | +| [`protected Inno.Editor.Interactions.EditorDrop.EditorDrop()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorDrop where TSource : Inno.Core.Identity.IdentityObject where TTarget : class : Inno.Editor.Interactions.EditorDrop`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L55) | Defines a typed drop operation for one managed source and target pair.
`TSource`: The managed drag-source type accepted by the operation.
`TTarget`: The managed drop-target type accepted by the operation. | — | — | +| [`public override sealed System.Type Inno.Editor.Interactions.EditorDrop.sourceType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L62) | Gets the concrete type handled by this extension implementation. | — | — | +| [`public override sealed System.Type Inno.Editor.Interactions.EditorDrop.targetType { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L67) | Gets the concrete type handled by this extension implementation. | — | — | +| [`public override sealed Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L78) | Evaluates the operation's current availability and presentation state.
`context`: The context that supplies state and services for this operation. | The validated editor drop status that represents the completed operation. | — | +| [`public override sealed Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L95) | Validates and applies the current editor drag-and-drop interaction atomically.
`context`: The context that supplies state and services for this operation. | The validated editor drop result that represents the completed operation. | — | +| [`protected abstract Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDrop.Query(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L112) | Evaluates whether the strongly typed source may be dropped on the strongly typed target.
`context`: The typed source, target, surface, and requested placement. | The compatibility state and standard target visual. | — | +| [`protected abstract Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDrop.Drop(Inno.Editor.Interactions.EditorDropContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L123) | Executes a delivered drop for the strongly typed source and target.
`context`: The typed source, target, surface, and requested placement. | The observable selection and reveal result of the operation. | — | +| [`protected Inno.Editor.Interactions.EditorDrop.EditorDrop()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L55) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorDropContext where TSource : class where TTarget : class`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L135) | Provides strongly typed source and target values to a drop operation.
`TSource`: The managed drag-source type.
`TTarget`: The managed drop-target type. | — | — | +| [`public Inno.Editor.Interactions.EditorDropContext Inno.Editor.Interactions.EditorDropContext.untyped { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L152) | Gets the untyped drop context. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDropContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L157) | Gets the active editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDropContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L162) | Gets the active interaction entry point. | — | — | +| [`public string Inno.Editor.Interactions.EditorDropContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L167) | Gets the interaction area. | — | — | +| [`public TSource Inno.Editor.Interactions.EditorDropContext.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L172) | Gets the typed drag source. | — | — | +| [`public TTarget Inno.Editor.Interactions.EditorDropContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L177) | Gets the typed drop target. | — | — | +| [`public Inno.Editor.Interactions.EditorDropPlacement Inno.Editor.Interactions.EditorDropContext.placement { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDrop.cs#L182) | Gets the requested placement. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorDropAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L9) | Registers a typed editor drop handler for an optional exact area. | — | — | +| [`public Inno.Editor.Interactions.EditorDropAttribute.EditorDropAttribute(int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L17) | Creates a drop registration that can participate on any interaction surface.
`priority`: The tie-breaking priority used after source and target specificity. | — | — | +| [`public Inno.Editor.Interactions.EditorDropAttribute.EditorDropAttribute(string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L31) | Creates a drop registration scoped to an exact interaction area.
`area`: The exact target area handled by this registration, or an empty string for any area.
`priority`: The tie-breaking priority used after source and target specificity. | — | — | +| [`public string Inno.Editor.Interactions.EditorDropAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L42) | Gets the optional exact interaction area. | — | — | +| [`public int Inno.Editor.Interactions.EditorDropAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropAttribute.cs#L47) | Gets the tie-breaking priority. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorDropContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L11) | Provides contextual state to an editor drop handler. | — | — | +| [`public Inno.Editor.Interactions.EditorDropContext.EditorDropContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, Inno.Editor.Interactions.EditorDragData data, Inno.Core.Identity.IdentityObject source, object target, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L44) | Creates a managed drop request.
`editor`: The shared passive editor context.
`interactions`: The active interaction entry point.
`area`: The target interaction area.
`data`: The active identity-based drag data.
`source`: The live source resolved from for this operation only.
`target`: The managed target object exposed to typed handlers.
`placement`: The requested position relative to the target. | — | `System.ArgumentNullException`: Thrown when , , , , or is .
`System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorDropContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L66) | Gets the shared passive editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorDropContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L71) | Gets the active interaction entry point. | — | — | +| [`public string Inno.Editor.Interactions.EditorDropContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L76) | Gets the target interaction area. | — | — | +| [`public Inno.Editor.Interactions.EditorDragData Inno.Editor.Interactions.EditorDropContext.data { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L81) | Gets the active managed drag data. | — | — | +| [`public Inno.Core.Identity.IdentityObject Inno.Editor.Interactions.EditorDropContext.source { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L86) | Gets the live source resolved for this query or delivery operation. | — | — | +| [`public object Inno.Editor.Interactions.EditorDropContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L91) | Gets the managed drop target. | — | — | +| [`public Inno.Editor.Interactions.EditorDropPlacement Inno.Editor.Interactions.EditorDropContext.placement { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropContext.cs#L96) | Gets the requested placement relative to the target. | — | — | +| [`public enum Inno.Editor.Interactions.EditorDropPlacement : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L6) | Describes the requested placement relative to an editor drop target. | — | — | +| [`Inno.Editor.Interactions.EditorDropPlacement.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L11) | No positional placement is requested. | — | — | +| [`Inno.Editor.Interactions.EditorDropPlacement.Before = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L16) | Insert before the target. | — | — | +| [`Inno.Editor.Interactions.EditorDropPlacement.Into = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L21) | Drop into the target. | — | — | +| [`Inno.Editor.Interactions.EditorDropPlacement.After = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropPlacement.cs#L26) | Insert after the target. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorDropResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Describes the observable result of a completed editor drop. | — | — | +| [`public Inno.Editor.Interactions.EditorDropResult.EditorDropResult(bool accepted, object? selectionTarget = null, object? revealTarget = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L20) | Creates the observable result of a completed drop operation.
`accepted`: Whether the handler accepted and completed the drop.
`selectionTarget`: An optional object that the originating view should select.
`revealTarget`: An optional object that the originating view should reveal or expand. | — | — | +| [`public bool Inno.Editor.Interactions.EditorDropResult.accepted { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L33) | Gets whether the drop was accepted. | — | — | +| [`public object? Inno.Editor.Interactions.EditorDropResult.selectionTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L38) | Gets the optional target that should become selected. | — | — | +| [`public object? Inno.Editor.Interactions.EditorDropResult.revealTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L43) | Gets the optional hierarchy target that should be revealed. | — | — | +| [`public static Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDropResult.rejected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L48) | Gets a rejected drop result. | — | — | +| [`public static Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorDropResult.Accepted(object? selectionTarget = null, object? revealTarget = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L62) | Creates an accepted drop result with optional presentation requests.
`selectionTarget`: An optional object that the originating view should select.
`revealTarget`: An optional object that the originating view should reveal or expand. | An accepted drop result containing the supplied presentation requests. | — | +| [`public override string Inno.Editor.Interactions.EditorDropResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorDropResult.operator !=(Inno.Editor.Interactions.EditorDropResult left, Inno.Editor.Interactions.EditorDropResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorDropResult.operator ==(Inno.Editor.Interactions.EditorDropResult left, Inno.Editor.Interactions.EditorDropResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorDropResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorDropResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorDropResult.Equals(Inno.Editor.Interactions.EditorDropResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropResult.cs#L6) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorDropStatus : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Describes whether and how a target accepts the active drag. | — | — | +| [`public Inno.Editor.Interactions.EditorDropStatus.EditorDropStatus(bool canDrop, Inno.Editor.Interactions.EditorDropVisual visual)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L17) | Creates the compatibility and presentation state of a potential drop target.
`canDrop`: Whether the active source may be delivered to the target.
`visual`: The standard visual requested for the target. | — | — | +| [`public bool Inno.Editor.Interactions.EditorDropStatus.canDrop { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L28) | Gets whether the active source may be dropped. | — | — | +| [`public Inno.Editor.Interactions.EditorDropVisual Inno.Editor.Interactions.EditorDropStatus.visual { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L33) | Gets the standard target visual. | — | — | +| [`public static Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDropStatus.rejected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L38) | Gets an incompatible drop status. | — | — | +| [`public static Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorDropStatus.Accept(Inno.Editor.Interactions.EditorDropVisual visual = Inno.Editor.Interactions.EditorDropVisual.Highlight)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L49) | Creates an accepted drop status with the requested standard target visual.
`visual`: The standard visual drawn while the compatible source hovers the target. | An accepted drop status. | — | +| [`public override string Inno.Editor.Interactions.EditorDropStatus.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorDropStatus.operator !=(Inno.Editor.Interactions.EditorDropStatus left, Inno.Editor.Interactions.EditorDropStatus right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorDropStatus.operator ==(Inno.Editor.Interactions.EditorDropStatus left, Inno.Editor.Interactions.EditorDropStatus right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorDropStatus.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorDropStatus.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorDropStatus.Equals(Inno.Editor.Interactions.EditorDropStatus other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropStatus.cs#L6) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public enum Inno.Editor.Interactions.EditorDropVisual : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L6) | Defines the standard visual used for a compatible drop target. | — | — | +| [`Inno.Editor.Interactions.EditorDropVisual.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L11) | No visual is drawn. | — | — | +| [`Inno.Editor.Interactions.EditorDropVisual.Highlight = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L16) | Draw a content highlight. | — | — | +| [`Inno.Editor.Interactions.EditorDropVisual.InsertBefore = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L21) | Draw an insertion line before the target. | — | — | +| [`Inno.Editor.Interactions.EditorDropVisual.InsertAfter = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L26) | Draw an insertion line after the target. | — | — | +| [`Inno.Editor.Interactions.EditorDropVisual.Disabled = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/DragDrop/EditorDropVisual.cs#L31) | Draw a disabled target visual. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorEditGesture : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L7) | Owns one continuous edit transaction without retaining edited objects, setters or extension instances. | — | — | +| [`public Inno.Editor.Interactions.EditorEditGesture.EditorEditGesture(Inno.Editor.Interactions.IEditorHistory history, string name)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L25) | Begins an edit whose updates use the existing domain mutation and History protocols.
`history`: The authoritative History for the current editor session.
`name`: The operation name displayed by Undo and Redo after a successful commit. | — | `System.ArgumentNullException`: The History owner is null.
`System.InvalidOperationException`: History cannot begin a transaction in its current state. | +| [`public string Inno.Editor.Interactions.EditorEditGesture.mergeKey { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L37) | Gets the transient merge boundary shared only by updates within this gesture. | — | — | +| [`public bool Inno.Editor.Interactions.EditorEditGesture.isActive { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L41) | Gets whether this gesture still owns an active transaction, including a rejected completion. | — | — | +| [`public void Inno.Editor.Interactions.EditorEditGesture.Commit()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L49) | Commits the edit exactly once; rejected completion leaves the transaction available for retry. | — | `System.InvalidOperationException`: The gesture is finished or a nested transaction remains active. | +| [`public Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorEditGesture.Cancel()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L66) | Attempts to restore the state before the gesture without discarding a failed rollback. | The rollback outcome; a state-preserving failure keeps this gesture active for retry. | `System.InvalidOperationException`: The gesture is finished or a nested transaction remains active. | +| [`public void Inno.Editor.Interactions.EditorEditGesture.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs#L85) | Cancels an unfinished gesture and releases its ownership only after rollback succeeds. | — | `System.InvalidOperationException`: Rollback is blocked or a nested transaction remains active. | +| [`public sealed class Inno.Editor.Interactions.EditorOperationPreparingEvent : Inno.Core.Events.Event`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorOperationPreparingEvent.cs#L10) | Synchronously closes transient edits before command execution or an extension-handler generation switch. | — | — | +| [`public Inno.Editor.Interactions.EditorOperationPreparingEvent.EditorOperationPreparingEvent()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorOperationPreparingEvent.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorHistoryAvailability : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Describes whether a history change can currently move in one direction. | — | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryAvailability.isAvailable { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L21) | Gets whether the requested transition is currently available. | — | — | +| [`public string Inno.Editor.Interactions.EditorHistoryAvailability.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L26) | Gets the diagnostic explaining why the transition is unavailable, or an empty string when available. | — | — | +| [`public static Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryAvailability.Available()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L34) | Creates an available transition result. | An availability value that permits the transition. | — | +| [`public static Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryAvailability.Unavailable(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L48) | Creates an unavailable transition result.
`message`: The non-empty diagnostic explaining why the transition is unavailable. | An availability value that rejects the transition. | `System.ArgumentException`: Thrown when is empty. | +| [`public override string Inno.Editor.Interactions.EditorHistoryAvailability.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorHistoryAvailability.operator !=(Inno.Editor.Interactions.EditorHistoryAvailability left, Inno.Editor.Interactions.EditorHistoryAvailability right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorHistoryAvailability.operator ==(Inno.Editor.Interactions.EditorHistoryAvailability left, Inno.Editor.Interactions.EditorHistoryAvailability right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorHistoryAvailability.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorHistoryAvailability.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryAvailability.Equals(Inno.Editor.Interactions.EditorHistoryAvailability other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryAvailability.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryChange : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L8) | Describes one neutral reload-safe mutation interpreted by an attribute-discovered history handler. | — | — | +| [`public Inno.Editor.Interactions.EditorHistoryChange.EditorHistoryChange(string kind, Inno.Editor.Interactions.EditorHistoryPayload payload, string? mergeKey = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L30) | Creates a neutral history change.
`kind`: The stable globally unique handler protocol identifier.
`payload`: The immutable neutral payload interpreted by the handler.
`mergeKey`: An optional stable key used by the handler to merge adjacent changes. | — | `System.ArgumentException`: Thrown when is empty.
`System.ArgumentNullException`: Thrown when is . | +| [`public string Inno.Editor.Interactions.EditorHistoryChange.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L44) | Gets the stable globally unique handler protocol identifier. | — | — | +| [`public Inno.Editor.Interactions.EditorHistoryPayload Inno.Editor.Interactions.EditorHistoryChange.payload { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L49) | Gets the immutable neutral payload interpreted by the active handler generation. | — | — | +| [`public string? Inno.Editor.Interactions.EditorHistoryChange.mergeKey { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L54) | Gets the optional stable key used to merge adjacent changes to the same logical value. | — | — | +| [`public void Inno.Editor.Interactions.EditorHistoryChange.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryChange.cs#L73) | Releases the payload storage owned by this history change. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L10) | Provides current-generation editor services to a history change handler. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorHistoryContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L23) | Gets the passive editor context for the active project. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorHistoryContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryContext.cs#L28) | Gets the active interaction entry point used for selection and feature coordination. | — | — | +| [`public enum Inno.Editor.Interactions.EditorHistoryDirection : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L6) | Identifies the direction in which an editor history change is being applied. | — | — | +| [`Inno.Editor.Interactions.EditorHistoryDirection.Undo = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L11) | Restores the state that existed before the change was committed. | — | — | +| [`Inno.Editor.Interactions.EditorHistoryDirection.Redo = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryDirection.cs#L16) | Restores the state produced by the committed change. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorHistoryHandler`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L8) | Interprets neutral editor history payloads for one current-generation feature protocol. | — | — | +| [`protected abstract Inno.Editor.Interactions.EditorHistoryAvailability Inno.Editor.Interactions.EditorHistoryHandler.Query(Inno.Editor.Interactions.EditorHistoryContext context, Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L25) | Determines whether a neutral change can currently transition in the requested direction.
`context`: The current-generation editor services.
`change`: The neutral history change.
`direction`: The requested Undo or Redo direction. | The current transition availability. | — | +| [`protected abstract Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryHandler.Apply(Inno.Editor.Interactions.EditorHistoryContext context, Inno.Editor.Interactions.EditorHistoryChange change, Inno.Editor.Interactions.EditorHistoryDirection direction)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L46) | Atomically applies a neutral history change in the requested direction.
`context`: The current-generation editor services.
`change`: The neutral history change.
`direction`: The requested Undo or Redo direction. | The transition result. A failure must leave the feature in its original state. | — | +| [`protected virtual bool Inno.Editor.Interactions.EditorHistoryHandler.TryMerge(Inno.Editor.Interactions.EditorHistoryChange older, Inno.Editor.Interactions.EditorHistoryChange newer, out Inno.Editor.Interactions.EditorHistoryChange? merged)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L67) | Attempts to merge two adjacent changes to the same logical value.
`older`: The existing older history change.
`newer`: The newly recorded adjacent history change.
`merged`: The independently owned merged change when successful. | when replaces both input changes. | — | +| [`protected static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryHandler.StateIntegrityFailure(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L85) | Creates a failed result for a transition whose compensation also failed.
`message`: The diagnostic containing both the transition and compensation failures. | A failure that faults the owning history. | — | +| [`protected Inno.Editor.Interactions.EditorHistoryHandler.EditorHistoryHandler()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandler.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryHandlerAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L9) | Registers a stateless editor history handler for one stable change protocol. | — | — | +| [`public Inno.Editor.Interactions.EditorHistoryHandlerAttribute.EditorHistoryHandlerAttribute(string kind)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L20) | Creates a history handler registration.
`kind`: The stable globally unique change protocol identifier. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Interactions.EditorHistoryHandlerAttribute.kind { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryHandlerAttribute.cs#L29) | Gets the stable globally unique change protocol identifier. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryOptions`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L8) | Configures retention and payload storage for one editor history. | — | — | +| [`public int Inno.Editor.Interactions.EditorHistoryOptions.maxEntries { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L13) | Gets or initializes the maximum number of committed top-level entries retained in memory. | — | — | +| [`public long Inno.Editor.Interactions.EditorHistoryOptions.maxResidentBytes { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L18) | Gets or initializes the maximum estimated resident payload bytes retained by the history. | — | — | +| [`public long Inno.Editor.Interactions.EditorHistoryOptions.maxDiskBytes { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L23) | Gets or initializes the maximum payload bytes retained in the temporary disk store. | — | — | +| [`public int Inno.Editor.Interactions.EditorHistoryOptions.inlinePayloadThreshold { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L28) | Gets or initializes the payload size at which immutable bytes are moved to the temporary disk store. | — | — | +| [`public string? Inno.Editor.Interactions.EditorHistoryOptions.cacheDirectory { get; init; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L33) | Gets or initializes the optional directory that owns temporary history payloads for this editor session. | — | — | +| [`public Inno.Editor.Interactions.EditorHistoryOptions.EditorHistoryOptions()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryOptions.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryPayload : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L9) | Owns immutable neutral bytes used by a reload-safe editor history change. | — | — | +| [`public long Inno.Editor.Interactions.EditorHistoryPayload.length { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L35) | Gets the number of immutable bytes represented by this payload. | — | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryPayload.isStoredOnDisk { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L40) | Gets whether the payload is retained in the temporary disk store instead of resident memory. | — | — | +| [`public static Inno.Editor.Interactions.EditorHistoryPayload Inno.Editor.Interactions.EditorHistoryPayload.FromBytes(System.ReadOnlySpan bytes)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L55) | Creates an immutable history payload by copying the supplied bytes.
`bytes`: The neutral bytes to retain for future Undo and Redo transitions. | A new independently owned payload. | — | +| [`public byte[] Inno.Editor.Interactions.EditorHistoryPayload.ReadBytes()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L66) | Reads the complete immutable payload into a newly allocated byte array. | A copy of the payload bytes. | `System.ObjectDisposedException`: Thrown after the payload has been disposed. | +| [`public void Inno.Editor.Interactions.EditorHistoryPayload.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryPayload.cs#L90) | Releases the resident or temporary disk storage owned by this payload. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorHistoryResult : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Describes the outcome of applying, undoing, or redoing an editor history operation. | — | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryResult.succeeded { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L23) | Gets whether the requested history transition completed successfully. | — | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryResult.statePreserved { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L32) | Gets whether a failed transition restored the domain state that existed before the attempt. | — | — | +| [`public string Inno.Editor.Interactions.EditorHistoryResult.message { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L37) | Gets the diagnostic message associated with a failed transition, or an empty string after success. | — | — | +| [`public static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryResult.Success()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L45) | Creates a successful history result. | A result representing a completed transition. | — | +| [`public static Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryResult.Failure(string message)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L59) | Creates a failed history result without changing the owning history stack.
`message`: The non-empty diagnostic explaining why the transition could not complete. | A result representing a rejected or failed transition. | `System.ArgumentException`: Thrown when is empty. | +| [`public override string Inno.Editor.Interactions.EditorHistoryResult.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorHistoryResult.operator !=(Inno.Editor.Interactions.EditorHistoryResult left, Inno.Editor.Interactions.EditorHistoryResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorHistoryResult.operator ==(Inno.Editor.Interactions.EditorHistoryResult left, Inno.Editor.Interactions.EditorHistoryResult right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorHistoryResult.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorHistoryResult.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorHistoryResult.Equals(Inno.Editor.Interactions.EditorHistoryResult other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryResult.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Interactions.EditorHistoryTransaction : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L8) | Collects several history operations into one atomic undo and redo entry. | — | — | +| [`public string Inno.Editor.Interactions.EditorHistoryTransaction.name { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L26) | Gets the user-facing name assigned to the transaction. | — | — | +| [`public void Inno.Editor.Interactions.EditorHistoryTransaction.Commit()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L36) | Commits all recorded child operations as one atomic history entry. | — | `System.InvalidOperationException`: Thrown when the transaction is no longer active. | +| [`public Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.EditorHistoryTransaction.Rollback()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L52) | Reverts every operation recorded by the transaction and does not add a history entry. | The result of the atomic rollback attempt. | `System.InvalidOperationException`: Thrown when the transaction is no longer active. | +| [`public void Inno.Editor.Interactions.EditorHistoryTransaction.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistoryTransaction.cs#L66) | Rolls back an uncommitted transaction before releasing it. | — | — | +| [`public interface Inno.Editor.Interactions.IEditorHistory`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L6) | Exposes reload-safe Undo and Redo operations based exclusively on neutral history changes. | — | — | | [`bool Inno.Editor.Interactions.IEditorHistory.canUndo { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L11) | Gets whether an Undo transition is currently available. | — | — | -| [`long Inno.Editor.Interactions.IEditorHistory.diskBytes { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L56) | Gets the temporary disk payload bytes retained by committed entries. | — | — | -| [`string? Inno.Editor.Interactions.IEditorHistory.faultReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L46) | Gets the diagnostic that faulted this history, or null. | — | — | +| [`bool Inno.Editor.Interactions.IEditorHistory.canRedo { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L16) | Gets whether a Redo transition is currently available. | — | — | | [`bool Inno.Editor.Interactions.IEditorHistory.isFaulted { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L21) | Gets whether a failed compensation left the domain state indeterminate. | — | — | -| [`string? Inno.Editor.Interactions.IEditorHistory.redoName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L31) | Gets the next Redo operation name, or null. | — | — | -| [`string? Inno.Editor.Interactions.IEditorHistory.redoUnavailableReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L41) | Gets why the next Redo entry is unavailable, or null. | — | — | +| [`string? Inno.Editor.Interactions.IEditorHistory.undoName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L26) | Gets the next Undo operation name, or . | — | — | +| [`string? Inno.Editor.Interactions.IEditorHistory.redoName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L31) | Gets the next Redo operation name, or . | — | — | +| [`string? Inno.Editor.Interactions.IEditorHistory.undoUnavailableReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L36) | Gets why the next Undo entry is unavailable, or . | — | — | +| [`string? Inno.Editor.Interactions.IEditorHistory.redoUnavailableReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L41) | Gets why the next Redo entry is unavailable, or . | — | — | +| [`string? Inno.Editor.Interactions.IEditorHistory.faultReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L46) | Gets the diagnostic that faulted this history, or . | — | — | | [`long Inno.Editor.Interactions.IEditorHistory.residentBytes { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L51) | Gets the resident payload bytes retained by committed entries. | — | — | -| [`string? Inno.Editor.Interactions.IEditorHistory.undoName { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L26) | Gets the next Undo operation name, or null. | — | — | -| [`string? Inno.Editor.Interactions.IEditorHistory.undoUnavailableReason { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L36) | Gets why the next Undo entry is unavailable, or null. | — | — | -| [`public interface Inno.Editor.Interactions.IEditorHistory`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L6) | Exposes reload-safe Undo and Redo operations based exclusively on neutral history changes. | — | — | - -### `Inno.Editor.Interactions.IEditorHistoryIsolation` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`System.IDisposable Inno.Editor.Interactions.IEditorHistoryIsolation.BeginHistoryIsolation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistoryIsolation.cs#L19) | Starts an independently disposable history branch while retaining the current editing branch.
返回:A lease that releases the temporary branch and restores the retained branch when disposed. | — | `System.InvalidOperationException`:Thrown while another history transition, transaction, or isolated branch is active. | +| [`long Inno.Editor.Interactions.IEditorHistory.diskBytes { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L56) | Gets the temporary disk payload bytes retained by committed entries. | — | — | +| [`Inno.Editor.Interactions.EditorHistoryTransaction Inno.Editor.Interactions.IEditorHistory.BeginTransaction(string name)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L67) | Begins an atomic group of neutral history operations.
`name`: The user-facing grouped operation name. | A transaction that must be committed or rolled back. | — | +| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Execute(string name, Inno.Editor.Interactions.EditorHistoryChange change)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L81) | Applies and records a neutral change through its current-generation handler.
`name`: The user-facing operation name.
`change`: The independently owned neutral change. | The result of applying the change in the Redo direction. | — | +| [`void Inno.Editor.Interactions.IEditorHistory.RecordApplied(string name, Inno.Editor.Interactions.EditorHistoryChange change)`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L95) | Records a neutral change whose domain mutation is already applied.
`name`: The user-facing operation name.
`change`: The independently owned neutral change. | — | — | +| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Undo()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L106) | Attempts to restore the state preceding the newest committed operation. | The transition result. A state-preserving failure remains available for retry. | — | +| [`Inno.Editor.Interactions.EditorHistoryResult Inno.Editor.Interactions.IEditorHistory.Redo()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistory.cs#L114) | Attempts to reapply the newest reverted operation. | The transition result. A state-preserving failure remains available for retry. | — | | [`public interface Inno.Editor.Interactions.IEditorHistoryIsolation`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistoryIsolation.cs#L8) | Creates temporary history branches for transient editor workflows without exposing history internals. | — | — | - -### `Inno.Editor.Interactions.IEditorSelectionCoordinator` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Interactions.IEditorSelectionCoordinator.SetSelection(object? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L19) | Replaces the current target after closing presentations owned by another target.
`target`:The new target, or null to clear selection. | — | — | -| [`object? Inno.Editor.Interactions.IEditorSelectionCoordinator.selectedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L11) | Gets the currently selected target, or null when selection is empty. | — | — | -| [`public interface Inno.Editor.Interactions.IEditorSelectionCoordinator`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L6) | Provides the narrow selection boundary used by editor features that replace object instances. | — | — | - -### `Inno.Editor.Interactions.IEditorViewportCoordinateConverter` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.IEditorViewportCoordinateConverter.ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L195) | Converts viewport-local pixel coordinates to world coordinates.
返回:World coordinates.
`screenPosition`:Viewport-local pixel coordinates. | — | — | -| [`Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.IEditorViewportCoordinateConverter.WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L206) | Converts world coordinates to viewport-local pixel coordinates.
返回:Viewport-local pixel coordinates.
`worldPosition`:World coordinates. | — | — | +| [`System.IDisposable Inno.Editor.Interactions.IEditorHistoryIsolation.BeginHistoryIsolation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/History/IEditorHistoryIsolation.cs#L19) | Starts an independently disposable history branch while retaining the current editing branch. | A lease that releases the temporary branch and restores the retained branch when disposed. | `System.InvalidOperationException`: Thrown while another history transition, transaction, or isolated branch is active. | +| [`public sealed class Inno.Editor.Interactions.EditorMenuAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L9) | Places an editor action at an arbitrary path on a menu surface. | — | — | +| [`public Inno.Editor.Interactions.EditorMenuAttribute.EditorMenuAttribute(string area, string path, int order = 0, bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L29) | Creates a static placement for the annotated action on an exact menu surface.
`area`: The exact interaction area whose menu receives the action.
`path`: The slash-delimited path used to create parent menus and the leaf label.
`order`: The stable ordering value among sibling placements.
`separatorBefore`: Whether a visual separator should precede the leaf placement. | — | `System.ArgumentException`: Thrown when or is empty. | +| [`public string Inno.Editor.Interactions.EditorMenuAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L48) | Gets the menu area. | — | — | +| [`public string Inno.Editor.Interactions.EditorMenuAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L53) | Gets the slash-delimited menu path. | — | — | +| [`public int Inno.Editor.Interactions.EditorMenuAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L58) | Gets the stable menu ordering value. | — | — | +| [`public bool Inno.Editor.Interactions.EditorMenuAttribute.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuAttribute.cs#L63) | Gets whether a separator is rendered before the item. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorMenuBuilder`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L9) | Collects dynamic menu item placements. | — | — | +| [`public void Inno.Editor.Interactions.EditorMenuBuilder.AddGroup(string path, int order = 0, bool separatorBefore = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L26) | Declares presentation for an intermediate menu group without inventing a no-op action.
`path`: The slash-delimited path of the group.
`order`: The stable ordering value among sibling groups and items.
`separatorBefore`: Whether a separator precedes this group at its own level. | — | — | +| [`public void Inno.Editor.Interactions.EditorMenuBuilder.Add(string path, string actionId, int order = 0, bool separatorBefore = false, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L57) | Adds a dynamic action placement to the menu currently being constructed.
`path`: The slash-delimited path used to create parent menus and the leaf label.
`actionId`: The stable action identifier invoked by the leaf.
`order`: The stable ordering value among sibling placements.
`separatorBefore`: Whether a visual separator should precede the leaf placement.
`argument`: An optional argument captured for this menu generation. | — | `System.ArgumentException`: Thrown when or is empty. | +| [`public Inno.Editor.Interactions.EditorMenuBuilder.EditorMenuBuilder()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuBuilder.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorMenuContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L10) | Provides contextual state while constructing an editor menu. | — | — | +| [`public Inno.Editor.Interactions.EditorMenuContext.EditorMenuContext(Inno.Editor.Core.EditorContext editor, Inno.Editor.Interactions.EditorInteractions interactions, string area, object? target = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L33) | Creates a contextual menu request.
`editor`: The shared passive editor context.
`interactions`: The active interaction entry point.
`area`: The exact menu area being requested.
`target`: The optional object the menu operates on. | — | `System.ArgumentNullException`: Thrown when or is .
`System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Interactions.EditorMenuContext.editor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L49) | Gets the shared passive editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorMenuContext.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L54) | Gets the active interaction entry point. | — | — | +| [`public string Inno.Editor.Interactions.EditorMenuContext.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L59) | Gets the requested menu area. | — | — | +| [`public object? Inno.Editor.Interactions.EditorMenuContext.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuContext.cs#L64) | Gets the contextual menu target. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorMenuItem`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L11) | Represents one immutable contextual menu node. | — | — | +| [`public string Inno.Editor.Interactions.EditorMenuItem.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L58) | Gets the visible node label. | — | — | +| [`public string Inno.Editor.Interactions.EditorMenuItem.actionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L64) | Gets the stable action identifier consumed by a presentation backend. | — | — | +| [`public int Inno.Editor.Interactions.EditorMenuItem.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L69) | Gets the stable ordering value. | — | — | +| [`public bool Inno.Editor.Interactions.EditorMenuItem.separatorBefore { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L74) | Gets whether a separator precedes this node. | — | — | +| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorMenuItem.status { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L79) | Gets the current command presentation state. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorMenuItem.children { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L84) | Gets child menu nodes. | — | — | +| [`public object? Inno.Editor.Interactions.EditorMenuItem.argument { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuItem.cs#L90) | Gets the generation-local action argument consumed by a presentation backend. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorMenuModel`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L9) | Contains a complete immutable menu tree. | — | — | +| [`public Inno.Editor.Interactions.EditorMenuModel.EditorMenuModel(System.Collections.Generic.IReadOnlyList? items)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L17) | Creates a complete immutable menu tree from resolved root nodes.
`items`: The root nodes in stable display order, or for an empty menu. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorMenuModel.items { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuModel.cs#L25) | Gets root menu nodes in display order. | — | — | +| [`public abstract class Inno.Editor.Interactions.EditorMenuSource`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L8) | Contributes dynamic entries to one or more editor menu surfaces. | — | — | +| [`public abstract void Inno.Editor.Interactions.EditorMenuSource.Build(Inno.Editor.Interactions.EditorMenuContext context, Inno.Editor.Interactions.EditorMenuBuilder builder)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L19) | Adds context-dependent placements for the supplied menu request.
`context`: The requested menu surface, target, and shared editor context.
`builder`: The collector that receives dynamic action placements. | — | — | +| [`protected Inno.Editor.Interactions.EditorMenuSource.EditorMenuSource()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSource.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorMenuSourceAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L9) | Registers a dynamic editor menu source. | — | — | +| [`public Inno.Editor.Interactions.EditorMenuSourceAttribute.EditorMenuSourceAttribute(string area, int priority = 0)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L23) | Creates a dynamic menu-source registration for an exact interaction surface.
`area`: The exact menu area that receives placements from the source.
`priority`: The ordering priority used when multiple dynamic sources contribute to the surface. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public string Inno.Editor.Interactions.EditorMenuSourceAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L36) | Gets the contributed menu area. | — | — | +| [`public int Inno.Editor.Interactions.EditorMenuSourceAttribute.priority { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuSourceAttribute.cs#L41) | Gets the provider ordering priority. | — | — | +| [`public readonly struct Inno.Editor.Interactions.EditorInteraction`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L14) | Provides a fluent handle that borrows an optional target for a synchronous interaction request. | — | — | +| [`public string Inno.Editor.Interactions.EditorInteraction.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L31) | Gets the stable interaction area. | — | — | +| [`public object? Inno.Editor.Interactions.EditorInteraction.target { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L35) | Gets the borrowed target without extending its validity or resolving a replacement generation. | — | — | +| [`public bool Inno.Editor.Interactions.EditorInteraction.isSelected { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L39) | Gets whether this handle's target is the current editor selection. | — | — | +| [`public void Inno.Editor.Interactions.EditorInteraction.Focus()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L44) | Marks this area and target as the active keyboard context. | — | — | +| [`public bool Inno.Editor.Interactions.EditorInteraction.Select()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L51) | Selects this target, or clears selection when the target is . | when the built-in selection action executed; otherwise, . | — | +| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorInteraction.Query(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L67) | Queries an action for this area and target.
`action`: The stable action name.
`argument`: An optional action argument. | The current action state. | `System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Interactions.EditorInteraction.Execute(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L86) | Executes an action for this area and target.
`action`: The stable action name.
`argument`: An optional action argument. | when a visible and enabled action executed; otherwise, . | `System.ArgumentException`: Thrown when is empty. | +| [`public void Inno.Editor.Interactions.EditorInteraction.Enqueue(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L102) | Queues an action until the current UI traversal completes.
`action`: The stable action name.
`argument`: An optional action argument. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Interactions.EditorInteraction.Present(string action, object? argument = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L121) | Presents an active action in place of this target's normal content.
`action`: The stable action name.
`argument`: Optional presentation data supplied by the current view. | when the action presented content; otherwise, . | `System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Interactions.EditorInteraction.IsActive(string action)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L137) | Gets whether an action owns an active multi-frame operation for this target.
`action`: The stable action name. | when the action is active for this exact target; otherwise, . | `System.ArgumentException`: Thrown when is empty. | +| [`public Inno.Editor.Interactions.EditorMenuModel Inno.Editor.Interactions.EditorInteraction.BuildMenu()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L144) | Builds the complete contextual menu for this area and target. | The immutable resolved menu model. | — | +| [`public Inno.Editor.Interactions.EditorMenuModel Inno.Editor.Interactions.EditorInteraction.BuildCommands()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L151) | Builds searchable commands from this area's menus and applicable unplaced Actions. | Current command states and neutral placement arguments; execution revalidates their targets. | — | +| [`public Inno.Editor.Interactions.EditorToolbarModel Inno.Editor.Interactions.EditorInteraction.BuildToolbar()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L158) | Builds the complete compact toolbar for this area and target. | The immutable resolved toolbar model. | — | +| [`public bool Inno.Editor.Interactions.EditorInteraction.TryGetShortcut(string action, out Inno.Editor.Interactions.HotKeyGesture gesture)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L174) | Resolves the shortcut displayed for an action in this area.
`action`: The stable action name.
`gesture`: The resolved gesture when successful. | when a compatible shortcut exists; otherwise, . | `System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Interactions.EditorInteraction.TryGetActiveDragIdentity(out Inno.Core.Identity.RuntimeIdentity identity)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L187) | Gets the currently active drag identity, if its source remains registered.
`identity`: The active domain-qualified runtime identity when available. | when a live drag source exists; otherwise, . | — | +| [`public Inno.Core.Identity.RuntimeIdentity Inno.Editor.Interactions.EditorInteraction.BeginDrag(Inno.Editor.Interactions.EditorDragData data)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L197) | Begins a managed drag originating from this area.
`data`: The managed source data. | The source object's domain-qualified transient identity. | — | +| [`public Inno.Editor.Interactions.EditorDropStatus Inno.Editor.Interactions.EditorInteraction.QueryDrop(Inno.Core.Identity.RuntimeIdentity identity, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L210) | Queries this handle as a drop target.
`identity`: The active domain-qualified runtime identity.
`placement`: The requested placement relative to the target. | The drop compatibility and visual state. | — | +| [`public Inno.Editor.Interactions.EditorDropResult Inno.Editor.Interactions.EditorInteraction.Drop(Inno.Core.Identity.RuntimeIdentity identity, Inno.Editor.Interactions.EditorDropPlacement placement = Inno.Editor.Interactions.EditorDropPlacement.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs#L226) | Delivers a managed drop to this target.
`identity`: The active domain-qualified runtime identity.
`placement`: The requested placement relative to the target. | The completed drop result. | — | +| [`public sealed class Inno.Editor.Interactions.EditorInteractionRuntime : Inno.Editor.Core.EditorRuntime`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L17) | Hosts attribute-discovered editor extensions without depending on a presentation backend. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(string projectDirectory, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, Inno.Editor.Core.EditorKeyboardPolicy keyboard)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L46) | Creates an interaction runtime for one project.
`projectDirectory`: The project root containing Assets and Library.
`types`: The type catalog that owns editor extension generations.
`logs`: The host-owned logging router used by editor infrastructure.
`keyboard`: The explicit immutable product keyboard conventions. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L71) | Creates an interaction runtime for an existing passive editor context.
`context`: The passive editor context shared with the presentation backend.
`types`: The type catalog that owns editor extension generations.
`logs`: The host-owned logging router used by editor infrastructure. | — | `System.ArgumentNullException`: Thrown when or is . | +| [`public Inno.Editor.Interactions.EditorInteractionRuntime.EditorInteractionRuntime(Inno.Editor.Core.EditorContext context, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs, System.Collections.Generic.IEnumerable hostServices)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L98) | Creates an interaction runtime with stable host-owned extension services.
`context`: The passive editor context shared with the presentation backend.
`hostServices`: Stable host-owned services that editor extension constructors may request by assignable contract.
`logs`: The host-owned logging router used by editor infrastructure.
`types`: The type catalog that owns editor extension generations. | — | `System.ArgumentNullException`: Thrown when or is . | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Interactions.EditorInteractionRuntime.interactions { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L128) | Gets the active presentation-independent interaction entry point. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorInteractionRuntime.panels { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L133) | Gets active dockable panel extensions in deterministic order. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorInteractionRuntime.modals { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L145) | Gets active modal extensions in deterministic order. | — | — | +| [`public int Inno.Editor.Interactions.EditorInteractionRuntime.panelCount { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L157) | Gets the number of active dockable panels. | — | — | +| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Start()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L162) | Starts value processing after validating the current state. | — | — | +| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Update(Inno.Editor.Core.EditorFrame frame)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L179) | Recomputes owned state from the current validated inputs.
`frame`: The frame consumed by update; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.Flush()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L194) | Flushes actions queued during the current presentation traversal. | — | — | +| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.SaveState()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L203) | Captures every stateful active module and panel and atomically flushes changed project state to disk. | — | `System.ObjectDisposedException`: Thrown after this runtime has been disposed. | +| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.PrepareShutdown()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L220) | Freezes automatic extension-state persistence and writes the final state before modules begin shutting down. | — | `System.ObjectDisposedException`: Thrown after this runtime has been disposed. | +| [`public void Inno.Editor.Interactions.EditorInteractionRuntime.HandleKeyPressed(Inno.Core.Events.KeyPressedEvent keyEvent)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L232) | Dispatches an unhandled keyboard event through contextual shortcuts.
`keyEvent`: The keyboard event received from the application event stream. | — | — | +| [`public override void Inno.Editor.Interactions.EditorInteractionRuntime.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractionRuntime.cs#L248) | Releases the resources owned by this implementation. | — | `System.AggregateException`: Thrown after all shutdown stages have been attempted when one or more stages failed.
`Inno.Core.Execution.RetirementPendingException`: A stage still owns live work. This runtime retains all remaining stages and rejects new updates. | +| [`public sealed class Inno.Editor.Interactions.EditorInteractions : Inno.Editor.Interactions.IEditorSelectionCoordinator, Inno.Editor.Interactions.IEditorHistoryIsolation`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L15) | Provides the single presentation-independent entry point for editor actions, menus, selection, focus, and drag-and-drop. | — | — | +| [`public Inno.Core.Events.EventDispatcher Inno.Editor.Interactions.EditorInteractions.events { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L57) | Gets synchronous Editor lifecycle notifications implemented by the Core event dispatcher. | — | — | +| [`public Inno.Editor.Interactions.EditorSelectionState Inno.Editor.Interactions.EditorInteractions.selection { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L63) | Gets the shared read-only editor selection state. | — | — | +| [`public Inno.Editor.Core.EditorKeyboardPolicy Inno.Editor.Interactions.EditorInteractions.keyboard { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L68) | Gets the immutable product keyboard policy used by commands and selection controls. | — | — | +| [`public Inno.Editor.Interactions.IEditorHistory Inno.Editor.Interactions.EditorInteractions.history { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L74) | Gets the transactional Undo and Redo history owned by this editor runtime. | — | — | +| [`public Inno.Editor.Interactions.IEditorDocumentService Inno.Editor.Interactions.EditorInteractions.documents { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L78) | Gets the headless reload-safe document lifetime used by dedicated asset editors and Inspectors. | — | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetModule(out TModule? module) where TModule : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L95) | Resolves an active feature module for immediate use in the current Editor callback.
`module`: The started, non-quarantined module, or null when unavailable.
`TModule`: The feature module contract to resolve. | True when the active generation supplies the requested module. | — | +| [`public System.IDisposable Inno.Editor.Interactions.EditorInteractions.BeginHistoryIsolation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L118) | Starts an isolated temporary Undo and Redo branch while retaining the current editing branch. | A scope that restores the retained history branch when disposed. | `System.InvalidOperationException`: Thrown during an Undo, Redo, transaction, or another isolated branch. | +| [`public string Inno.Editor.Interactions.EditorInteractions.focusedArea { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L123) | Gets the area that most recently received keyboard focus. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorInteractions.GetPanelDeclarations()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L131) | Gets immutable discovery metadata for the active, available panels. | Current panel declarations; the snapshot contains no panel or extension instances. | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetPanelState(string panelId, out bool isOpen)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L144) | Reads whether one available panel is currently open.
`panelId`: The stable panel identity.
`isOpen`: Receives the open state, or false when the panel is unavailable. | True when the current generation contains the requested available panel. | — | +| [`public object? Inno.Editor.Interactions.EditorInteractions.focusedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L156) | Gets the target associated with the focused area. | — | — | +| [`public Inno.Editor.Interactions.EditorInteraction Inno.Editor.Interactions.EditorInteractions.For(string area, object? target = null)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L173) | Creates a lightweight interaction handle for one area and optional target.
`area`: The stable interaction area.
`target`: The optional object represented by the area. | A lightweight interaction handle. | `System.ArgumentException`: Thrown when is empty. | +| [`public void Inno.Editor.Interactions.EditorInteractions.SetSelection(object? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L183) | Replaces the editor selection after closing presentations owned by other targets.
`target`: The target to select, or to clear the selection. | — | — | +| [`public void Inno.Editor.Interactions.EditorInteractions.SetSelection(System.Collections.Generic.IEnumerable targets, Inno.Core.Identity.IdentityObject? anchor)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L208) | Replaces the ordered selection; duplicates are removed and the final target becomes primary.
`targets`: Registered objects resolved for the current callback.
`anchor`: The range anchor, or null to use the first selected target. | — | `System.ArgumentNullException`: The target collection or one member is null.
`System.InvalidOperationException`: A target has no live identity registration. | +| [`public void Inno.Editor.Interactions.EditorInteractions.ToggleSelection(Inno.Core.Identity.IdentityObject target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L226) | Adds or removes a member while preserving the existing range anchor.
`target`: A currently registered selection target. | — | `System.ArgumentNullException`: The target is null. | +| [`public void Inno.Editor.Interactions.EditorInteractions.SelectRange(System.Collections.Generic.IReadOnlyList orderedTargets, Inno.Core.Identity.IdentityObject target, bool additive = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L258) | Selects the inclusive visible range between the current anchor and the clicked target.
`orderedTargets`: Runtime identities in the current visible sort and filter order, resolved through Core Identity only when selected.
`target`: The clicked range endpoint, which becomes primary.
`additive`: Whether to preserve existing members outside the selected range. | — | `System.ArgumentNullException`: The visible targets or endpoint is null.
`System.ArgumentException`: The endpoint is not in the visible target order.
`System.InvalidOperationException`: A range identity is unavailable; the current selection remains unchanged. | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetDragSource(Inno.Core.Identity.RuntimeIdentity identity, out Inno.Core.Identity.IdentityObject? source)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L313) | Resolves the live source for an active runtime identity.
`identity`: The domain-qualified runtime identity carried by the drag protocol.
`source`: The currently registered drag source when resolution succeeds. | when the identity is active and resolves to a live source; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryGetActiveDragIdentity(out Inno.Core.Identity.RuntimeIdentity identity)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L326) | Gets the active drag identity used to select the matching native payload domain.
`identity`: The active runtime identity when one remains valid. | when a live drag source exists; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TogglePanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L339) | Toggles one panel in the currently active extension generation.
`panelId`: The stable panel identifier to resolve. | when an available panel was found and toggled. | `System.ArgumentException`: Thrown when is empty. | +| [`public System.IDisposable Inno.Editor.Interactions.EditorInteractions.RegisterIdentityDomain(Inno.Core.Identity.IdentityAllocator allocator)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L364) | Makes a temporary Core identity domain available to selection, focus and drag resolution.
`allocator`: The borrowed allocator owned by the temporary runtime session. | A routing lease; retire it after restoring Edit selection and before releasing the session. | `System.ArgumentNullException`: The allocator is null.
`System.InvalidOperationException`: The domain is already registered or the caller is not the owner thread.
`System.ObjectDisposedException`: The interaction runtime has retired its identity routing. | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryResolveIdentity(Inno.Core.Identity.RuntimeIdentity identity, out Inno.Core.Identity.IdentityObject? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L387) | Resolves a domain-qualified runtime identity through the Editor's complete identity-domain set.
`identity`: Runtime identity captured from a live Editor target.
`target`: Receives the current live object, or when its domain or generation is unavailable. | when the exact identity resolves in its owning domain. | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.TryResolveIdentity(Inno.Core.Identity.IdentityDomainId domainId, System.Guid persistentId, out Inno.Core.Identity.IdentityObject? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L411) | Resolves a persistent identity through one explicitly selected Editor identity domain.
`domainId`: Identity domain that owns the object and any replacement generation.
`persistentId`: Stable object identity to resolve inside that domain.
`target`: Receives the current live object, or when the domain or object is unavailable. | when the stable identity resolves in the requested domain. | — | +| [`public bool Inno.Editor.Interactions.EditorInteractions.OpenPanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L432) | Opens and requests presentation focus for one panel in the active extension generation.
`panelId`: The stable panel identifier to resolve. | when an available panel was found. | `System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Interactions.EditorInteractions.ClosePanel(string panelId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs#L450) | Closes one panel in the active extension generation without toggling its current state.
`panelId`: The stable panel identifier to resolve. | when an available panel was found. | `System.ArgumentException`: Thrown when is empty. | +| [`public sealed class Inno.Editor.Interactions.EditorModalExtension`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L11) | Describes one active modal extension. | — | — | +| [`public string Inno.Editor.Interactions.EditorModalExtension.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L34) | Gets the stable modal identifier. | — | — | +| [`public string Inno.Editor.Interactions.EditorModalExtension.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L39) | Gets the visible modal title. | — | — | +| [`public int Inno.Editor.Interactions.EditorModalExtension.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L44) | Gets the stable modal ordering value. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.TryGetPresentation(out Inno.Editor.Interactions.EditorModalExtension.Presentation presentation)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L55) | Safely captures generation-local modal presentation values.
`presentation`: The captured immutable presentation, or its default value when the modal is quarantined. | when every presentation value was read successfully. | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L95) | Safely draws the modal body and quarantines a failing extension instance.
`context`: The active editor context supplied by the presentation backend. | when the modal completed drawing without being quarantined. | `System.ArgumentNullException`: Thrown when is . | +| [`public readonly record struct Inno.Editor.Interactions.EditorModalExtension.Presentation : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Stores immutable modal window policy values read from one extension generation. | — | — | +| [`public Inno.Editor.Interactions.EditorModalExtension.Presentation.Presentation(bool isVisible, bool blocksInteraction, bool canMove, bool canResize, bool allowScrolling, System.Numerics.Vector2 initialSize, System.Numerics.Vector2 minimumSize)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L142) | Creates an immutable modal presentation snapshot.
`isVisible`: Whether the modal should currently be visible.
`blocksInteraction`: Whether the modal blocks regular editor interaction.
`canMove`: Whether the modal window can be moved.
`canResize`: Whether the modal window can be resized.
`allowScrolling`: Whether the modal window owns overflow scrolling.
`initialSize`: The initial size in unscaled editor units.
`minimumSize`: The minimum size in unscaled editor units. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.isVisible { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L163) | Gets whether the modal should currently be visible. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.blocksInteraction { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L168) | Gets whether the modal blocks regular editor interaction. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.canMove { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L173) | Gets whether the modal window can be moved. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.canResize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L178) | Gets whether the modal window can be resized. | — | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.allowScrolling { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L183) | Gets whether overflow scrolling belongs to the modal window. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Interactions.EditorModalExtension.Presentation.initialSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L188) | Gets the initial size in unscaled editor units. | — | — | +| [`public System.Numerics.Vector2 Inno.Editor.Interactions.EditorModalExtension.Presentation.minimumSize { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L193) | Gets the minimum size in unscaled editor units. | — | — | +| [`public override string Inno.Editor.Interactions.EditorModalExtension.Presentation.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorModalExtension.Presentation.operator !=(Inno.Editor.Interactions.EditorModalExtension.Presentation left, Inno.Editor.Interactions.EditorModalExtension.Presentation right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorModalExtension.Presentation.operator ==(Inno.Editor.Interactions.EditorModalExtension.Presentation left, Inno.Editor.Interactions.EditorModalExtension.Presentation right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorModalExtension.Presentation.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorModalExtension.Presentation.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorModalExtension.Presentation.Equals(Inno.Editor.Interactions.EditorModalExtension.Presentation other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorModalExtension.cs#L116) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Interactions.EditorPanelExtension`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L11) | Describes one active dockable panel extension. | — | — | +| [`public string Inno.Editor.Interactions.EditorPanelExtension.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L37) | Gets the stable panel identifier. | — | — | +| [`public string Inno.Editor.Interactions.EditorPanelExtension.title { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L42) | Gets the visible panel title. | — | — | +| [`public int Inno.Editor.Interactions.EditorPanelExtension.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L47) | Gets the stable panel ordering value. | — | — | +| [`public bool Inno.Editor.Interactions.EditorPanelExtension.isOpen { get; set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L52) | Gets or sets whether this panel is open in the current extension generation. | — | — | +| [`public bool Inno.Editor.Interactions.EditorPanelExtension.TakeFocusRequest()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L64) | Consumes a pending request to focus this panel window. | once for each accepted focus request. | — | +| [`public bool Inno.Editor.Interactions.EditorPanelExtension.TryGetWindowPresentation(out bool useWindowPadding, out bool allowScrolling, out System.Numerics.Vector2 initialSize)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L81) | Safely reads the panel window-presentation policy through the active extension boundary.
`useWindowPadding`: The requested padding policy, or the safe default when the panel is quarantined.
`allowScrolling`: The requested scrolling policy, or the safe default when the panel is quarantined.
`initialSize`: The preferred first-use floating size, or zero when native sizing is requested. | when the policy was read without quarantining the panel. | — | +| [`public bool Inno.Editor.Interactions.EditorPanelExtension.Draw(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorPanelExtension.cs#L118) | Safely draws the panel body and quarantines a failing extension instance.
`context`: The active editor context supplied by the presentation backend. | when the panel completed drawing without being quarantined. | `System.ArgumentNullException`: Thrown when is . | +| [`public interface Inno.Editor.Interactions.IEditorSelectionCoordinator`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L10) | Coordinates host-owned selection and the Core identity lifetime of temporary presentations. | — | — | +| [`System.IDisposable Inno.Editor.Interactions.IEditorSelectionCoordinator.RegisterIdentityDomain(Inno.Core.Identity.IdentityAllocator allocator)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L31) | Registers the Core identity domain used by a temporary scene presentation.
`allocator`: The borrowed allocator owned by that presentation's runtime session. | A routing lease released after restoring the preceding selection. | `System.ArgumentNullException`: The allocator is null.
`System.InvalidOperationException`: The domain is already active or the caller is not the owner thread.
`System.ObjectDisposedException`: The coordinator has retired. | +| [`object? Inno.Editor.Interactions.IEditorSelectionCoordinator.selectedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L36) | Gets the currently selected target, or when selection is empty. | — | — | +| [`void Inno.Editor.Interactions.IEditorSelectionCoordinator.SetSelection(object? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs#L44) | Replaces the current target after closing presentations owned by another target.
`target`: The new target, or to clear selection. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorSelectionSnapshot`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs#L10) | Captures ordered selection identities without retaining any domain object or extension instance. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorSelectionSnapshot.identities { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs#L25) | Gets unique selection identities in order; the final identity is the primary selection. | — | — | +| [`public Inno.Core.Identity.RuntimeIdentity? Inno.Editor.Interactions.EditorSelectionSnapshot.anchor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs#L29) | Gets the range anchor identity, or null when there is no registered anchor. | — | — | +| [`public int Inno.Editor.Interactions.EditorSelectionSnapshot.CopyTargets(Inno.Editor.Interactions.EditorInteractions interactions, System.Collections.Generic.ICollection destination) where TTarget : class`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs#L46) | Resolves this immutable selection in its original domains, including same-identity recovery after a generation change.
`interactions`: The active session supplying the original identity domains.
`destination`: A temporary caller-owned collection to clear and fill.
`TTarget`: The target type accepted by the current presenter. | The number of currently available compatible targets; missing members remain represented by this snapshot. | — | +| [`public Inno.Core.Identity.RuntimeIdentity? Inno.Editor.Interactions.EditorSelectionSnapshot.primary { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs#L64) | Gets the primary identity, or null when this snapshot contains no registered targets. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorSelectionState`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L10) | Owns ordered transient selection through domain-qualified identities instead of retaining live objects. | — | — | +| [`public object? Inno.Editor.Interactions.EditorSelectionState.selectedTarget { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L25) | Gets the current primary target for this callback, or null when unavailable or unselected. | — | — | +| [`public int Inno.Editor.Interactions.EditorSelectionState.count { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L29) | Gets the number of selected identities, including temporarily unavailable members. | — | — | +| [`public object? Inno.Editor.Interactions.EditorSelectionState.anchor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L33) | Gets the current range anchor, or null when the anchor is unavailable. | — | — | +| [`public ulong Inno.Editor.Interactions.EditorSelectionState.version { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L37) | Gets the selection revision, incremented only when membership, order or resolved generation changes. | — | — | +| [`public bool Inno.Editor.Interactions.EditorSelectionState.Contains(object? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L48) | Checks membership by the target's owning identity domain and generation.
`target`: The immediate target to inspect. | True when the target is a member of this selection. | — | +| [`public int Inno.Editor.Interactions.EditorSelectionState.CopyTargets(System.Collections.Generic.ICollection destination) where TTarget : class`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L78) | Copies currently available selected targets into a caller-owned temporary collection.
`destination`: The collection to clear and fill; do not retain live entries across callbacks.
`TTarget`: The target type accepted by the caller. | The number of targets resolved in this callback. | `System.ArgumentNullException`: The destination is null. | +| [`public Inno.Editor.Interactions.EditorSelectionSnapshot Inno.Editor.Interactions.EditorSelectionState.Capture()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L98) | Captures the ordered runtime identities without retaining their resolved objects. | An immutable callback-independent snapshot; resolve identities afresh before using targets. | — | +| [`public bool Inno.Editor.Interactions.EditorSelectionState.TryGet(out TTarget? target)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs#L117) | Resolves the primary selected target as the requested type for immediate use.
`target`: Receives the available primary target, or null.
`TTarget`: The expected target type. | True when the current primary target has the requested type. | — | +| [`public enum Inno.Editor.Interactions.EditorToolbarIcon : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L6) | Identifies a presentation-independent symbol for an editor toolbar command. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.None = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L11) | No symbol is requested. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.Play = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L16) | Starts an operation or simulation. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.Stop = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L21) | Stops an operation or simulation. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.Pause = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L26) | Pauses an operation or simulation. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.Step = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L31) | Advances a paused operation by one step. | — | — | +| [`Inno.Editor.Interactions.EditorToolbarIcon.Edit = 5`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarIcon.cs#L36) | Returns to an editing state. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorToolbarItem`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L6) | Describes one resolved toolbar command. | — | — | +| [`public string Inno.Editor.Interactions.EditorToolbarItem.actionId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L25) | Gets the stable action dispatched when this item is pressed. | — | — | +| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItem.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L30) | Gets the resolved presentation-independent symbol. | — | — | +| [`public string Inno.Editor.Interactions.EditorToolbarItem.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L35) | Gets the resolved contextual tooltip. | — | — | +| [`public int Inno.Editor.Interactions.EditorToolbarItem.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L40) | Gets the stable ordering value. | — | — | +| [`public Inno.Editor.Interactions.EditorActionState Inno.Editor.Interactions.EditorToolbarItem.status { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItem.cs#L45) | Gets the current action availability and checked state. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorToolbarItemAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L9) | Places an editor action on a compact toolbar surface. | — | — | +| [`public Inno.Editor.Interactions.EditorToolbarItemAttribute.EditorToolbarItemAttribute(string area, Inno.Editor.Interactions.EditorToolbarIcon icon, string tooltip, int order = 0, Inno.Editor.Interactions.EditorToolbarIcon activeIcon = Inno.Editor.Interactions.EditorToolbarIcon.None)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L37) | Creates a toolbar placement for the annotated targetless action.
`area`: The exact interaction area whose toolbar receives the action.
`icon`: The symbol shown while the action is not checked.
`tooltip`: The fallback tooltip shown when the action has no contextual display name.
`order`: The stable ordering value within the toolbar.
`activeIcon`: The symbol shown while the action is checked, or to retain . | — | `System.ArgumentException`: Thrown when or is empty, or when is .
`System.ArgumentOutOfRangeException`: Thrown when an icon value is not defined. | +| [`public string Inno.Editor.Interactions.EditorToolbarItemAttribute.area { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L65) | Gets the exact toolbar interaction area. | — | — | +| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItemAttribute.icon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L70) | Gets the symbol shown while the action is not checked. | — | — | +| [`public Inno.Editor.Interactions.EditorToolbarIcon Inno.Editor.Interactions.EditorToolbarItemAttribute.activeIcon { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L75) | Gets the optional replacement symbol shown while the action is checked. | — | — | +| [`public string Inno.Editor.Interactions.EditorToolbarItemAttribute.tooltip { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L80) | Gets the fallback tooltip. | — | — | +| [`public int Inno.Editor.Interactions.EditorToolbarItemAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarItemAttribute.cs#L85) | Gets the stable ordering value. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorToolbarModel`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarModel.cs#L8) | Contains an immutable resolved toolbar for one interaction area. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Interactions.EditorToolbarModel.items { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Toolbars/EditorToolbarModel.cs#L18) | Gets visible toolbar commands in deterministic display order. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorPlanarNavigation`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L9) | Shares captured planar gestures and mouse-anchored zoom math between scene and graph canvases. | — | — | +| [`public bool Inno.Editor.Interactions.EditorPlanarNavigation.isPanning { get; private set; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L16) | Gets whether this canvas owns a pan gesture, including while the pointer is outside its bounds. | — | — | +| [`public bool Inno.Editor.Interactions.EditorPlanarNavigation.Update(bool hovered, bool primaryPressed, bool middlePressed, bool primaryDown, bool middleDown, bool alt, bool allowAltPrimary = true)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L45) | Advances pointer capture using logical button state rather than window-local release events.
`hovered`: Whether a new gesture may start in this canvas.
`primaryPressed`: Whether the primary button was pressed this frame.
`middlePressed`: Whether the middle button was pressed this frame.
`primaryDown`: Current global primary button state.
`middleDown`: Current global middle button state.
`alt`: Whether Alt is held when the gesture starts.
`allowAltPrimary`: Whether Alt-primary belongs to pan rather than another navigation mode. | Whether this canvas owns the current pointer gesture. | — | +| [`public void Inno.Editor.Interactions.EditorPlanarNavigation.Cancel()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L67) | Releases capture when a canvas closes or a gesture is cancelled. | — | — | +| [`public static float Inno.Editor.Interactions.EditorPlanarNavigation.WheelFactor(float wheel, float sensitivity = 0.16)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L84) | Converts wheel input to the common exponential magnification used by Editor canvases.
`wheel`: Logical wheel units, independent of framebuffer DPI.
`sensitivity`: Positive exponential sensitivity. | A finite positive magnification factor; positive wheel magnifies content. | `System.ArgumentOutOfRangeException`: Input is non-finite or sensitivity is not positive. | +| [`public static System.Numerics.Vector2 Inno.Editor.Interactions.EditorPlanarNavigation.ZoomOrigin(System.Numerics.Vector2 origin, System.Numerics.Vector2 pivot, float previousScale, float nextScale)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L116) | Changes the screen-space origin so zoom preserves the same content point beneath the cursor.
`origin`: Current logical-pixel content origin.
`pivot`: Logical-pixel cursor position relative to the canvas.
`previousScale`: Previous positive content magnification.
`nextScale`: New positive content magnification after clamping. | The origin preserving the pivot's content coordinate. | `System.ArgumentOutOfRangeException`: An input is non-finite or a scale is not positive. | +| [`public Inno.Editor.Interactions.EditorPlanarNavigation.EditorPlanarNavigation()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorPlanarNavigation.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public enum Inno.Editor.Interactions.EditorViewportPointerPhase : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L10) | Selects the lifecycle phase of one viewport pointer sample. | — | — | +| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Down = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L15) | The pointer button began a gesture. | — | — | +| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Move = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L19) | The pointer moved or changed hover position. | — | — | +| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Up = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L23) | The pointer button ended a gesture. | — | — | +| [`Inno.Editor.Interactions.EditorViewportPointerPhase.Cancel = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L27) | The platform cancelled the gesture. | — | — | +| [`public enum Inno.Editor.Interactions.EditorViewportCursor : int`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L33) | Selects a presentation-independent cursor requested by a viewport tool. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.Arrow = 0`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L38) | Uses the normal arrow cursor. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.Crosshair = 1`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L42) | Uses a precise crosshair. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.Hand = 2`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L46) | Uses a hand suitable for panning. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.Move = 3`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L50) | Uses four-direction movement arrows. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.ResizeHorizontal = 4`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L54) | Uses horizontal resizing arrows. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.ResizeVertical = 5`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L58) | Uses vertical resizing arrows. | — | — | +| [`Inno.Editor.Interactions.EditorViewportCursor.Hidden = 6`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L62) | Hides the pointer while the tool owns it. | — | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorViewportShortcut : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Contains one presentation-independent shortcut sample for a focused viewport tool. | — | — | +| [`public Inno.Editor.Interactions.EditorViewportShortcut.EditorViewportShortcut(Inno.Core.Input.KeyCode key, Inno.Core.Input.KeyModifier modifiers = Inno.Core.Input.KeyModifier.None, bool repeat = false)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L82) | Creates one viewport shortcut sample.
`key`: Pressed key.
`modifiers`: Active keyboard modifiers.
`repeat`: Whether the platform generated an auto-repeat press. | — | — | +| [`public Inno.Core.Input.KeyCode Inno.Editor.Interactions.EditorViewportShortcut.key { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L95) | Gets the pressed key. | — | — | +| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorViewportShortcut.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L100) | Gets active keyboard modifiers. | — | — | +| [`public bool Inno.Editor.Interactions.EditorViewportShortcut.repeat { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L105) | Gets whether the shortcut is an auto-repeat press. | — | — | +| [`public override string Inno.Editor.Interactions.EditorViewportShortcut.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorViewportShortcut.operator !=(Inno.Editor.Interactions.EditorViewportShortcut left, Inno.Editor.Interactions.EditorViewportShortcut right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorViewportShortcut.operator ==(Inno.Editor.Interactions.EditorViewportShortcut left, Inno.Editor.Interactions.EditorViewportShortcut right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorViewportShortcut.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorViewportShortcut.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorViewportShortcut.Equals(Inno.Editor.Interactions.EditorViewportShortcut other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L68) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly record struct Inno.Editor.Interactions.EditorViewportPointerEvent : System.IEquatable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Contains one immutable viewport pointer sample in screen and world coordinates. | — | — | +| [`public Inno.Editor.Interactions.EditorViewportPointerEvent.EditorViewportPointerEvent(int pointerId, Inno.Editor.Interactions.EditorViewportPointerPhase phase, Inno.Core.Mathematics.Vector2 screenPosition, Inno.Core.Mathematics.Vector2 worldPosition, int button, Inno.Core.Input.KeyModifier modifiers)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L134) | Creates one viewport pointer sample.
`pointerId`: Platform pointer identity.
`phase`: Gesture lifecycle phase.
`screenPosition`: Viewport-local pixel position.
`worldPosition`: Corresponding world position.
`button`: Platform button number.
`modifiers`: Active keyboard modifiers. | — | — | +| [`public int Inno.Editor.Interactions.EditorViewportPointerEvent.pointerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L153) | Gets the platform pointer identity. | — | — | +| [`public Inno.Editor.Interactions.EditorViewportPointerPhase Inno.Editor.Interactions.EditorViewportPointerEvent.phase { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L158) | Gets the gesture lifecycle phase. | — | — | +| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportPointerEvent.screenPosition { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L163) | Gets the viewport-local pixel position. | — | — | +| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportPointerEvent.worldPosition { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L168) | Gets the corresponding world position. | — | — | +| [`public int Inno.Editor.Interactions.EditorViewportPointerEvent.button { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L173) | Gets the platform button number. | — | — | +| [`public Inno.Core.Input.KeyModifier Inno.Editor.Interactions.EditorViewportPointerEvent.modifiers { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L178) | Gets active keyboard modifiers. | — | — | +| [`public override string Inno.Editor.Interactions.EditorViewportPointerEvent.ToString()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Interactions.EditorViewportPointerEvent.operator !=(Inno.Editor.Interactions.EditorViewportPointerEvent left, Inno.Editor.Interactions.EditorViewportPointerEvent right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Interactions.EditorViewportPointerEvent.operator ==(Inno.Editor.Interactions.EditorViewportPointerEvent left, Inno.Editor.Interactions.EditorViewportPointerEvent right)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Interactions.EditorViewportPointerEvent.GetHashCode()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Interactions.EditorViewportPointerEvent.Equals(object obj)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Interactions.EditorViewportPointerEvent.Equals(Inno.Editor.Interactions.EditorViewportPointerEvent other)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L111) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | | [`public interface Inno.Editor.Interactions.IEditorViewportCoordinateConverter`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L184) | Converts coordinates for one viewport camera without exposing its presentation backend. | — | — | +| [`Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.IEditorViewportCoordinateConverter.ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L195) | Converts viewport-local pixel coordinates to world coordinates.
`screenPosition`: Viewport-local pixel coordinates. | World coordinates. | — | +| [`Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.IEditorViewportCoordinateConverter.WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L206) | Converts world coordinates to viewport-local pixel coordinates.
`worldPosition`: World coordinates. | Viewport-local pixel coordinates. | — | +| [`public abstract class Inno.Editor.Interactions.EditorViewportTool`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L212) | Defines one presentation-independent, reloadable viewport editing tool. | — | — | +| [`public abstract string Inno.Editor.Interactions.EditorViewportTool.id { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L217) | Gets the globally stable tool identity. | — | — | +| [`public virtual Inno.Editor.Interactions.EditorViewportCursor Inno.Editor.Interactions.EditorViewportTool.cursor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L222) | Gets the cursor requested while this tool is active. | — | — | +| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerDown(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L233) | Handles a pointer press.
`context`: Gesture and coordinate context.
`pointer`: Immutable pointer sample. | — | — | +| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerMove(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L249) | Handles pointer movement or hover.
`context`: Gesture and coordinate context.
`pointer`: Immutable pointer sample. | — | — | +| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerUp(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L265) | Handles a pointer release.
`context`: Gesture and coordinate context.
`pointer`: Immutable pointer sample. | — | — | +| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.OnPointerCancel(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L281) | Handles platform cancellation of an active gesture.
`context`: Gesture and coordinate context.
`pointer`: Immutable pointer sample. | — | — | +| [`public virtual bool Inno.Editor.Interactions.EditorViewportTool.OnShortcut(Inno.Editor.Interactions.EditorViewportToolContext context, Inno.Editor.Interactions.EditorViewportShortcut shortcut)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L302) | Handles a focused viewport shortcut before global action routing.
`context`: Gesture and coordinate context.
`shortcut`: Pressed key sample. | when the tool consumed the key. | — | +| [`public virtual void Inno.Editor.Interactions.EditorViewportTool.DrawOverlay(Inno.Editor.Interactions.EditorViewportToolContext context)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L316) | Draws transient gizmos and diagnostics over the viewport.
`context`: Gesture and coordinate context. | — | — | +| [`protected Inno.Editor.Interactions.EditorViewportTool.EditorViewportTool()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportTool.cs#L212) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Interactions.EditorViewportToolContext`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L8) | Coordinates pointer capture, transforms, and one history transaction for a viewport gesture. | — | — | +| [`public bool Inno.Editor.Interactions.EditorViewportToolContext.hasPointerCapture { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L25) | Gets whether this tool owns a pointer. | — | — | +| [`public int? Inno.Editor.Interactions.EditorViewportToolContext.capturedPointerId { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L29) | Gets the captured pointer identity, or . | — | — | +| [`public bool Inno.Editor.Interactions.EditorViewportToolContext.hasHistoryGesture { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L33) | Gets whether a history transaction is active for the current gesture. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolContext.CapturePointer(int pointerId)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L41) | Captures one pointer until explicit release or cancellation.
`pointerId`: Platform pointer identity. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolContext.ReleasePointer()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L51) | Releases the currently captured pointer. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolContext.BeginHistoryGesture(string name)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L64) | Begins the only history transaction allowed for the current captured gesture.
`name`: User-facing Undo operation name. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolContext.CompleteHistoryGesture(bool commit)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L80) | Commits or rolls back the current gesture transaction exactly once.
`commit`: to commit; otherwise, roll back. | — | — | +| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportToolContext.ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L107) | Converts viewport-local pixel coordinates to world coordinates.
`screenPosition`: Viewport-local pixel coordinates. | World coordinates. | — | +| [`public Inno.Core.Mathematics.Vector2 Inno.Editor.Interactions.EditorViewportToolContext.WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs#L117) | Converts world coordinates to viewport-local pixel coordinates.
`worldPosition`: World coordinates. | Viewport-local pixel coordinates. | — | +| [`public sealed class Inno.Editor.Interactions.EditorViewportToolSession : System.IDisposable`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L8) | Routes focused viewport input to one active tool while enforcing pointer-capture ownership. | — | — | +| [`public Inno.Editor.Interactions.EditorViewportToolSession.EditorViewportToolSession(Inno.Editor.Interactions.IEditorHistory history, Inno.Editor.Interactions.IEditorViewportCoordinateConverter coordinates)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L23) | Creates a viewport tool session.
`history`: History stack used by gesture transactions.
`coordinates`: Current viewport coordinate converter. | — | — | +| [`public Inno.Editor.Interactions.EditorViewportTool? Inno.Editor.Interactions.EditorViewportToolSession.tool { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L33) | Gets the active tool, or . | — | — | +| [`public Inno.Editor.Interactions.EditorViewportCursor Inno.Editor.Interactions.EditorViewportToolSession.cursor { get; }`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L38) | Gets the requested cursor for the active tool. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolSession.SetTool(Inno.Editor.Interactions.EditorViewportTool? tool)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L46) | Activates a tool after cancelling any current gesture.
`tool`: Tool to activate, or to deactivate. | — | — | +| [`public bool Inno.Editor.Interactions.EditorViewportToolSession.HandlePointer(Inno.Editor.Interactions.EditorViewportPointerEvent pointer)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L64) | Routes one immutable pointer sample.
`pointer`: Pointer sample with precomputed world coordinates. | when an active tool accepted the sample. | — | +| [`public bool Inno.Editor.Interactions.EditorViewportToolSession.HandleShortcut(Inno.Editor.Interactions.EditorViewportShortcut shortcut)`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L97) | Routes one focused keyboard shortcut.
`shortcut`: Pressed key sample. | when consumed. | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolSession.DrawOverlay()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L106) | Draws the active tool overlay. | — | — | +| [`public void Inno.Editor.Interactions.EditorViewportToolSession.Dispose()`](../../src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolSession.cs#L115) | Cancels active gesture state and deactivates the tool. | — | — | ## 项目依赖 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):公开引用边界由实际签名核对。 -- [Inno.Core.Events](../core/Inno.Core.Events.md):公开引用边界由实际签名核对。 -- [Inno.Core.Input](../core/Inno.Core.Input.md):公开引用边界由实际签名核对。 -- [Inno.Core.Mathematics](../core/Inno.Core.Mathematics.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Core.Identity` | 公开签名依赖 | +| `Inno.Core.Events` | 公开签名依赖 | +| `Inno.Core.Input` | 公开签名依赖 | +| `Inno.Core.Mathematics` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.FileBrowser.md b/docs/editor/Inno.Editor.Panel.FileBrowser.md index fb6f59ce7..181f4b6a8 100644 --- a/docs/editor/Inno.Editor.Panel.FileBrowser.md +++ b/docs/editor/Inno.Editor.Panel.FileBrowser.md @@ -36,8 +36,8 @@ Import Sample 的 Action 只启动资产层事务,`Samples/` 中的 feature Mo | `AssetEditorContext` | 当前 `EditorContext`、interactions、路径、Asset 信息和实例。 | | `AssetFileEntry` | EditorScript 可检查的源文件身份、路径、扩展名和只读状态。 | | `AssetIconAttribute` | 按 imported Asset 类型或 source extension 配置 Tree/List/Grid 共用图标;glyph 使用 `InnoEditor.ImGui.ImGuiIcon`。 | -| `AssetEditorModule.GetIcon` | 为其他 Editor presentation 解析完全相同的 Asset 图标。 | -| `AssetEditorModule.CreateSource` / `BeginCreatedSourceRename` | 让其他 Editor feature 复用原子 source 创建,并在用户发起创建时接入统一选择与 inline Rename。 | +| `AssetEditingModule.GetIcon` | 为其他 Editor presentation 解析完全相同的 Asset 图标。 | +| `AssetEditingModule.CreateSource` / `BeginCreatedSourceRename` | 让其他 Editor feature 复用原子 source 创建,并在用户发起创建时接入统一选择与 inline Rename。 | ## 为新 Asset 添加双击与右键行为 @@ -45,7 +45,8 @@ Import Sample 的 Action 只启动资产层事务,`Samples/` 中的 feature Mo using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; [AssetEditor(typeof(TextAsset), useForChildren: true, priority: 100)] public sealed class TextAssetEditor : AssetEditor @@ -59,7 +60,7 @@ public sealed class TextAssetEditor : AssetEditor Asset Rename/Delete 的物理事务始终由 `AssetPipeline` 执行。AssetEditor 只能验证以及接收提交后的通知,不能自行移动 source/meta/artifact,因此外部文件变化与 Editor 操作拥有同一身份规则。 -Create Asset、Create Folder、Import Sample、Rename、Move 与 Delete 都接入共享中立 Undo/Redo。每个 Asset 创建模板以及 Shader Editor 的创建、复制、折叠为 Subgraph 流程都必须通过 `AssetEditorModule.CreateSource`,并记录一个 `Create Asset` 历史项。该历史项保存完整 source 与 `.imeta`;Undo 删除已创建资产,Redo 恢复同一 persistent ID,而不是创建新的对象身份。Rename/Move 只记录 source/target path;Delete 与 Sample Import 同样把 source、目录结构和 `.imeta` 编码进 History payload。大 payload 自动落到 `/Library/Editor/History`,Undo 先在临时目录完整验证 archive,再提交回 Asset root 并 `Rescan`,因此恢复失败不会留下半个目录。目标发生外部冲突时操作失败并留在原栈,绝不覆盖新文件。Asset Browser selection 仅在文件系统事务成功后 best-effort 更新,通知异常不改变 History 结果。 +Create Asset、Create Folder、Import Sample、Rename、Move 与 Delete 都接入共享中立 Undo/Redo。每个 Asset 创建模板以及 Shader Editor 的创建、复制、折叠为 Subgraph 流程都必须通过 `AssetEditingModule.CreateSource`,并记录一个 `Create Asset` 历史项。该历史项保存完整 source 与 `.imeta`;Undo 删除已创建资产,Redo 恢复同一 persistent ID,而不是创建新的对象身份。Rename/Move 只记录 source/target path;Delete 与 Sample Import 同样把 source、目录结构和 `.imeta` 编码进 History payload。大 payload 自动落到 `/Library/Editor/History`,Undo 先在临时目录完整验证 archive,再提交回 Asset root 并 `Rescan`,因此恢复失败不会留下半个目录。目标发生外部冲突时操作失败并留在原栈,绝不覆盖新文件。Asset Browser selection 仅在文件系统事务成功后 best-effort 更新,通知异常不改变 History 结果。 ## 文件与目录移动 @@ -103,7 +104,8 @@ Material 与 Render Pipeline 已分别由自己的 feature 贡献模板。其他 ```csharp using Inno.Assets; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; [AssetIcon(typeof(TextAsset), "T", useForChildren: true, priority: 100)] [AssetIcon(".txt", "T")] @@ -120,12 +122,13 @@ internal static class TextAssetIcons 内建 Text、Binary、Scene、Prefab 和 Scripting 图标全部在 `BuiltInAssetIcons` 上使用 extension overload 声明,没有基于具体 Asset CLR 类型的引用。图标发现不依赖具体 Asset 类型项目;FileBrowser 的公开资产 API 仍声明 `Inno.Assets` 依赖,Sample module 通过实现依赖 `Inno.Editor.Scripting` 查询编译状态。内部 `AssetIconRegistry` 扫描当前 TypeCache snapshot 中的声明类型。EditorScripts 热重载时,新增或修改声明会随候选代际原子生效;移除声明或整个容器类型后,Registry 会释放旧映射并恢复优先级较低的内建声明,没有匹配时则使用通用 File icon。 -`AssetEditorModule.GetIcon(entry)` 是唯一对外 presentation resolver,同时通过 `IInspectionIconProvider` 向 Inspection 基础设施提供同一个规则。File Browser 的三种视图与 Asset Inspection Header 都调用该入口,不复制 extension switch,也不各自持有 Registry snapshot。Registry 先按类型/extension 选中声明;若 declaration 字符串是已注册 Settings path,就直接读取其中的 `value`,否则把它当作 literal glyph。Settings 基础项目不提供 icon resolver。 +`AssetEditingModule.GetIcon(entry)` 是唯一对外 presentation resolver,同时通过 `IInspectionIconProvider` 向 Inspection 基础设施提供同一个规则。File Browser 的三种视图与 Asset Inspection Header 都调用该入口,不复制 extension switch,也不各自持有 Registry snapshot。Registry 先按类型/extension 选中声明;若 declaration 字符串是已注册 Settings path,就直接读取其中的 `value`,否则把它当作 literal glyph。Settings 基础项目不提供 icon resolver。 ## Rename 与打开 - 快速双击调用 AssetEditor 的 Open。 - Rename 只能从 entry 的右键菜单、F2 快捷键或 Create Folder 的创建完成流程启动;单击、延迟单击和双击都不会进入重命名。 +- 树、列表与网格根据当前键盘焦点更新重命名的呈现位置。纯键盘导航后,输入框仍属于同一个区域,不要求先点击条目,也不复用另一个区域的旧鼠标位置。 - F2 会使用当前正在操作的 Tree、List 或 Grid 展示位置绘制输入框。 - Create Folder 完成后会选中新目录并自动进入重命名。 - Rename Action 自己持有输入/验证状态;Tree/List/Grid 只调用 `Present` 绘制 inline editor。 @@ -146,217 +149,30 @@ EditorScripts 使用 `InnoEditor.Assets` 扩展 AssetEditor、声明 AssetIcon/A ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Panel.FileBrowser.AssetBrowserRoot` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Panel.FileBrowser.AssetBrowserRoot.Assets = 0`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L21) | The writable project Assets authoring root. | — | — | -| [`Inno.Editor.Panel.FileBrowser.AssetBrowserRoot.Plugins = 1`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L26) | The read-only Plugins installation root. | — | — | -| [`public enum Inno.Editor.Panel.FileBrowser.AssetBrowserRoot : int`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L16) | Identifies the authoring or installed-content root displayed by the Asset Browser. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetBrowserState` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string? Inno.Editor.Panel.FileBrowser.AssetBrowserState.GetSelectedPath(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L88) | Gets the selected asset path when the editor-wide target belongs to the Asset Browser.
返回:The normalized source-relative path, or null when another target type is selected.
`context`:The shared editor context containing the global selection. | — | `System.ArgumentNullException`:Thrown when context is null. | -| [`public void Inno.Editor.Panel.FileBrowser.AssetBrowserState.Select(Inno.Editor.Core.EditorContext context, string? relativePath)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L171) | Selects an asset path through the editor-wide selection state, or clears Asset selection.
`context`:The shared editor context whose selection should be updated.
`relativePath`:The source-relative path to select, or an empty value to clear selection. | — | `System.ArgumentNullException`:Thrown when context is null. | -| [`public void Inno.Editor.Panel.FileBrowser.AssetBrowserState.SetCurrentDirectory(string relativePath)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L142) | Sets the current Asset Browser directory and infers its root from the isolated source identity.
`relativePath`:The isolated source-relative directory path, or an empty value for the currently displayed root. | — | — | -| [`public void Inno.Editor.Panel.FileBrowser.AssetBrowserState.SetRoot(Inno.Editor.Panel.FileBrowser.AssetBrowserRoot value)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L134) | Switches the displayed root while preserving the last directory visited in each root.
`value`:The authoring or installed-content root to display. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetBrowserState.currentDirectory { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L67) | Gets the current directory inside Inno.Editor.Panel.FileBrowser.AssetBrowserState.root. An empty value identifies that root's overview. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetBrowserState.projectDirectory { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L74) | Gets the most recently visited writable project directory, independently of the displayed root. | — | — | -| [`public Inno.Editor.Panel.FileBrowser.AssetBrowserRoot Inno.Editor.Panel.FileBrowser.AssetBrowserState.root { get; private set; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L62) | Gets the root currently displayed by the Asset Browser. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetBrowserState`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs#L32) | Stores asset browser navigation independently from global object selection. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetEditor` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Panel.FileBrowser.AssetEditor.AssetEditor()`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public virtual bool Inno.Editor.Panel.FileBrowser.AssetEditor.CanOpen(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L19) | Gets whether the fallback asset editor can open the supplied source entry.
返回:true when Inno.Editor.Panel.FileBrowser.AssetEditor.Open(Inno.Editor.Panel.FileBrowser.AssetEditorContext) can handle the entry; otherwise, false.
`context`:The immutable source and catalog snapshot for the operation. | — | — | -| [`public virtual bool Inno.Editor.Panel.FileBrowser.AssetEditor.CanStartDrag(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L97) | Gets whether the supplied entry can begin a managed editor drag operation.
返回:true when drag data can be created; otherwise, false.
`context`:The immutable source and catalog snapshot for the entry. | — | — | -| [`public virtual Inno.Editor.Interactions.EditorDragData Inno.Editor.Panel.FileBrowser.AssetEditor.CreateDragData(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L108) | Creates the managed source, preview label, and validity predicate for an asset drag.
返回:The managed drag data published by the Asset Browser.
`context`:The immutable source and catalog snapshot for the entry. | — | — | -| [`public virtual void Inno.Editor.Panel.FileBrowser.AssetEditor.OnDeleted(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L84) | Runs after an asset deletion transaction commits successfully.
`context`:The immutable snapshot captured before the entry was deleted. | — | — | -| [`public virtual void Inno.Editor.Panel.FileBrowser.AssetEditor.OnRenamed(Inno.Editor.Panel.FileBrowser.AssetEditorContext context, string oldPath, string newPath)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L60) | Runs after an asset move transaction commits successfully.
`context`:The immutable snapshot captured before the move.
`oldPath`:The previous source-relative path.
`newPath`:The committed source-relative destination path. | — | — | -| [`public virtual void Inno.Editor.Panel.FileBrowser.AssetEditor.Open(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L27) | Opens the supplied asset entry when no more specific typed open action handled it.
`context`:The immutable source and catalog snapshot for the operation. | — | — | -| [`public virtual Inno.Editor.Panel.FileBrowser.AssetOperationValidation Inno.Editor.Panel.FileBrowser.AssetEditor.ValidateDelete(Inno.Editor.Panel.FileBrowser.AssetEditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L76) | Validates a requested asset deletion before the AssetPipeline transaction begins.
返回:A valid result when the transaction may proceed, or a diagnostic describing the rejection.
`context`:The immutable snapshot of the entry that would be deleted. | — | — | -| [`public virtual Inno.Editor.Panel.FileBrowser.AssetOperationValidation Inno.Editor.Panel.FileBrowser.AssetEditor.ValidateRename(Inno.Editor.Panel.FileBrowser.AssetEditorContext context, string targetPath)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L43) | Validates a requested asset move before the AssetPipeline transaction begins.
返回:A valid result when the transaction may proceed, or a diagnostic describing the rejection.
`context`:The immutable snapshot of the source entry before the move.
`targetPath`:The normalized source-relative destination path. | — | — | -| [`public abstract class Inno.Editor.Panel.FileBrowser.AssetEditor`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs#L8) | Customizes editor interactions for one imported asset type. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetEditorAttribute` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.FileBrowser.AssetEditorAttribute.AssetEditorAttribute(System.Type assetType, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs#L26) | Creates an asset-editor registration for an imported runtime asset type.
`assetType`:The imported asset type handled by the editor.
`useForChildren`:Whether the editor may also handle assignable derived asset types.
`priority`:The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`:Thrown when assetType is null. | -| [`public System.Type Inno.Editor.Panel.FileBrowser.AssetEditorAttribute.assetType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs#L39) | Gets the imported asset type handled by the editor. | — | — | -| [`public int Inno.Editor.Panel.FileBrowser.AssetEditorAttribute.priority { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs#L49) | Gets the tie-breaking priority. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetEditorAttribute.useForChildren { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs#L44) | Gets whether derived asset types are accepted. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetEditorAttribute : System.Attribute`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs#L9) | Associates an asset editor with an imported asset type. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetEditorContext` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Type? Inno.Editor.Panel.FileBrowser.AssetEditorContext.assetType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L100) | Gets the resolved imported asset type when available. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Panel.FileBrowser.AssetEditorContext.editorContext { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L70) | Gets the shared editor context. | — | — | -| [`public Inno.Assets.AssetInfo? Inno.Editor.Panel.FileBrowser.AssetEditorContext.info { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L95) | Gets the cataloged asset information when available. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Panel.FileBrowser.AssetEditorContext.interactions { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L75) | Gets the active editor interaction entry point. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetEditorContext.isDirectory { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L90) | Gets whether the source represents a directory. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetEditorContext.name { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L85) | Gets the final source path segment. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetEditorContext.relativePath { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L80) | Gets the source-relative path. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetEditorContext`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs#L12) | Provides an immutable snapshot for an asset editor operation. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetEditorModule` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.FileBrowser.AssetEditorModule.AssetEditorModule(Inno.Editor.Interactions.EditorInteractions interactions, Inno.Editor.Settings.EditorSettings settings, Inno.Assets.Pipeline.AssetPipeline pipeline, Inno.Plugins.Authoring.PluginEnvironment plugins, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Logging.LogRouter logs)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L61) | Creates the Asset Browser feature module.
`interactions`:The active editor interaction entry point.
`settings`:The project Settings service that owns semantic icon values.
`pipeline`:The isolated authoring asset pipeline used by browser operations.
`plugins`:The active Plugin catalog that authoritatively identifies Plugin-owned source mounts.
`types`:The active type catalog used to build extension registry snapshots.
`logs`:The application log router used for Asset Browser operation diagnostics. | — | `System.ArgumentNullException`:Thrown when any required service is null. | -| [`public void Inno.Editor.Panel.FileBrowser.AssetEditorModule.BeginCreatedSourceRename(Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs#L22) | Selects a newly created source and starts its shared inline rename interaction.
`entry`:The newly indexed writable source. | — | — | -| [`protected override void Inno.Editor.Panel.FileBrowser.AssetEditorModule.Capture(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L93) | Captures an immutable snapshot of the current observable state.
`state`:The lifecycle or domain state applied by this operation. | — | — | -| [`public Inno.Assets.Pipeline.AssetFileEntry Inno.Editor.Panel.FileBrowser.AssetEditorModule.CreateSource(Inno.Assets.AssetPath path, System.ReadOnlySpan bytes)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs#L48) | Creates a native or ordinary-text asset source as one recoverable shared-history operation.
返回:The indexed source identity, even when its first import reports a source error.
`path`:A new file in an existing writable asset directory.
`bytes`:Complete source bytes supplied by the feature's native writer. | — | `System.IO.IOException`:The source already exists, has no existing parent, or cannot be installed.
`System.InvalidOperationException`:The source mount is unavailable/read-only or history cannot retain the change. | -| [`public string Inno.Editor.Panel.FileBrowser.AssetEditorModule.GetIcon(Inno.Assets.Pipeline.AssetFileEntry entry)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L542) | Resolves the presentation icon registered for an asset type or source extension.
返回:The most specific registered icon, or the built-in directory or file icon when no registration matches the entry.
`entry`:The source entry whose presentation icon should be resolved. | — | `System.ArgumentNullException`:Thrown when entry is null. | -| [`protected override void Inno.Editor.Panel.FileBrowser.AssetEditorModule.OnDispose()`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L582) | Releases resources retained by this feature after it has stopped. | — | — | -| [`protected override void Inno.Editor.Panel.FileBrowser.AssetEditorModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L563) | Initializes this feature when its owning runtime becomes active.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Panel.FileBrowser.AssetEditorModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L574) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Panel.FileBrowser.AssetEditorModule.Restore(Inno.Editor.Core.EditorState state)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L106) | Restores the supplied snapshot while preserving current invariants.
`state`:The lifecycle or domain state applied by this operation. | — | — | -| [`public Inno.Editor.Panel.FileBrowser.AssetBrowserState Inno.Editor.Panel.FileBrowser.AssetEditorModule.browser { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs#L85) | Gets shared Asset Browser navigation and selection state. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetEditorModule : Inno.Editor.Core.EditorModule, Inno.Editor.Inspection.IInspectionIconProvider`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs#L14) | Handles asset creation actions in the Editor file browser. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetIconAttribute` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.FileBrowser.AssetIconAttribute.AssetIconAttribute(string extension, string icon, int priority = 0)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L71) | Creates an icon declaration for files ending with a source extension.
`extension`:The simple or compound file extension. A leading period is optional and matching is case-insensitive.
`icon`:The Editor icon glyph to render.
`priority`:The tie-breaking priority after extension specificity. | — | `System.ArgumentException`:Thrown when extension is empty or contains a path separator, or when icon is empty. | -| [`public Inno.Editor.Panel.FileBrowser.AssetIconAttribute.AssetIconAttribute(System.Type assetType, string icon, bool useForChildren = false, int priority = 0)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L43) | Creates an icon declaration using a glyph from the Editor icon catalog.
`assetType`:The imported asset type represented by the icon.
`icon`:The Editor icon glyph to render.
`useForChildren`:Whether the declaration may also represent derived asset types.
`priority`:The tie-breaking priority after exactness and inheritance distance. | — | `System.ArgumentNullException`:Thrown when assetType is null.
`System.ArgumentException`:Thrown when icon is empty. | -| [`public System.Type? Inno.Editor.Panel.FileBrowser.AssetIconAttribute.assetType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L85) | Gets the imported asset type represented by this declaration, or null for an extension declaration. | — | — | -| [`public string? Inno.Editor.Panel.FileBrowser.AssetIconAttribute.extension { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L91) | Gets the normalized source extension represented by this declaration, or null for a type declaration. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetIconAttribute.icon { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L96) | Gets the Editor icon glyph to render. | — | — | -| [`public int Inno.Editor.Panel.FileBrowser.AssetIconAttribute.priority { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L107) | Gets the tie-breaking priority after target specificity. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetIconAttribute.useForChildren { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L102) | Gets whether a type declaration may also represent derived asset types. Extension declarations always return false. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetIconAttribute : System.Attribute`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs#L20) | Declares the icon used by the Asset Browser for an imported asset type or source extension. | Apply one or more declarations to a container type. The container has no runtime responsibility; it only keeps related icon declarations together so the type catalog can discover them. | — | - -### `Inno.Editor.Panel.FileBrowser.AssetImportSettingsEdits` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.FileBrowser.AssetImportSettingsEdits.AssetImportSettingsEdits(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Types.TypeCatalog types, Inno.Editor.Interactions.EditorInteractions interactions)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs#L39) | Uses the authoring owners responsible for source identity, converter generations and shared undo.
`assets`:Authoritative asset pipeline.
`serialization`:Current owner converter registry.
`types`:Current type generation owner.
`interactions`:Shared Editor history owner. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetImportSettingsEdits.Apply(Inno.Assets.AssetPath path, Inno.Core.Serialization.ISerializable settings, string expectedFingerprint)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs#L66) | Saves one settings gesture, recording neutral before/after properties even when reimport reports an error.
返回:Whether the saved configuration reimported successfully; false does not mean the settings were discarded.
`path`:Writable source asset path.
`settings`:Detached current-generation settings value.
`expectedFingerprint`:Sidecar fingerprint captured when editing began. | — | — | -| [`public sealed class Inno.Editor.Panel.FileBrowser.AssetImportSettingsEdits : Inno.Editor.Core.EditorModule`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs#L16) | Applies importer configuration through the common sidecar pipeline and stable-identity Editor history. | — | — | - -### `Inno.Editor.Panel.FileBrowser.AssetOperationValidation` - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.FileBrowser.AssetOperationValidation.AssetOperationValidation(bool isValid, string message)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L17) | Creates the validation result returned before an asset transaction begins.
`isValid`:Whether the requested asset operation may proceed.
`message`:The user-facing rejection diagnostic, or an empty string for a valid result. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetOperationValidation.Equals(Inno.Editor.Panel.FileBrowser.AssetOperationValidation other)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Panel.FileBrowser.AssetOperationValidation.Equals(object obj)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Panel.FileBrowser.AssetOperationValidation.GetHashCode()`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static Inno.Editor.Panel.FileBrowser.AssetOperationValidation Inno.Editor.Panel.FileBrowser.AssetOperationValidation.Invalid(string message)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L49) | Creates a failed asset-operation validation result.
返回:An invalid validation result containing the supplied diagnostic.
`message`:The user-facing diagnostic explaining why the operation was rejected. | — | — | -| [`public static bool Inno.Editor.Panel.FileBrowser.AssetOperationValidation.operator ==(Inno.Editor.Panel.FileBrowser.AssetOperationValidation left, Inno.Editor.Panel.FileBrowser.AssetOperationValidation right)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Panel.FileBrowser.AssetOperationValidation.operator !=(Inno.Editor.Panel.FileBrowser.AssetOperationValidation left, Inno.Editor.Panel.FileBrowser.AssetOperationValidation right)`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Panel.FileBrowser.AssetOperationValidation.ToString()`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Panel.FileBrowser.AssetOperationValidation.isValid { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L28) | Gets whether the operation may proceed. | — | — | -| [`public string Inno.Editor.Panel.FileBrowser.AssetOperationValidation.message { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L33) | Gets the validation diagnostic. | — | — | -| [`public static Inno.Editor.Panel.FileBrowser.AssetOperationValidation Inno.Editor.Panel.FileBrowser.AssetOperationValidation.valid { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L38) | Gets a successful validation result. | — | — | -| [`public readonly record struct Inno.Editor.Panel.FileBrowser.AssetOperationValidation : System.IEquatable`](../../src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs#L6) | Describes whether an editor asset operation may proceed. | — | — | +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Scripting.Compiler](../scripting/Inno.Scripting.Compiler.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Assets](Inno.Editor.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scripting](Inno.Editor.Scripting.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Inspection](Inno.Editor.Inspection.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Plugins.Authoring](../plugins/Inno.Plugins.Authoring.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Plugins.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scripting` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Inspection` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.GameView.md b/docs/editor/Inno.Editor.Panel.GameView.md index 7a052a49e..2c473c26d 100644 --- a/docs/editor/Inno.Editor.Panel.GameView.md +++ b/docs/editor/Inno.Editor.Panel.GameView.md @@ -24,23 +24,24 @@ Editor 的通用 `RenderRuntime` 不注入任何隐式 Scene content。Scene Vie ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Rendering](Inno.Editor.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.PlayMode](Inno.Editor.PlayMode.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.References](../references/Inno.References.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Runtime](../rendering/Inno.Rendering.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.References` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Hierarchy.md b/docs/editor/Inno.Editor.Panel.Hierarchy.md index 6d49f86ea..14881d1b3 100644 --- a/docs/editor/Inno.Editor.Panel.Hierarchy.md +++ b/docs/editor/Inno.Editor.Panel.Hierarchy.md @@ -4,6 +4,8 @@ 该项目拥有 Hierarchy Panel、Scene/GameObject Action、上下文菜单以及 Scene/GameObject 拖放排序。Scene document 和可逆编辑实现位于独立的 `Inno.Editor.Scene`;Hierarchy 只把查询/工作流交给 `IEditorSceneWorkspace`,把可逆修改交给 `SceneEdits`,不维护图快照或 Undo 实现。 +内部 `Styling/HierarchyStyle` 拥有层级行距、重命名与空白拖放区域的尺寸,以及 inactive 行的颜色。可见行缓存只保存 Core runtime identity 和 depth,并由 Scene hierarchy revision 与展开状态更新;正常离屏行不提交完整控件。公开 drop target 是当前同步请求的借用,不可保存到后续帧、History 或 generation。 + ## 公共 API | API | 作用 | @@ -82,48 +84,40 @@ EditorScripts 使用 `InnoEditor.Hierarchy` 获取公开 drop target;Attribute ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget.HierarchyObjectDropTarget(Inno.Scene.GameObject gameObject)`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L22) | Creates a hierarchy drop target representing one live game object row.
`gameObject`:The game object represented by the target row. | — | `System.ArgumentNullException`:Thrown when gameObject is null. | -| [`public Inno.Scene.GameObject Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget.gameObject { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L30) | Gets the target game object. | — | — | -| [`public sealed class Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L11) | Identifies a game object hierarchy drop target. | — | — | - -### `Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget.HierarchySceneDropTarget(Inno.Scene.GameScene scene)`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchySceneDropTarget.cs#L22) | Creates a hierarchy drop target representing one loaded scene row or root.
`scene`:The loaded scene represented by the target. | — | `System.ArgumentNullException`:Thrown when scene is null. | -| [`public Inno.Scene.GameScene Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget.scene { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchySceneDropTarget.cs#L30) | Gets the target scene. | — | — | +| [`public sealed class Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L11) | Identifies a game object hierarchy drop target. | — | — | +| [`public Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget.HierarchyObjectDropTarget(Inno.Scene.GameObject gameObject)`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L22) | Creates a hierarchy drop target representing one live game object row.
`gameObject`: The game object represented by the target row. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Scene.GameObject Inno.Editor.Panel.Hierarchy.HierarchyObjectDropTarget.gameObject { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchyObjectDropTarget.cs#L30) | Gets the target game object. | — | — | | [`public sealed class Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchySceneDropTarget.cs#L11) | Identifies a scene row or scene root hierarchy drop target. | — | — | +| [`public Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget.HierarchySceneDropTarget(Inno.Scene.GameScene scene)`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchySceneDropTarget.cs#L22) | Creates a hierarchy drop target representing one loaded scene row or root.
`scene`: The loaded scene represented by the target. | — | `System.ArgumentNullException`: Thrown when is . | +| [`public Inno.Scene.GameScene Inno.Editor.Panel.Hierarchy.HierarchySceneDropTarget.scene { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/HierarchySceneDropTarget.cs#L30) | Gets the target scene. | — | — | ## 项目依赖 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Panel.FileBrowser](Inno.Editor.Panel.FileBrowser.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene.Assets](../scene/Inno.Scene.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene](../scene/Inno.Scene.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Scene` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Inspector.md b/docs/editor/Inno.Editor.Panel.Inspector.md index d0c047853..87468042f 100644 --- a/docs/editor/Inno.Editor.Panel.Inspector.md +++ b/docs/editor/Inno.Editor.Panel.Inspector.md @@ -18,7 +18,7 @@ Play Mode 中 Project scene 的运行副本保持可编辑;Inspector 的提示 Target Header 右上角提供 lock/unlock 控件,其交互面积、图标居中与 hover 表现和 Panel Tab Bar 的关闭 X 使用同一套 compact icon widget。锁定只固定 Inspector 当前展示目标,不修改全局 Selection;Hierarchy 和 File Browser 可以继续选择其他对象,以便把它们拖到被锁定目标的属性上。Scene identity 只以 persistent ID 保留并从当前 Edit/Play Session 重新解析,因此 assembly reload 后会指向 replacement 或 Missing placeholder;没有稳定 identity 的 collectible target 只保留弱引用。锁定目标被销毁、移除或无法重解析时自动解锁,不会固定退休 Plugin/Script ALC。 -Asset target Drawer 由 FileBrowser 项目自身提供,并通过 `IInspectionIconProvider` 复用 `AssetEditorModule` 的 type/extension icon registry;因此 File Browser Tree/List/Grid 与 Inspector Header 始终一致,EditorScripts 热重载图标声明后两处会同时更新。第二行 source path 使用与 File Browser 底部 breadcrumb 相同的半透明 palette color。Plugin Source Mount 根使用 `IPlugin` 类型,不伪装成普通 Directory,也不创建 `.iplugin` companion asset。 +Asset target Drawer 由 FileBrowser 项目自身提供,并通过 `IInspectionIconProvider` 复用 `AssetEditingModule` 的 type/extension icon registry;因此 File Browser Tree/List/Grid 与 Inspector Header 始终一致,EditorScripts 热重载图标声明后两处会同时更新。第二行 source path 使用与 File Browser 底部 breadcrumb 相同的半透明 palette color。Plugin Source Mount 根使用 `IPlugin` 类型,不伪装成普通 Directory,也不创建 `.iplugin` companion asset。 GameObject Header 的第二行包含 Active、项目 Tag picker 和 Layer picker。`SceneProjectSettingsModule` 按 `ProjectSettingsStore.revision` 刷新隔离的 `GameTagCatalog` 与 `GameLayerCatalog` 快照;Inspector 不从 `editor.ini` 或 `Settings.Editor.inno` 建立第二份 catalog。对象修改通过 `SceneEdits` 进入 Scene History。 @@ -87,57 +87,52 @@ EditorScripts 使用 `InnoEditor.Inspection`,可声明 InspectionDrawer、Prop ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Panel.Inspector.AssetReferenceDropTarget` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.AssetReferenceDropTarget(System.Type expectedType, System.Action assign)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L28) | Creates a drop target that validates an asset type and assigns its persistent identity to a property.
`expectedType`:The runtime asset type accepted by the property.
`assign`:The callback that writes an accepted persistent identity to the property. | — | `System.ArgumentNullException`:Thrown when either argument is null.
`System.InvalidOperationException`:Thrown when the type does not belong to the active type catalog. | -| [`public void Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.Assign(System.Guid persistentId)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L48) | Assigns a persistent asset identity to the represented property.
`persistentId`:The stable persistent identity used for lookup. | — | — | -| [`public System.Type Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.expectedType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L40) | Gets the required asset type. | — | — | -| [`public sealed class Inno.Editor.Panel.Inspector.AssetReferenceDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L8) | Provides an assignable asset-reference property drop target. | — | — | - -### `Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget` - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.EngineObjectReferenceDropTarget(System.Type expectedType, System.Action assign)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L31) | Creates a drop target that validates and assigns an engine object to a serialized property.
`expectedType`:The engine object type accepted by the property.
`assign`:The callback that writes an accepted object to the property. | — | `System.ArgumentNullException`:Thrown when either argument is null.
`System.InvalidOperationException`:Thrown when the type does not belong to the active type catalog. | -| [`public void Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.Assign(Inno.Scene.EngineObject value)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L51) | Assigns an accepted engine object to the represented property.
`value`:The concrete value read or transformed by this operation. | — | — | -| [`public System.Type Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.expectedType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L43) | Gets the required engine object type. | — | — | -| [`public sealed class Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L11) | Provides an assignable engine-object-reference property drop target. | — | — | +| [`public sealed class Inno.Editor.Panel.Inspector.AssetReferenceDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L12) | Borrows an asset property's type and assignment callback for one synchronous drop request. | — | — | +| [`public Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.AssetReferenceDropTarget(System.Type expectedType, System.Action assign)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L29) | Creates a drop target that validates an asset type and assigns its persistent identity to a property.
`expectedType`: The runtime asset type accepted by the property.
`assign`: The callback that writes an accepted persistent identity to the property. | — | `System.ArgumentNullException`: Thrown when either argument is . | +| [`public System.Type Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.expectedType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L41) | Gets the required asset type. | — | — | +| [`public void Inno.Editor.Panel.Inspector.AssetReferenceDropTarget.Assign(System.Guid persistentId)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs#L49) | Assigns a persistent asset identity to the represented property.
`persistentId`: The stable persistent identity used for lookup. | — | — | +| [`public sealed class Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L15) | Borrows an object property's type and assignment callback for one synchronous drop request. | — | — | +| [`public Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.EngineObjectReferenceDropTarget(System.Type expectedType, System.Action assign)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L32) | Creates a drop target that validates and assigns an engine object to a serialized property.
`expectedType`: The engine object type accepted by the property.
`assign`: The callback that writes an accepted object to the property. | — | `System.ArgumentNullException`: Thrown when either argument is . | +| [`public System.Type Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.expectedType { get; }`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L44) | Gets the required engine object type. | — | — | +| [`public void Inno.Editor.Panel.Inspector.EngineObjectReferenceDropTarget.Assign(Inno.Scene.EngineObject value)`](../../src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs#L52) | Assigns an accepted engine object to the represented property.
`value`: The concrete value read or transformed by this operation. | — | — | ## 项目依赖 -- [Inno.Editor.Inspection](Inno.Editor.Inspection.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.PlayMode](Inno.Editor.PlayMode.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene.Assets](../scene/Inno.Scene.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene](../scene/Inno.Scene.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Shaders.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Inspection` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Scene` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Logging.md b/docs/editor/Inno.Editor.Panel.Logging.md index bfad0a844..6fa6a8e18 100644 --- a/docs/editor/Inno.Editor.Panel.Logging.md +++ b/docs/editor/Inno.Editor.Panel.Logging.md @@ -4,11 +4,15 @@ 该项目同时订阅彼此独立的 `Inno.Core.Logging` 与 `Inno.Core.Diagnostics`,并把两种数据组合成统一的 Editor Console。目录和项目名使用 `Logging`,避免 `Log` 被 ignore 规则或输出目录约定误判。 +内部 `Styling/ConsoleStyle` 拥有等级颜色、日志卡片、展开按钮与自动滚动容差;通用 Palette 不再保存 Console 策略。这些内部值不属于脚本公开 API。可变高度测量和原生子窗口槽位由 Panel 维护,按内容、宽度、尺度和展开状态失效,退休记录不会按操作次数积累原生窗口。 + ## 组成 - `EditorConsole`:位于 `Inno.Editor.Diagnostics`,拥有有界日志、当前诊断、全局 fingerprint 分组和 Play Session 保留策略。 - `ConsolePanel`:只读取 `IEditorConsole` 的不可变 Snapshot,负责等级过滤、折叠、详情和操作表现。 +搜索采用不区分大小写的普通子串匹配,覆盖 message、source、file、code、category 和已有 stack trace;与等级过滤共同生效。派生视图只在 snapshot revision、查询或筛选变化时更新,不修改 Diagnostics 原始记录。Collapse 只统计匹配 occurrences,展开详情和 Copy Message/Copy Full Entry 指向同一匹配组。复制目标由当前视图拥有,Clear、过滤失去目标或 Panel 退休时释放;不会为每个可见行预先编码完整复制文本。当前选择支持键盘导航与 Enter 展开,主修饰键+C / 主修饰键+Shift+C 复用同一 Action。 + Console 根 Panel 与布局 Child 不滚动,仅 entry region 承担纵向滚动,避免父子同时显示滚动条。当滚动条原本位于底部时,新日志会继续滚到底部;用户向上浏览后不会抢夺滚动位置。按钮、折叠三角、行距与颜色全部来自 `ImGuiWidget`、`EditorStyleMetrics` 和 `EditorPalette`。相邻 card 只使用一次标准 `ItemSpacing`,不会再叠加额外占位元素,因此展开与收起状态都保持确定、均匀的外部间距。 内部实现中,Console entry identity 由来源种类与完整 64 位序号共同组成。普通 Log 与 Diagnostic 的计数空间彼此独立,ImGui scope 会分别压入来源、序号高位和低位,不能直接使用 `long.GetHashCode()` 代替身份;后者会让部分正负序号产生相同的 32 位值,并导致 auto-resize child 错误复用另一张 card 的布局状态。 @@ -38,21 +42,21 @@ Console card header 显示等级和来源,例如 `[Info] [Log]` 或 `[Error] [ ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Diagnostics](Inno.Editor.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.PlayMode](Inno.Editor.PlayMode.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.SceneView.md b/docs/editor/Inno.Editor.Panel.SceneView.md index 2fc406069..d9162695a 100644 --- a/docs/editor/Inno.Editor.Panel.SceneView.md +++ b/docs/editor/Inno.Editor.Panel.SceneView.md @@ -37,25 +37,25 @@ Contributor 在 `EditorViewportManipulationSpace.plane` 声明平面时,Scene ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGuizmo](../backends/ImGui/Inno.Native.ImGuizmo.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Rendering](Inno.Editor.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Mathematics](../core/Inno.Core.Mathematics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene](../scene/Inno.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.References](../references/Inno.References.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.PlayMode` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Core.Mathematics` | 实现依赖;不传递 compile surface | +| `Inno.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.References` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Settings.md b/docs/editor/Inno.Editor.Panel.Settings.md index d435701dc..3d8f22de8 100644 --- a/docs/editor/Inno.Editor.Panel.Settings.md +++ b/docs/editor/Inno.Editor.Panel.Settings.md @@ -11,6 +11,8 @@ Settings └─ Build/... → Settings.Build.inno ``` +Settings 专用字段底色与内边距由内部 `Styling/SettingsStyle` 维护。它只读取当前帧的共享尺度,不把 Settings 策略或 Session 引用放回公共 metrics。 + ## 窗口行为 - 主菜单 `Edit/Settings...` 打开可移动、可缩放但不可 Dock/Collapse 的 Modal。 @@ -21,17 +23,20 @@ Settings - 任何拥有子页面的非叶页面都会在 description 下方自动显示相同的子页面跳转列表,即使该页面自身同时拥有设置字段;字段仍从跳转列表下方开始,不要求每个页面重复编写导航 UI。 - 右侧每个完整字段使用自动内容行高;label/content/reset 保持对齐,连续行之间没有空隙。Reset 固定宽度扣除后,label/content 使用共享 2:3 比例,长标签在本列换行。Field Table 严格使用 page 的真实 content width,不通过负 cursor 或扩大 table width 穿透 padding,因此不会污染 `CursorMaxPos` 或产生虚假水平滚动范围。左右 gutter 背景作为不参与 layout 的 draw-list geometry 延伸至内容窗口边缘,文字与控件继续使用正常 window/cell inset。两种背景使用轻微明度差和固定 `0.005` alpha,只辅助辨认连续字段而不形成明显色块。 - 合成页面不需要中央 page 注册;frontend 根据 slash-delimited path 自动补齐祖先。 -- Catalog generation 改变时丢弃旧 staged generation,按新 definitions 原子重建窗口 session。 +- 窗口借用当前 generation 的 definitions 和隔离编辑值。打开期间通过既有 reload admission guard 延迟脚本请求与激活;关闭后恢复许可,不在后台丢弃未提交草稿。导航只跟踪中立 session revision,不保存上一次编辑 Session。 ## 单一 Apply 操作 底部根据 staged state 使用两种互斥状态:没有变化时只显示右对齐的 `OK`;存在变化时显示右对齐的 `Cancel` 与 `Apply`,其中 `Cancel` 在左、`Apply` 在右: -- `Apply` 一次提交所有 dirty scope;Editor、Project、Build 分别原子写入 `Settings.Editor.inno`、`Settings.Project.inno`、`Settings.Build.inno`,并形成各自的 History entry。 +- `Apply` 使用同一个既有 History transaction 提交所有 dirty scope;三个 owner 分别原子写入自己的文件,全部成功后形成一个 `Apply Settings` Undo/Redo entry,才更新草稿基线。 - `Cancel` 同时丢弃三个域尚未 Apply 的隔离 staged 值并关闭窗口。 - `OK` 仅在 session clean 时关闭窗口,不执行无意义的持久化。 - `Apply` 仅在 staged effective value 相对窗口打开或上次 Apply 的基线真正变化时启用。把一次未提交修改 Reset 回原始默认值不会留下虚假的 reset intent。 -- 三个持久化域仍分别保证各自的原子写入与 History;单一按钮不把三个文件伪装成一个跨文件事务。 +- 任一写入或 History 记录失败时,通过既有事务回滚本次已应用修改。普通文件或校验失败留在窗口内显示,草稿可重试或取消,完整诊断进入 Console,不因此隔离整个 Modal。 +- 回滚无法证明状态完整时保留原始与回滚诊断,遵守 History 的 Faulted 状态,禁用 Apply 并明确要求重启。关闭失败前不会释放仍承担恢复的 Session/transaction owner。 +- 意外 Drawer/呈现异常仍由既有 Registry 隔离;先取消窗口的临时编辑 owner,释放正常 reload admission。Settings 动作明确禁用并说明需恢复扩展;catalog 更新后才能重开。不会留下已不可操作、却永久阻塞重载的 Modal。 +- 这是进程内的领域回滚和单一 History 操作;三个文件仍是各自原子写入,不宣称断电或进程崩溃期间具有跨文件提交保证。 Reset Editor 恢复字段定义的 `defaultValue`。Reset Project 删除项目 override,并恢复 Host 默认值与依赖有序 Plugin 默认贡献的合成结果。Reset Build 恢复由项目名、host target 和首个已导入 Scene 建立的 project-derived 默认值。页面顶部 Reset 会递归作用于当前页面所有后代字段,但提交仍遵循各自数据域。 @@ -110,20 +115,21 @@ Settings frontend 不内建 Boolean、Layer、Tag、PBR 或其他业务字段类 ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Build](../build/Inno.Build.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Build.Managed](../build/Inno.Build.Managed.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Build` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Build.Managed` | 实现依赖;不传递 compile surface | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.ShaderEditor.md b/docs/editor/Inno.Editor.Panel.ShaderEditor.md index a59f3db1b..9a1bac244 100644 --- a/docs/editor/Inno.Editor.Panel.ShaderEditor.md +++ b/docs/editor/Inno.Editor.Panel.ShaderEditor.md @@ -89,32 +89,36 @@ Stage Input 的 `Source = Builtin` 表示该值由 GPU 阶段或引擎/Adapter ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Editor.Inspection](Inno.Editor.Inspection.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Shaders](Inno.Editor.Shaders.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Rendering](Inno.Editor.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Panel.FileBrowser](Inno.Editor.Panel.FileBrowser.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Input](../core/Inno.Core.Input.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Shaders](../rendering/Inno.Rendering.Shaders.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets](../rendering/Inno.Rendering.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Graph](Inno.Editor.Graph.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Graphs](../core/Inno.Core.Graphs.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets.Authoring](../rendering/Inno.Rendering.Assets.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.Graph.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Shaders.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Inspection` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Core.Input` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Rendering` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Graph` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Core.Graphs` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Stats.md b/docs/editor/Inno.Editor.Panel.Stats.md index 347e01753..63f41d091 100644 --- a/docs/editor/Inno.Editor.Panel.Stats.md +++ b/docs/editor/Inno.Editor.Panel.Stats.md @@ -4,7 +4,7 @@ 该项目提供最小独立 Stats Panel,不拥有 Scene、Asset、Rendering 或 Interaction 业务。 -`FrameStatisticsSampler` 对瞬时 delta time/FPS 进行时间窗口平滑,Panel 展示稳定的人类可读值,而不是每帧直接显示抖动的倒数。采样仍保留当前 frame time,平均值仅影响诊断显示,不改变 `Time` 或引擎更新。 +`FrameStatisticsSampler` 对瞬时 delta time/FPS 进行时间窗口平滑,Panel 展示稳定的人类可读值,而不是每帧直接显示抖动的倒数。每 0.5 秒产生一次显示样本,最多保存最近 60 秒的 120 个 FPS/frame-time 样本;停止绘制、时间倒退或长期暂停后重置。趋势直接借用 Panel 的有界数值缓冲,不把普通统计字符串推断成数值。平均值仅影响诊断显示,不改变 `Time` 或引擎更新。 显示层保留 `—— 分组 ——` 的诊断结构,但每组正文使用带共享内边距的两列自适应表格:左侧是弱化 label,右侧是可换行 value。交替行直接复用 Hierarchy GameObject 的 `collectionRow/collectionRowAlternate`;分组标题通过 `ImGuiWidget.CollectionSectionHeader` 复用 Hierarchy Scene 的背景、Inspector 的线色与统一 border thickness。标题 padding 来自共享 `EditorStyleMetrics.sectionHeaderPadding`,Inspector fieldset legend 使用同一缩进,不再由两个 Panel 分别估算。`Time`、`Delta` 和 `FPS` 归入 `Frame Timing`,与 Rendering、Game View 等 feature 统计使用同一视觉层级;长 Pipeline ID、Contributor 名称和分辨率说明不会再横向撑出 Panel。 @@ -27,7 +27,7 @@ static void PublishPending( } ``` -同一帧重复发布相同 ID 时最后值替换之前值。交换保留一个 completed-frame handoff,使贡献者 +内容及排序不变时 `GetSnapshot()` 复用同一不可变快照;Stats 只在快照变化时重建分组。同一帧重复发布相同 ID 时最后值替换之前值。交换保留一个 completed-frame handoff,使贡献者 位于 Stats 前后任意 Panel draw order 都能被看到;停止发布后不会形成长期 stale 数据。 宿主通过 internal lifecycle bridge 调用 Panel 的 protected `OnAttach`、`OnDraw` 与 `OnDetach`;扩展和脚本不能手工驱动生命周期。当前 generation 的 Draw 失败只 quarantine Stats Panel,不会中断其他 Editor 内容绘制。 @@ -38,15 +38,15 @@ static void PublishPending( ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.PlayMode.md b/docs/editor/Inno.Editor.PlayMode.md index 1139189c6..067eb34dd 100644 --- a/docs/editor/Inno.Editor.PlayMode.md +++ b/docs/editor/Inno.Editor.PlayMode.md @@ -58,7 +58,7 @@ flowchart LR | `isPlaying` | 仅在 `Playing` 时为 `true`。 | | `lastFailure` | 最近一次编译门禁、切换或模拟失败;新 entry 请求会清空。 | | `activeSessionId` | 当前 Play 日志 Session;不在 Play 请求内时为 `none`。 | -| `stateChanged` | 状态实际变化后触发;单个订阅者异常被隔离。 | +| `ListenStateChanges(callback)` | 通过 Core Events 订阅状态变化;返回值由订阅者释放。通知全部观察者后聚合异常。 | | `EnterPlayMode()` | 从 `Editing` 或 `Failed` 接受 fresh 请求;返回值表示是否接受。 | | `ExitPlayMode()` | 取消 Compiling、停止 Preparing/Playing,或把 `Failed` 清回 `Editing`;其他状态返回 `false`。 | @@ -142,57 +142,15 @@ Command/Ctrl + `P` 与 icon 使用同一个 Action 和状态查询,因此不 - 编译门禁:[Inno.Editor.Scripting](Inno.Editor.Scripting.md) - 下一页:[Inno.Editor.Hosting](Inno.Editor.Hosting.md) -## 当前源码公开 API 清单 - -签名来自当前 SDK 求值与语义符号;按类型列出 public/protected 契约。工作流与生命周期见本页前文。 - -### `Inno.Editor.PlayMode.EditorPlayModeController` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Action? Inno.Editor.PlayMode.EditorPlayModeController.stateChanged`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L144) | Occurs after the controller commits a state transition. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.PlayMode.EditorPlayModeController.EditorPlayModeController(Inno.Runtime.EngineHost engineHost, Inno.Runtime.RuntimeSessionOptions runtimeOptions, Inno.Editor.Scene.IEditorScenePlayMode scenes, Inno.Editor.Scripting.IEditorScriptCompilation scripting, Inno.Editor.Interactions.IEditorHistoryIsolation history, Inno.Core.Logging.LogRouter logs, Inno.Editor.Audio.IEditorAudioHost? audio = null)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L84) | Creates a Play Mode controller around the runtime host, scene snapshot, scripting, and history boundaries.
`engineHost`:The application host that owns every isolated Play Mode runtime session.
`runtimeOptions`:The validated Play Mode storage and timing policy used for every accepted request.
`scenes`:The scene workspace that creates isolated runtime scene sessions.
`scripting`:The script compilation service that publishes generation readiness.
`history`:The editor history boundary isolated during simulation.
`logs`:The application log router that receives Play Mode lifecycle failures.
`audio`:Optional Editor audio host that creates an isolated backend generation for Play Mode. | 约束由所属类型与本页生命周期定义。 | `System.ArgumentNullException`:Thrown when any dependency is null. | -| [`public void Inno.Editor.PlayMode.EditorPlayModeController.AdvanceTransition()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L206) | Advances at most one Play Mode transition at an editor-controlled frame-safe point. | 约束由所属类型与本页生命周期定义。 | `System.ObjectDisposedException`:Thrown after the controller has been disposed. | -| [`public void Inno.Editor.PlayMode.EditorPlayModeController.Dispose()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L242) | Releases the isolated runtime session and history scope owned by this controller. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.EnterPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L155) | Requests a fresh script generation before preparing an isolated runtime session.
返回:true when the controller accepted a new request; otherwise, false. | 约束由所属类型与本页生命周期定义。 | `System.ObjectDisposedException`:Thrown after the controller has been disposed. | -| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.ExitPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L179) | Requests cancellation, runtime-session disposal, or dismissal of a failed transition.
返回:true when the current state accepted the request; otherwise, false. | 约束由所属类型与本页生命周期定义。 | `System.ObjectDisposedException`:Thrown after the controller has been disposed. | -| [`public void Inno.Editor.PlayMode.EditorPlayModeController.Tick(float deltaTime)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L230) | Advances the isolated runtime session by one complete frame.
`deltaTime`:The elapsed editor frame time in seconds. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Core.Logging.LogSessionId Inno.Editor.PlayMode.EditorPlayModeController.activeSessionId { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L139) | Gets the isolated runtime log session associated with the current request. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.isPlaying { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L117) | Gets whether isolated runtime scenes are actively simulating. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public string? Inno.Editor.PlayMode.EditorPlayModeController.lastFailure { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L134) | Gets the most recent transition or simulation failure. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public Inno.Editor.PlayMode.EditorPlayModeState Inno.Editor.PlayMode.EditorPlayModeController.state { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L112) | Gets the current Play Mode transition state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.PlayMode.EditorPlayModeController : Inno.Editor.PlayMode.IEditorPlayMode, Inno.Editor.Core.IEditorReloadGuard, System.IDisposable`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L28) | Implements the deterministic state machine for one editor Play Mode workflow. | The editor host advances transitions at frame-safe points. Editable scenes remain owned by the scene workspace while the controller owns only the isolated runtime session. | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.PlayMode.EditorPlayModeLoop` +## 状态观察生命周期 - - - - - +IEditorPlayMode.ListenStateChanges 返回 Core Events 的独立订阅 lease;Console 在 Start 注册并在 Dispose 释放。Controller 的私有 Core EventHub 在成功退休后释放,pending retirement 保留 owner。观察者异常仍在所有观察者得到通知后聚合报告,不吞掉错误。没有第二个 UI 监听器注册表或事件总线。 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.PlayMode.EditorPlayModeLoop.EditorPlayModeLoop()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L8) | C# 生成的声明;状态与值语义遵循所属类型契约。 | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public System.IDisposable Inno.Editor.PlayMode.EditorPlayModeLoop.EnterPresentationScope()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L29) | Binds editor presentation and interaction work to the isolated Play session while simulation is active, or returns an inert scope while the editor is not presenting runtime copies.
返回:A scope that must be disposed after the current editor presentation phase completes. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public void Inno.Editor.PlayMode.EditorPlayModeLoop.Quiesce()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L37) | Drains the active Play session before the product tears down editor modules, history or presentation. | 约束由所属类型与本页生命周期定义。 | `System.InvalidOperationException`:The session cannot retire within its deadline; the host is faulted and its dependencies must remain alive. | -| [`public void Inno.Editor.PlayMode.EditorPlayModeLoop.Tick(float deltaTime)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L20) | Advances the active simulation by one complete frame when Play Mode is running.
`deltaTime`:The elapsed frame time in seconds. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public sealed class Inno.Editor.PlayMode.EditorPlayModeLoop`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L8) | Bridges the editor host frame clock to the active Play Mode session. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | +## 当前源码公开 API 清单 -### `Inno.Editor.PlayMode.EditorPlayModeState` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -201,55 +159,86 @@ Command/Ctrl + `P` 与 icon 使用同一个 Action 和状态查询,因此不 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L16) | Play Mode is waiting for the requested script generation to become active. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Editing = 0`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L11) | Editable scene documents are loaded and game simulation is stopped. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Failed = 5`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L36) | The most recent transition failed and its diagnostic remains available for inspection. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Playing = 3`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L26) | Isolated runtime scenes are receiving the game update lifecycle. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Preparing = 2`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L21) | The active script generation is ready and isolated runtime state is being prepared. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState.Stopping = 4`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L31) | The isolated runtime session is stopping and releasing its owned state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public enum Inno.Editor.PlayMode.EditorPlayModeState : int`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L6) | Identifies the current relationship between editor documents and game simulation. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | - -### `Inno.Editor.PlayMode.IEditorPlayMode` - - + + + + + + + + + + + + + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`System.Action? Inno.Editor.PlayMode.IEditorPlayMode.stateChanged`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L35) | Occurs after P:Inno.Editor.PlayMode.IEditorPlayMode.state changes. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`bool Inno.Editor.PlayMode.IEditorPlayMode.EnterPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L43) | Requests entry after the active script generation becomes ready.
返回:true when a new entry request was accepted. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`bool Inno.Editor.PlayMode.IEditorPlayMode.ExitPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L51) | Requests disposal of the active runtime session or dismisses a failed transition.
返回:true when entry was cancelled or a new exit request was accepted. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Core.Logging.LogSessionId Inno.Editor.PlayMode.IEditorPlayMode.activeSessionId { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L30) | Gets the active isolated runtime log session, or P:Inno.Core.Logging.LogSessionId.none outside a Play Mode request. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`bool Inno.Editor.PlayMode.IEditorPlayMode.isPlaying { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L20) | Gets whether isolated runtime scenes are actively simulating. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`string? Inno.Editor.PlayMode.IEditorPlayMode.lastFailure { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L25) | Gets the most recent transition or simulation failure, or null when no failure is active. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`Inno.Editor.PlayMode.EditorPlayModeState Inno.Editor.PlayMode.IEditorPlayMode.state { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L15) | Gets the current Play Mode transition state. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | -| [`public interface Inno.Editor.PlayMode.IEditorPlayMode`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L10) | Controls the editor's isolated game-simulation session. | 约束由所属类型与本页生命周期定义。 | 具体失败按方法与类型契约处理。 | +| [`public enum Inno.Editor.PlayMode.EditorPlayModeState : int`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L6) | Identifies the current relationship between editor documents and game simulation. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Editing = 0`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L11) | Editable scene documents are loaded and game simulation is stopped. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L16) | Play Mode is waiting for the requested script generation to become active. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Preparing = 2`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L21) | The active script generation is ready and isolated runtime state is being prepared. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Playing = 3`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L26) | Isolated runtime scenes are receiving the game update lifecycle. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Stopping = 4`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L31) | The isolated runtime session is stopping and releasing its owned state. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState.Failed = 5`](../../src/composition/editor/features/Inno.Editor.PlayMode/EditorPlayModeState.cs#L36) | The most recent transition failed and its diagnostic remains available for inspection. | — | — | +| [`public interface Inno.Editor.PlayMode.IEditorPlayMode`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L10) | Controls the editor's isolated game-simulation session. | — | — | +| [`Inno.Editor.PlayMode.EditorPlayModeState Inno.Editor.PlayMode.IEditorPlayMode.state { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L15) | Gets the current Play Mode transition state. | — | — | +| [`bool Inno.Editor.PlayMode.IEditorPlayMode.isPlaying { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L20) | Gets whether isolated runtime scenes are actively simulating. | — | — | +| [`string? Inno.Editor.PlayMode.IEditorPlayMode.lastFailure { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L25) | Gets the most recent transition or simulation failure, or when no failure is active. | — | — | +| [`Inno.Core.Logging.LogSessionId Inno.Editor.PlayMode.IEditorPlayMode.activeSessionId { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L30) | Gets the active isolated runtime log session, or outside a Play Mode request. | — | — | +| [`System.IDisposable Inno.Editor.PlayMode.IEditorPlayMode.ListenStateChanges(System.Action listener)`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L44) | Subscribes to committed state transitions through the Core event lifetime.
`listener`: The observer invoked synchronously after a transition. | An independently disposable subscription; dispose it before retiring the observer. | `System.ArgumentNullException`: The listener is null. | +| [`bool Inno.Editor.PlayMode.IEditorPlayMode.EnterPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L52) | Requests entry after the active script generation becomes ready. | when a new entry request was accepted. | — | +| [`bool Inno.Editor.PlayMode.IEditorPlayMode.ExitPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs#L60) | Requests disposal of the active runtime session or dismisses a failed transition. | when entry was cancelled or a new exit request was accepted. | — | +| [`public sealed class Inno.Editor.PlayMode.EditorPlayModeController : Inno.Editor.PlayMode.IEditorPlayMode, Inno.Editor.Core.IEditorReloadGuard, System.IDisposable`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L29) | Implements the deterministic state machine for one editor Play Mode workflow. | — | — | +| [`public Inno.Editor.PlayMode.EditorPlayModeController.EditorPlayModeController(Inno.Runtime.EngineHost engineHost, Inno.Runtime.RuntimeSessionOptions runtimeOptions, Inno.Editor.Scene.IEditorScenePlayMode scenes, Inno.Editor.Scripting.IEditorScriptCompilation scripting, Inno.Editor.Interactions.IEditorHistoryIsolation history, Inno.Core.Logging.LogRouter logs, Inno.Editor.Audio.IEditorAudioHost? audio = null)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L87) | Creates a Play Mode controller around the runtime host, scene snapshot, scripting, and history boundaries.
`engineHost`: The application host that owns every isolated Play Mode runtime session.
`runtimeOptions`: The validated Play Mode storage and timing policy used for every accepted request.
`scenes`: The scene workspace that creates isolated runtime scene sessions.
`scripting`: The script compilation service that publishes generation readiness.
`history`: The editor history boundary isolated during simulation.
`logs`: The application log router that receives Play Mode lifecycle failures.
`audio`: Optional Editor audio host that creates an isolated backend generation for Play Mode. | — | `System.ArgumentNullException`: Thrown when any dependency is . | +| [`public Inno.Editor.PlayMode.EditorPlayModeState Inno.Editor.PlayMode.EditorPlayModeController.state { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L116) | Gets the current Play Mode transition state. | — | — | +| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.isPlaying { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L121) | Gets whether isolated runtime scenes are actively simulating. | — | — | +| [`public string? Inno.Editor.PlayMode.EditorPlayModeController.lastFailure { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L138) | Gets the most recent transition or simulation failure. | — | — | +| [`public Inno.Core.Logging.LogSessionId Inno.Editor.PlayMode.EditorPlayModeController.activeSessionId { get; }`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L143) | Gets the isolated runtime log session associated with the current request. | — | — | +| [`public System.IDisposable Inno.Editor.PlayMode.EditorPlayModeController.ListenStateChanges(System.Action listener)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L146) | Subscribes to committed state transitions through the Core event lifetime.
`listener`: The observer invoked synchronously after a transition. | An independently disposable subscription; dispose it before retiring the observer. | `System.ArgumentNullException`: The listener is null. | +| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.EnterPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L172) | Requests a fresh script generation before preparing an isolated runtime session. | when the controller accepted a new request; otherwise, . | `System.ObjectDisposedException`: Thrown after the controller has been disposed. | +| [`public bool Inno.Editor.PlayMode.EditorPlayModeController.ExitPlayMode()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L196) | Requests cancellation, runtime-session disposal, or dismissal of a failed transition. | when the current state accepted the request; otherwise, . | `System.ObjectDisposedException`: Thrown after the controller has been disposed. | +| [`public void Inno.Editor.PlayMode.EditorPlayModeController.AdvanceTransition()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L223) | Advances at most one Play Mode transition at an editor-controlled frame-safe point. | — | `System.ObjectDisposedException`: Thrown after the controller has been disposed. | +| [`public void Inno.Editor.PlayMode.EditorPlayModeController.Tick(float deltaTime)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L247) | Advances the isolated runtime session by one complete frame.
`deltaTime`: The elapsed editor frame time in seconds. | — | — | +| [`public void Inno.Editor.PlayMode.EditorPlayModeController.Dispose()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs#L259) | Releases the isolated runtime session and history scope owned by this controller. | — | — | +| [`public sealed class Inno.Editor.PlayMode.EditorPlayModeLoop`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L8) | Bridges the editor host frame clock to the active Play Mode session. | — | — | +| [`public void Inno.Editor.PlayMode.EditorPlayModeLoop.Tick(float deltaTime)`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L20) | Advances the active simulation by one complete frame when Play Mode is running.
`deltaTime`: The elapsed frame time in seconds. | — | — | +| [`public System.IDisposable Inno.Editor.PlayMode.EditorPlayModeLoop.EnterPresentationScope()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L29) | Binds editor presentation and interaction work to the isolated Play session while simulation is active, or returns an inert scope while the editor is not presenting runtime copies. | A scope that must be disposed after the current editor presentation phase completes. | — | +| [`public void Inno.Editor.PlayMode.EditorPlayModeLoop.Quiesce()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L37) | Drains the active Play session before the product tears down editor modules, history or presentation. | — | `System.InvalidOperationException`: The session cannot retire within its deadline; the host is faulted and its dependencies must remain alive. | +| [`public Inno.Editor.PlayMode.EditorPlayModeLoop.EditorPlayModeLoop()`](../../src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeLoop.cs#L8) | Inherited or compiler-provided contract. | — | — | ## 项目依赖 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.UI.Runtime](../ui/Inno.UI.Runtime.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Compiler](../scripting/Inno.Scripting.Compiler.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Input](../core/Inno.Core.Input.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene](../scene/Inno.Scene.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Audio](Inno.Editor.Audio.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Scene](Inno.Editor.Scene.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Scripting](Inno.Editor.Scripting.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Events` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.UI.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 实现依赖;不传递 compile surface | +| `Inno.Core.Input` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Scene` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Audio` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Runtime` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.Editor.Scene` | 公开签名依赖 | +| `Inno.Editor.Scripting` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Rendering.md b/docs/editor/Inno.Editor.Rendering.md index 436351a2d..34c8d2f32 100644 --- a/docs/editor/Inno.Editor.Rendering.md +++ b/docs/editor/Inno.Editor.Rendering.md @@ -23,8 +23,8 @@ 公开 API(同时导出到 `InnoEditor.Rendering`): - `Open(path)` / `Read(id)`:打开源并解码独立 Pipeline 草稿。 -- `Replace(id, candidate, finishGesture)` / `Commit(id)`:一手势一次共享 History,不保存、不改变 Scene/Game。 -- `ReplaceSettings(id, settings, finishGesture)`:自动捕获设置的完整依赖,写入草稿 pipelineState。 +- `Replace(id, candidate, mergeKey)`:每次修改立即进入共享 History;连续手势通过同一事务和 merge key 合并,不保存、不改变 Scene/Game。 +- `ReplaceSettings(id, settings, mergeKey)`:自动捕获设置的完整依赖,写入草稿 pipelineState。 Save/Revert 使用共享文档服务,保存与导入/激活状态分开呈现;源冲突拒绝覆盖。 当前 Inspector 支持既有设置类型的字段、Feature 开关和顺序。Pipeline 类型及新增 Feature 由配置创建 API/插件模板提供。 @@ -106,46 +106,82 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Rendering.EditorGizmoContext` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorGizmoContext.EditorGizmoContext(Inno.References.ContentReadScope content, Inno.Core.Identity.RuntimeIdentity? selected, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L29) | Creates a frame-scoped gizmo request.
`content`:The scenes presented by the Scene viewport.
`selected`:The selected live object, when available.
`pixelWidth`:The Scene viewport width.
`pixelHeight`:The Scene viewport height. | — | — | -| [`public Inno.References.ContentReadScope Inno.Editor.Rendering.EditorGizmoContext.content { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L44) | Gets the scene roots visible in this Editor viewport. | — | — | -| [`public int Inno.Editor.Rendering.EditorGizmoContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L56) | Gets the viewport height in physical pixels. | — | — | -| [`public int Inno.Editor.Rendering.EditorGizmoContext.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L52) | Gets the viewport width in physical pixels. | — | — | -| [`public Inno.Core.Identity.RuntimeIdentity? Inno.Editor.Rendering.EditorGizmoContext.selected { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L48) | Gets the transient selected identity. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorGizmoContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L12) | Provides scene content and selection for transient Editor gizmos. | — | — | - -### `Inno.Editor.Rendering.EditorGizmoFrame` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorGizmoFrame.EditorGizmoFrame()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L176) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Editor.Rendering.EditorGizmoFrame.Icon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L202) | Appends a selectable scene icon to the current gizmo frame.
`owner`:The object that owns the resulting lifetime and state.
`position`:The position consumed by icon; ownership remains with the caller unless explicitly stated otherwise.
`iconId`:The stable icon identity resolved by the Scene View. | — | — | -| [`public void Inno.Editor.Rendering.EditorGizmoFrame.Line(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L220) | Appends a world space line to the current gizmo frame.
`start`:The start consumed by line; ownership remains with the caller unless explicitly stated otherwise.
`end`:The end consumed by line; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorGizmoFrame.icons { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L184) | Gets the ordered selectable icons. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorGizmoFrame.lines { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L188) | Gets the ordered non-interactive lines. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorGizmoFrame : Inno.Editor.Rendering.IEditorGizmoSink`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L176) | Holds the transient gizmo primitives for one Scene viewport frame. | — | — | - -### `Inno.Editor.Rendering.EditorGizmoIcon` - + + + + + + + + + @@ -158,24 +194,6 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorGizmoIcon.EditorGizmoIcon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | One selectable Editor icon.
`owner`:The object that owns the resulting lifetime and state.
`position`:The vector3 value used to initialize this instance.
`iconId`:The stable visual identity resolved by the Scene View presentation. | — | — | -| [`public void Inno.Editor.Rendering.EditorGizmoIcon.Deconstruct(out Inno.Core.Identity.Identity owner, out Inno.Core.Mathematics.Vector3 position, out string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Rendering.EditorGizmoIcon.Equals(Inno.Editor.Rendering.EditorGizmoIcon other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorGizmoIcon.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorGizmoIcon.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorGizmoIcon.operator ==(Inno.Editor.Rendering.EditorGizmoIcon left, Inno.Editor.Rendering.EditorGizmoIcon right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorGizmoIcon.operator !=(Inno.Editor.Rendering.EditorGizmoIcon left, Inno.Editor.Rendering.EditorGizmoIcon right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorGizmoIcon.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public string Inno.Editor.Rendering.EditorGizmoIcon.iconId { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L156) | The stable visual identity resolved by the Scene View presentation. | — | — | -| [`public Inno.Core.Identity.Identity Inno.Editor.Rendering.EditorGizmoIcon.owner { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L154) | The object that owns the resulting lifetime and state. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoIcon.position { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L155) | The vector3 value used to initialize this instance. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorGizmoIcon : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | One selectable Editor icon.
`owner`:The object that owns the resulting lifetime and state.
`position`:The vector3 value used to initialize this instance.
`iconId`:The stable visual identity resolved by the Scene View presentation. | — | — | - -### `Inno.Editor.Rendering.EditorGizmoLine` - @@ -187,47 +205,12 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorGizmoLine.EditorGizmoLine(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | One non-interactive Editor line.
`start`:The vector3 value used to initialize this instance.
`end`:The vector3 value used to initialize this instance. | — | — | -| [`public void Inno.Editor.Rendering.EditorGizmoLine.Deconstruct(out Inno.Core.Mathematics.Vector3 start, out Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Rendering.EditorGizmoLine.Equals(Inno.Editor.Rendering.EditorGizmoLine other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorGizmoLine.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorGizmoLine.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorGizmoLine.operator ==(Inno.Editor.Rendering.EditorGizmoLine left, Inno.Editor.Rendering.EditorGizmoLine right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorGizmoLine.operator !=(Inno.Editor.Rendering.EditorGizmoLine left, Inno.Editor.Rendering.EditorGizmoLine right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorGizmoLine.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoLine.end { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L170) | The vector3 value used to initialize this instance. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoLine.start { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L169) | The vector3 value used to initialize this instance. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorGizmoLine : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | One non-interactive Editor line.
`start`:The vector3 value used to initialize this instance.
`end`:The vector3 value used to initialize this instance. | — | — | - -### `Inno.Editor.Rendering.EditorGizmoProvider` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Rendering.EditorGizmoProvider.EditorGizmoProvider()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L100) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract void Inno.Editor.Rendering.EditorGizmoProvider.Collect(Inno.Editor.Rendering.EditorGizmoContext context, Inno.Editor.Rendering.IEditorGizmoSink sink)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L111) | Submits transient icons and lines for the presented scenes.
`context`:The current Scene viewport request.
`sink`:The collector that owns submitted primitives for this frame. | — | — | -| [`public abstract class Inno.Editor.Rendering.EditorGizmoProvider`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L100) | Contributes Editor-only icons and bounds without modifying a rendering model. | — | — | - -### `Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute.EditorGizmoProviderExtensionAttribute(string id)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L129) | Creates a provider declaration with a stable identifier.
`id`:The globally unique provider identifier. | — | — | -| [`public string Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L138) | Gets the stable provider identifier. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L121) | Marks one reloadable Editor gizmo provider. | — | — | - -### `Inno.Editor.Rendering.EditorPreviewHandle` - + + + + + + @@ -241,25 +224,15 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorPreviewHandle.EditorPreviewHandle(ulong value, uint deviceGeneration, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L28) | Creates one generation-scoped preview handle.
`value`:Non-zero preview identity.
`deviceGeneration`:Non-zero rendering-device generation.
`pixelWidth`:Positive source width.
`pixelHeight`:Positive source height. | — | — | -| [`public bool Inno.Editor.Rendering.EditorPreviewHandle.Equals(Inno.Editor.Rendering.EditorPreviewHandle other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorPreviewHandle.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorPreviewHandle.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorPreviewHandle.operator ==(Inno.Editor.Rendering.EditorPreviewHandle left, Inno.Editor.Rendering.EditorPreviewHandle right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorPreviewHandle.operator !=(Inno.Editor.Rendering.EditorPreviewHandle left, Inno.Editor.Rendering.EditorPreviewHandle right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorPreviewHandle.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public uint Inno.Editor.Rendering.EditorPreviewHandle.deviceGeneration { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L52) | Gets the rendering-device generation that owns this handle. | — | — | -| [`public bool Inno.Editor.Rendering.EditorPreviewHandle.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L67) | Gets whether this handle identifies a usable generation-scoped preview. | — | — | -| [`public int Inno.Editor.Rendering.EditorPreviewHandle.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L62) | Gets source height in pixels. | — | — | -| [`public int Inno.Editor.Rendering.EditorPreviewHandle.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L57) | Gets source width in pixels. | — | — | -| [`public ulong Inno.Editor.Rendering.EditorPreviewHandle.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L47) | Gets the opaque preview identity. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorPreviewHandle : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Identifies one preview texture owned by a specific rendering-device generation. | — | — | - -### `Inno.Editor.Rendering.EditorRenderingModule` - + + + + + + + + + @@ -281,199 +254,22 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorRenderingModule.EditorRenderingModule(Inno.Editor.Rendering.IEditorRenderingHost host, Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L56) | Creates the composition host around stable rendering and interaction services.
`host`:Host-owned target and opaque texture bridge.
`interactions`:Shared Editor interaction and selection service.
`types`:The type catalog that owns viewport contributor generations. | — | — | -| [`public Inno.Editor.Rendering.EditorGizmoFrame Inno.Editor.Rendering.EditorRenderingModule.CollectGizmos(string viewportId, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L84) | Collects Editor-only icons and bounds for the current Scene viewport.
返回:Transient gizmo primitives for the submitted frame.
`viewportId`:The Scene viewport identity.
`pixelWidth`:The viewport width.
`pixelHeight`:The viewport height. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.Draw(Inno.Editor.Rendering.EditorViewportOutput output, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L558) | Draws a ready output in the current panel.
`output`:Opaque output returned by Inno.Editor.Rendering.EditorRenderingModule.TrySubmit(Inno.Editor.Rendering.EditorViewportKindId,System.String,System.Int32,System.Int32,Inno.Editor.Rendering.EditorViewportOutput@).
`logicalSize`:Destination size in logical UI pixels. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.DrawControllerToolbar(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L319) | Draws toolbar controls owned by the selected model controller.
`kind`:Open viewport purpose.
`viewportId`:Stable panel viewport identity.
`pixelWidth`:Current target width.
`pixelHeight`:Current target height. | — | — | -| [`public string? Inno.Editor.Rendering.EditorRenderingModule.GetCompositionError(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L158) | Gets the most recent isolated contribution or composition failure for one stable viewport.
返回:The failure message, or null when no current failure exists.
`viewportId`:Stable panel viewport identity. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationState Inno.Editor.Rendering.EditorRenderingModule.GetNavigationState(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L174) | Gets the host-owned neutral navigation state for one stable Editor viewport.
返回:The reusable navigation state owned by the Editor host.
`viewportId`:Stable panel viewport identity. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.HandlePointer(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, float x, float y, int button)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L518) | Forwards a normalized click to the selected model controller.
`kind`:Open viewport purpose.
`viewportId`:Stable panel viewport identity.
`pixelWidth`:Current target width.
`pixelHeight`:Current target height.
`x`:Normalized horizontal position.
`y`:Normalized vertical position.
`button`:Platform-independent pointer button index. | — | — | -| [`public bool Inno.Editor.Rendering.EditorRenderingModule.HasContributors(Inno.Editor.Rendering.EditorViewportKindId kind)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L110) | Gets whether the current extension generation contributes to one viewport purpose.
返回:true when at least one contributor is registered.
`kind`:Open viewport purpose. | — | — | -| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnDispose()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L672) | Releases resources retained by this feature after it has stopped. | — | — | -| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L587) | Initializes this feature when its owning runtime becomes active.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L654) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L599) | Advances this feature using the current runtime state.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.Release(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L569) | Stops retaining one viewport target.
`viewportId`:Stable viewport identity. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetContentScope(string viewportId, Inno.References.ContentReadScope content)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L195) | Sets the explicit ordered host content visible to one Editor viewport.
`viewportId`:Stable panel viewport identity.
`content`:Current frame-safe content scope. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetOutputInput(string viewportId, Inno.Rendering.Runtime.RenderOutputInput input)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L121) | Supplies viewport-local input captured by the output panel for this frame.
`viewportId`:Stable output identity.
`input`:Physical-pixel input snapshot. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetOutputRoute(string viewportId, Inno.Rendering.Runtime.RenderOutputRoute? route)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L138) | Sets the exact model contributor order for an output with several applicable models.
`viewportId`:Stable output identity.
`route`:Explicit contributor route, or null for single-model selection. | — | — | -| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetPresentation(string viewportId, Inno.Editor.Rendering.EditorViewportPresentation presentation)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L275) | Sets presentation preferences supplied to every contributor for one Editor viewport.
`viewportId`:Stable panel viewport identity.
`presentation`:Current host-owned presentation preferences. | — | — | -| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TryConfigureNavigation(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorViewportNavigationProfile profile)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L227) | Queries the selected controller's navigation contract before viewport input is processed.
返回:True when a contributor returned a usable navigation profile and became the controller.
`kind`:Open viewport purpose.
`viewportId`:Stable panel viewport identity.
`pixelWidth`:Positive target width.
`pixelHeight`:Positive target height.
`profile`:Receives the selected controller profile. | — | — | -| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TryGetManipulationSpace(string viewportId, out Inno.Editor.Rendering.EditorViewportManipulationSpace space)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L296) | Tries to get the manipulation space from the selected controller's latest contribution.
返回:true when the selected controller supplied a manipulation space.
`viewportId`:Stable panel viewport identity.
`space`:Receives the latest exact view/projection contract. | — | — | -| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TrySubmit(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorViewportOutput output)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L368) | Builds, composes, submits, and returns one host-owned offscreen viewport.
返回:true when at least one model contribution was accepted.
`kind`:Open viewport purpose.
`viewportId`:Stable panel viewport identity.
`pixelWidth`:Positive target width.
`pixelHeight`:Positive target height.
`output`:Receives the opaque viewport output. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorRenderingModule : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L22) | Composes reloadable rendering-model contributors while retaining only opaque presentation outputs. | — | — | - -### `Inno.Editor.Rendering.EditorRenderTargetArtifactProvider` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.EditorRenderTargetArtifactProvider(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Types.TypeCatalog types, Inno.Rendering.Assets.Authoring.ShaderCompiler shaderCompiler, Inno.Rendering.Assets.Authoring.ITextureTargetCompiler textureCompiler, Inno.Core.Diagnostics.IDiagnosticReporter diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L63) | Creates an Editor artifact provider backed by explicit shader and texture toolchains.
`shaderCompiler`:The compiler that turns backend-neutral shader IR into device target programs.
`assets`:The authoring asset pipeline that owns source mounts.
`serialization`:The serialization registry that owns active Shader contracts.
`textureCompiler`:The compiler that turns artist texture sources into portable KTX artifacts.
`diagnostics`:The sink that receives compilation and last-good fallback diagnostics.
`types`:The shared authoring type generation owner for graph and source extensions. | — | `System.ArgumentNullException`:Thrown when any required service is null. | -| [`public void Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.Dispose()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L240) | Cancels pending toolchain work and releases every cached authoring artifact. | — | — | -| [`public Inno.Rendering.Runtime.RenderTargetArtifactStatus Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.GetShaderArtifact(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant, Inno.Rendering.GraphicsCapabilities capabilities, out Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L104) | Returns a matching compiled shader when available and schedules a new candidate when source state changed.
返回:Inno.Rendering.Runtime.RenderTargetArtifactStatus.Ready when a current or last-good artifact is usable; otherwise, the exact pending or failed state of the first candidate.
`shader`:The imported backend-neutral shader asset.
`variant`:The exact material keyword selection.
`capabilities`:The active device capability snapshot used to select the target compiler profile.
`artifact`:Receives the current candidate or the last-good artifact while a replacement is compiling. | — | `System.ObjectDisposedException`:Thrown when this provider has been disposed.
`System.ArgumentNullException`:Thrown when shader or capabilities is null. | -| [`public Inno.Rendering.Runtime.RenderTargetArtifactStatus Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.GetTextureArtifact(Inno.Rendering.Assets.RenderTextureArtifactReference texture, out System.ReadOnlyMemory artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L212) | Returns a matching compiled texture when available and schedules a replacement when source state changed.
返回:Inno.Rendering.Runtime.RenderTargetArtifactStatus.Ready when a current or last-good KTX artifact is usable; otherwise, the exact pending or failed state of the first candidate.
`texture`:The imported texture asset whose authoring source is compiled.
`artifact`:Receives the current candidate or the last-good KTX artifact while a replacement is compiling. | — | `System.ObjectDisposedException`:Thrown when this provider has been disposed.
`System.ArgumentNullException`:Thrown when texture is null. | -| [`public Inno.Rendering.Assets.ShaderDefinition Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.ReadShaderDefinition(Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L154) | Reads and validates the shader definition value from its authoritative source.
返回:The validated shader definition that represents the completed operation.
`artifact`:The resolved immutable artifact payload returned to the caller. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.RequestShaderCompilation(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant, Inno.Rendering.GraphicsCapabilities capabilities)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L177) | Schedules compilation if required and reports saving-independent state for the exact shader, variant and device target.
返回:A detached status and diagnostic snapshot; last-good is explicit and never implies current-source success.
`shader`:Current imported shader.
`variant`:Selected keyword variant.
`capabilities`:Current device capabilities. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorRenderTargetArtifactProvider : Inno.Rendering.Runtime.IRenderTargetArtifactProvider, System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L23) | Produces target-specific render artifacts from imported authoring assets without exposing source access to the backend-neutral render runtime. | — | — | - -### `Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.EditorShaderArtifactValidationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Contains active-device validation state for one immutable Editor shader candidate.
`state`:Pending, successful, or failed validation state.
`diagnostics`:Detached device validation diagnostics. | — | — | -| [`public void Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Determines whether the specified object is equal to the current object.
返回:true if the specified object is equal to the current object; otherwise, false.
`obj`:The object to compare with the current object. | — | — | -| [`public override int Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Serves as the default hash function.
返回:A hash code for the current object. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? left, Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? left, Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Returns a string that represents the current object.
返回:A string that represents the current object. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L19) | Detached device validation diagnostics. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L18) | Pending, successful, or failed validation state. | — | — | -| [`public sealed record Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Contains active-device validation state for one immutable Editor shader candidate.
`state`:Pending, successful, or failed validation state.
`diagnostics`:Detached device validation diagnostics. | — | — | - -### `Inno.Editor.Rendering.EditorShaderCompilation` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorShaderCompilation.EditorShaderCompilation(Inno.Editor.Rendering.EditorRenderTargetArtifactProvider provider, Inno.Editor.Rendering.IEditorShaderArtifactValidator validator)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L103) | Pairs the authoring artifact owner with active-device validation at frame safety points.
`provider`:Host-owned shader compilation service.
`validator`:Host-owned active-device artifact validator. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilation.EditorShaderCompilation(Inno.Editor.Rendering.EditorRenderTargetArtifactProvider provider, Inno.Rendering.GraphicsCapabilities capabilities)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L86) | Pairs the authoring artifact owner with the active device's immutable capability snapshot.
`provider`:Host-owned shader compilation service.
`capabilities`:Current device capability snapshot. | — | — | -| [`public Inno.Rendering.Assets.ShaderDefinition Inno.Editor.Rendering.EditorShaderCompilation.ReadDefinition(Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L159) | Decodes the exact contract captured with a candidate using the current owner's references.
返回:A detached current-generation definition.
`artifact`:Complete candidate, not the latest uncompiled asset definition. | — | — | -| [`public void Inno.Editor.Rendering.EditorShaderCompilation.ReleaseDraft(System.Guid documentId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L216) | Cancels a document's preview work and retires its cached candidate without touching canonical artifacts.
`documentId`:Closing preview owner identity. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.Request(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L123) | Requests current compilation and reads its explicit last-good status.
返回:A detached status snapshot.
`shader`:Current generation shader asset, not retained by the service.
`variant`:Exact static keyword selection. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.RequestArtifact(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L141) | Reads the canonical Shader candidate for an isolated Material preview without modifying Material values.
返回:Compilation status and immutable last-good artifact when available.
`shader`:Current owner Shader reference.
`variant`:Exact Material keyword selection. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.RequestDraft(System.Guid documentId, Inno.Core.Graphs.GraphDocument graph, ulong revision, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L179) | Compiles a detached document through the same graph toolchain in an isolated preview cache.
返回:Preview-only state and artifact; calling this method cannot publish to Scene/Game.
`documentId`:Preview owner identity, independent of canonical artifact lookup.
`graph`:Current draft, never modified or retained.
`revision`:Monotonic draft revision, including Undo/Redo.
`variant`:Exact static keyword selection. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorShaderCompilation`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L72) | Schedules Editor shader compilation for the active device without exposing native handles to authoring panels. | — | — | - -### `Inno.Editor.Rendering.EditorShaderCompilationSnapshot` - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot.EditorShaderCompilationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, bool usingLastGood, System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Contains detached compiler state without retaining assets, tasks or extension instances.
`state`:Latest requested semantic candidate state.
`usingLastGood`:Whether rendering still uses a previous successful candidate.
`diagnostics`:Immutable latest completed compiler diagnostics. | — | — | -| [`public void Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out bool usingLastGood, out System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Determines whether the specified object is equal to the current object.
返回:true if the specified object is equal to the current object; otherwise, false.
`obj`:The object to compare with the current object. | — | — | -| [`public override int Inno.Editor.Rendering.EditorShaderCompilationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Serves as the default hash function.
返回:A hash code for the current object. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorShaderCompilationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Returns a string that represents the current object.
返回:A string that represents the current object. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderCompilationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L44) | Immutable latest completed compiler diagnostics. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderCompilationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L42) | Latest requested semantic candidate state. | — | — | -| [`public bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.usingLastGood { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L43) | Whether rendering still uses a previous successful candidate. | — | — | -| [`public sealed record Inno.Editor.Rendering.EditorShaderCompilationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Contains detached compiler state without retaining assets, tasks or extension instances.
`state`:Latest requested semantic candidate state.
`usingLastGood`:Whether rendering still uses a previous successful candidate.
`diagnostics`:Immutable latest completed compiler diagnostics. | — | — | - -### `Inno.Editor.Rendering.EditorShaderCompilationState` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Rendering.EditorShaderCompilationState.Compiling = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L18) | The latest semantic candidate is being compiled. | — | — | -| [`Inno.Editor.Rendering.EditorShaderCompilationState.Failed = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L26) | The latest semantic candidate failed. | — | — | -| [`Inno.Editor.Rendering.EditorShaderCompilationState.Succeeded = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L22) | The latest semantic candidate compiled successfully. | — | — | -| [`public enum Inno.Editor.Rendering.EditorShaderCompilationState : int`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L13) | Identifies native compilation state independently of document persistence. | — | — | - -### `Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot` - - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.EditorShaderDraftCompilationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, bool usingLastGood, System.Collections.Generic.IReadOnlyList diagnostics, Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Contains a preview-only immutable candidate, never registered as a canonical asset artifact.
`state`:Latest draft compilation state.
`usingLastGood`:Whether the preview artifact belongs to an earlier successful draft.
`diagnostics`:Latest detached preview diagnostics, separate from project diagnostics.
`artifact`:Preview-only complete shader candidate, or null before the first success. | — | — | -| [`public void Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out bool usingLastGood, out System.Collections.Generic.IReadOnlyList diagnostics, out Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Determines whether the specified object is equal to the current object.
返回:true if the specified object is equal to the current object; otherwise, false.
`obj`:The object to compare with the current object. | — | — | -| [`public override int Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Serves as the default hash function.
返回:A hash code for the current object. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Returns a string that represents the current object.
返回:A string that represents the current object. | — | — | -| [`public Inno.Rendering.Assets.RenderShaderArtifact? Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.artifact { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L66) | Preview-only complete shader candidate, or null before the first success. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L65) | Latest detached preview diagnostics, separate from project diagnostics. | — | — | -| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L63) | Latest draft compilation state. | — | — | -| [`public bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.usingLastGood { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L64) | Whether the preview artifact belongs to an earlier successful draft. | — | — | -| [`public sealed record Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Contains a preview-only immutable candidate, never registered as a canonical asset artifact.
`state`:Latest draft compilation state.
`usingLastGood`:Whether the preview artifact belongs to an earlier successful draft.
`diagnostics`:Latest detached preview diagnostics, separate from project diagnostics.
`artifact`:Preview-only complete shader candidate, or null before the first success. | — | — | - -### `Inno.Editor.Rendering.EditorViewportComposition` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportComposition.EditorViewportComposition(string viewportId, int pixelWidth, int pixelHeight, Inno.Rendering.RenderTextureFormat targetFormat, System.Collections.Generic.IEnumerable layers)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L207) | Creates an immutable viewport composition.
`viewportId`:Stable panel viewport identity.
`pixelWidth`:Positive target width.
`pixelHeight`:Positive target height.
`targetFormat`:Shared presentation target format required by every layer.
`layers`:Ordered non-empty rendering-model layer collection. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorViewportComposition.layers { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L261) | Gets the ordered rendering-model layers. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportComposition.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L251) | Gets the target height. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportComposition.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L246) | Gets the target width. | — | — | -| [`public Inno.Rendering.RenderTextureFormat Inno.Editor.Rendering.EditorViewportComposition.targetFormat { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L256) | Gets the shared presentation target format. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportComposition.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L241) | Gets the stable viewport identity. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportComposition`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L185) | Describes a complete ordered set of rendering-model layers targeting one Editor viewport. | — | — | - -### `Inno.Editor.Rendering.EditorViewportContext` - + + + + + + + + + + + + + + + + @@ -487,167 +283,21 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.References.ContentReadScope Inno.Editor.Rendering.EditorViewportContext.content { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L184) | Gets the explicit ordered host content visible to this viewport. | — | — | -| [`public Inno.Editor.Core.EditorContext Inno.Editor.Rendering.EditorViewportContext.editor { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L149) | Gets the current Editor context. | — | — | -| [`public ulong Inno.Editor.Rendering.EditorViewportContext.frameIndex { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L199) | Gets the frame index shared by all Editor views in this output frame. | — | — | -| [`public Inno.Rendering.Runtime.RenderOutputInput Inno.Editor.Rendering.EditorViewportContext.input { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L203) | Gets input located in this viewport's physical pixels. | — | — | -| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Rendering.EditorViewportContext.interactions { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L154) | Gets the shared Editor interaction and selection service. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportKindId Inno.Editor.Rendering.EditorViewportContext.kind { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L159) | Gets the open viewport purpose. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationState Inno.Editor.Rendering.EditorViewportContext.navigation { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L179) | Gets host-owned neutral navigation state that the selected controller can map to its camera model. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L174) | Gets target height in physical pixels. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportContext.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L169) | Gets target width in physical pixels. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportPresentation Inno.Editor.Rendering.EditorViewportContext.presentation { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L189) | Gets host-selected presentation preferences for this viewport. | — | — | -| [`public Inno.Rendering.Runtime.IViewContentCollector Inno.Editor.Rendering.EditorViewportContext.viewContent { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L194) | Gets the active generation's model-independent world-content collector. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportContext.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L164) | Gets the stable panel viewport identity. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L108) | Supplies frame-only Editor interaction and output dimensions to a contributor. | — | — | - -### `Inno.Editor.Rendering.EditorViewportContribution` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportContribution.EditorViewportContribution(Inno.Rendering.Runtime.RenderFrameData data, Inno.Rendering.Assets.RenderPipelineAsset? pipeline = null, Inno.Rendering.RenderTextureFormat targetFormat = Inno.Rendering.RenderTextureFormat.RGBA8Srgb, Inno.Editor.Rendering.EditorViewportManipulationSpace? manipulationSpace = null)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L346) | Creates a viewport model contribution.
`data`:Pipeline-defined frame-only data.
`pipeline`:Contributor-selected pipeline, or null for the project default.
`targetFormat`:Presentation target format expected by the pipeline.
`manipulationSpace`:Optional exact view/projection contract for host-owned transform manipulation tools. | — | — | -| [`public Inno.Rendering.Runtime.RenderFrameData Inno.Editor.Rendering.EditorViewportContribution.data { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L361) | Gets pipeline-defined frame-only data. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportManipulationSpace? Inno.Editor.Rendering.EditorViewportContribution.manipulationSpace { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L376) | Gets the optional exact view/projection contract used by host-owned transform manipulation tools. | — | — | -| [`public Inno.Rendering.Assets.RenderPipelineAsset? Inno.Editor.Rendering.EditorViewportContribution.pipeline { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L366) | Gets the selected pipeline, or null for the project default. | — | — | -| [`public Inno.Rendering.RenderTextureFormat Inno.Editor.Rendering.EditorViewportContribution.targetFormat { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L371) | Gets the presentation target format expected by the pipeline. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportContribution`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L329) | Returns one rendering-model contribution without exposing a GPU backend. | — | — | - -### `Inno.Editor.Rendering.EditorViewportContributor` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Rendering.EditorViewportContributor.EditorViewportContributor()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L425) | Creates a parameterless reloadable viewport contributor. | — | — | -| [`public abstract Inno.Editor.Rendering.EditorViewportContribution Inno.Editor.Rendering.EditorViewportContributor.Build(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L461) | Builds one model-neutral render contribution for the current frame.
返回:Contributor-selected pipeline and frame data.
`context`:Frame-only Editor and viewport context. | — | — | -| [`public abstract bool Inno.Editor.Rendering.EditorViewportContributor.CanContribute(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L438) | Determines whether this rendering model participates in the supplied viewport content.
返回:true when this contributor can build a valid model contribution.
`context`:Frame-only Editor, navigation, content, and viewport context. | — | — | -| [`public virtual Inno.Editor.Rendering.EditorViewportNavigationProfile Inno.Editor.Rendering.EditorViewportContributor.ConfigureNavigation(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L449) | Configures neutral host navigation before input is processed and a request is built.
返回:The controller's current navigation capabilities and optional selection focus bound.
`context`:Frame-only Editor, navigation, content, and viewport context. | — | — | -| [`public virtual void Inno.Editor.Rendering.EditorViewportContributor.DrawToolbar(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L469) | Draws optional controller-specific toolbar controls when this contributor owns viewport interaction.
`context`:Frame-only Editor and viewport context. | — | — | -| [`public virtual void Inno.Editor.Rendering.EditorViewportContributor.HandlePointer(Inno.Editor.Rendering.EditorViewportPointerContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L479) | Handles one pointer click when this contributor owns viewport interaction.
`context`:Normalized frame-only pointer context. | — | — | -| [`public abstract class Inno.Editor.Rendering.EditorViewportContributor`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L420) | Contributes one rendering model while the host owns viewport composition, targets, and presentation. | — | — | - -### `Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.EditorViewportContributorExtensionAttribute(string id, string kind, int order = 0, int controllerPriority = 0)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L70) | Creates a viewport contributor declaration.
`id`:Globally stable contributor identity.
`kind`:Open viewport purpose handled by the contributor.
`order`:Ascending model-composition order within the viewport.
`controllerPriority`:Priority used to select the contributor that owns navigation, tools, and pointer interaction. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.controllerPriority { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L102) | Gets the priority used to select the viewport interaction controller. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L87) | Gets the globally stable contributor identity. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportKindId Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.kind { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L92) | Gets the open viewport purpose handled by the contributor. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.order { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L97) | Gets ascending model-composition order. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L53) | Marks a reloadable rendering-model contributor for one Editor viewport purpose. | — | — | - -### `Inno.Editor.Rendering.EditorViewportFocusBounds` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportFocusBounds.EditorViewportFocusBounds(Inno.Core.Mathematics.Vector3 center, float radius)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L142) | Creates a finite focus bound.
`center`:World-space focus center.
`radius`:Non-negative world-space radius. | — | `System.ArgumentException`:Thrown when the center contains a non-finite value.
`System.ArgumentOutOfRangeException`:Thrown when the radius is negative or non-finite. | -| [`public bool Inno.Editor.Rendering.EditorViewportFocusBounds.Equals(Inno.Editor.Rendering.EditorViewportFocusBounds other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportFocusBounds.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportFocusBounds.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportFocusBounds.operator ==(Inno.Editor.Rendering.EditorViewportFocusBounds left, Inno.Editor.Rendering.EditorViewportFocusBounds right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportFocusBounds.operator !=(Inno.Editor.Rendering.EditorViewportFocusBounds left, Inno.Editor.Rendering.EditorViewportFocusBounds right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportFocusBounds.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportFocusBounds.center { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L157) | Gets the world-space focus center. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportFocusBounds.radius { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L162) | Gets the non-negative world-space radius. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportFocusBounds : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Describes a world-space sphere that can be framed by host navigation. | — | — | - -### `Inno.Editor.Rendering.EditorViewportKindId` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportKindId.EditorViewportKindId(string value)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L24) | Creates a stable viewport purpose identifier.
`value`:Globally stable viewport purpose. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportKindId.Equals(Inno.Editor.Rendering.EditorViewportKindId other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportKindId.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportKindId.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportKindId.operator ==(Inno.Editor.Rendering.EditorViewportKindId left, Inno.Editor.Rendering.EditorViewportKindId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportKindId.operator !=(Inno.Editor.Rendering.EditorViewportKindId left, Inno.Editor.Rendering.EditorViewportKindId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportKindId.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L46) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportKindId.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L38) | Gets whether this identifier is usable. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportKindId.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L33) | Gets the stable viewport purpose. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportKindId : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Identifies one open Editor viewport purpose without prescribing rendering semantics. | — | — | - -### `Inno.Editor.Rendering.EditorViewportLayer` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportLayer.EditorViewportLayer(string contributorId, Inno.Rendering.Assets.RenderPipelineAsset? pipeline, Inno.Rendering.Runtime.RenderFrameData data, int order)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L147) | Creates one immutable model-neutral viewport layer.
`contributorId`:Stable identity of the rendering-model contributor that produced the layer.
`pipeline`:Contributor-selected pipeline, or null for the project default.
`data`:Contributor-defined frame-only data.
`order`:Ascending model-composition order. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportLayer.contributorId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L164) | Gets the stable identity of the rendering-model contributor. | — | — | -| [`public Inno.Rendering.Runtime.RenderFrameData Inno.Editor.Rendering.EditorViewportLayer.data { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L174) | Gets contributor-defined frame-only data. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportLayer.order { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L179) | Gets ascending model-composition order. | — | — | -| [`public Inno.Rendering.Assets.RenderPipelineAsset? Inno.Editor.Rendering.EditorViewportLayer.pipeline { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L169) | Gets the contributor-selected pipeline, or null for the project default. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportLayer`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L130) | Describes one model-neutral layer in an Editor viewport composition. | — | — | - -### `Inno.Editor.Rendering.EditorViewportManipulationPlane` - + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.Spatial = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L249) | Keeps the full three-dimensional translation, rotation, and scale controls. | — | — | -| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.XY = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L254) | Uses the X/Y translation and scale axes with the Z rotation handle. | — | — | -| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.XZ = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L259) | Uses the X/Z translation and scale axes with the Y rotation handle. | — | — | -| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.YZ = 3`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L264) | Uses the Y/Z translation and scale axes with the X rotation handle. | — | — | -| [`public enum Inno.Editor.Rendering.EditorViewportManipulationPlane : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L244) | Selects whether transform handles use all axes or a contributor-declared planar orientation. | — | — | - -### `Inno.Editor.Rendering.EditorViewportManipulationSpace` - @@ -660,24 +310,31 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportManipulationSpace.EditorViewportManipulationSpace(Inno.Core.Mathematics.Matrix viewMatrix, Inno.Core.Mathematics.Matrix projectionMatrix, bool isOrthographic, Inno.Editor.Rendering.EditorViewportManipulationPlane plane = Inno.Editor.Rendering.EditorViewportManipulationPlane.Spatial)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L291) | Creates a manipulation space matching one rendered viewport frame.
`viewMatrix`:World-to-view matrix used by the submitted frame.
`projectionMatrix`:View-to-clip matrix used by the submitted frame.
`isOrthographic`:Whether the projection is orthographic; this does not imply planar manipulation.
`plane`:The contributor-selected transform handle orientation, independent of projection. | — | `System.ArgumentOutOfRangeException`:The plane is not one of the defined neutral manipulation orientations. | -| [`public bool Inno.Editor.Rendering.EditorViewportManipulationSpace.Equals(Inno.Editor.Rendering.EditorViewportManipulationSpace other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportManipulationSpace.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportManipulationSpace.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportManipulationSpace.operator ==(Inno.Editor.Rendering.EditorViewportManipulationSpace left, Inno.Editor.Rendering.EditorViewportManipulationSpace right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportManipulationSpace.operator !=(Inno.Editor.Rendering.EditorViewportManipulationSpace left, Inno.Editor.Rendering.EditorViewportManipulationSpace right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportManipulationSpace.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportManipulationSpace.isOrthographic { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L318) | Gets whether the submitted frame used an orthographic projection. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportManipulationPlane Inno.Editor.Rendering.EditorViewportManipulationSpace.plane { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L323) | Gets the contributor-declared neutral manipulation plane, or Spatial for all axes. | — | — | -| [`public Inno.Core.Mathematics.Matrix Inno.Editor.Rendering.EditorViewportManipulationSpace.projectionMatrix { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L313) | Gets the view-to-clip matrix used by the submitted frame. | — | — | -| [`public Inno.Core.Mathematics.Matrix Inno.Editor.Rendering.EditorViewportManipulationSpace.viewMatrix { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L308) | Gets the world-to-view matrix used by the submitted frame. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportManipulationSpace : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Describes the exact backend-neutral view and projection used to draw a viewport so host tools can manipulate selected scene transforms without knowing the controller's camera model. | — | — | - -### `Inno.Editor.Rendering.EditorViewportNavigationCapabilities` - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -686,34 +343,26 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Fly = 8`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L78) | Allows free-look movement. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.FrameSelection = 16`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L83) | Allows framing a provider-supplied or host-derived selection bound. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.None = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L53) | Disables host-owned navigation. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Orbit = 4`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L73) | Allows pivot-oriented orbit and dolly. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Pan = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L58) | Allows translating the view parallel to its image plane. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Planar = Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Pan | Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Zoom`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L68) | Allows the complete plane-oriented pan and zoom interaction model. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Zoom = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L63) | Allows changing orthographic size or perspective focus distance. | — | — | -| [`public enum Inno.Editor.Rendering.EditorViewportNavigationCapabilities : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L48) | Declares navigation operations supported by one viewport interaction controller. | — | — | - -### `Inno.Editor.Rendering.EditorViewportNavigationMode` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Fly = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L41) | Moves and looks freely from the current view position. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Orbit = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L36) | Rotates around a focus pivot and dollies along the view direction. | — | — | -| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Planar = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L31) | Pans and zooms over a provider-defined plane. | — | — | -| [`public enum Inno.Editor.Rendering.EditorViewportNavigationMode : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L26) | Identifies the active interaction model without prescribing a rendering camera type. | — | — | - -### `Inno.Editor.Rendering.EditorViewportNavigationProfile` - + + + + + + + + + + + + + + + + + + + + @@ -730,54 +379,6 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportNavigationProfile.EditorViewportNavigationProfile(Inno.Editor.Rendering.EditorViewportNavigationProfileId id, Inno.Editor.Rendering.EditorViewportNavigationCapabilities capabilities, Inno.Editor.Rendering.EditorViewportNavigationMode defaultMode)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L184) | Creates a provider-defined navigation profile.
`id`:Stable profile identity.
`capabilities`:Operations accepted by the provider.
`defaultMode`:Preferred mode when current state is unsupported. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationCapabilities Inno.Editor.Rendering.EditorViewportNavigationProfile.capabilities { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L219) | Gets the operations accepted by this provider. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationMode Inno.Editor.Rendering.EditorViewportNavigationProfile.defaultMode { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L224) | Gets the mode selected when current state is not supported. | — | — | -| [`public static Inno.Editor.Rendering.EditorViewportNavigationProfile Inno.Editor.Rendering.EditorViewportNavigationProfile.disabled { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L279) | Gets a disabled fallback profile used when no provider navigation contract exists. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.fastMovementMultiplier { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L269) | Gets or sets the multiplier used while fast fly movement is requested. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportFocusBounds? Inno.Editor.Rendering.EditorViewportNavigationProfile.focusBounds { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L234) | Gets or sets the optional current selection bound. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.framePadding { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L274) | Gets or sets additional framing space around selection bounds. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationProfileId Inno.Editor.Rendering.EditorViewportNavigationProfile.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L214) | Gets the stable profile identity. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.maximumFocusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L264) | Gets or sets maximum orbit or framing distance. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.maximumOrthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L254) | Gets or sets maximum orthographic half-height. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.minimumFocusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L259) | Gets or sets minimum orbit or framing distance. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.minimumOrthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L249) | Gets or sets minimum orthographic half-height. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.rotationSensitivity { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L239) | Gets or sets pointer rotation sensitivity in radians per pixel. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationProfile.worldUp { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L229) | Gets or sets the provider-defined world-up direction. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.zoomSensitivity { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L244) | Gets or sets exponential wheel zoom sensitivity. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportNavigationProfile`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L170) | Describes how the host may navigate one provider-owned viewport without exposing the provider's camera model. | — | — | - -### `Inno.Editor.Rendering.EditorViewportNavigationProfileId` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportNavigationProfileId.EditorViewportNavigationProfileId(string value)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L97) | Creates a stable profile identifier.
`value`:Globally stable profile identity. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.Equals(Inno.Editor.Rendering.EditorViewportNavigationProfileId other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportNavigationProfileId.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.operator ==(Inno.Editor.Rendering.EditorViewportNavigationProfileId left, Inno.Editor.Rendering.EditorViewportNavigationProfileId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.operator !=(Inno.Editor.Rendering.EditorViewportNavigationProfileId left, Inno.Editor.Rendering.EditorViewportNavigationProfileId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportNavigationProfileId.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L119) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L111) | Gets whether this identifier contains a usable value. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportNavigationProfileId.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L106) | Gets the stable profile identity. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportNavigationProfileId : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Identifies a provider-defined navigation profile across reload generations. | — | — | - -### `Inno.Editor.Rendering.EditorViewportNavigationState` - @@ -794,28 +395,6 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportNavigationState.EditorViewportNavigationState()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L303) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Editor.Rendering.EditorViewportNavigationState.ConfigureOrthographic(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, float orthographicSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L501) | Atomically initializes an orthographic navigation view.
`position`:Provider-defined world-space position.
`rotation`:Provider-defined world-space rotation.
`orthographicSize`:Positive orthographic half-height. | — | — | -| [`public void Inno.Editor.Rendering.EditorViewportNavigationState.ConfigurePerspective(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, float fieldOfView, float nearClip, float farClip)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L538) | Atomically initializes a perspective navigation view.
`position`:Provider-defined world-space position.
`rotation`:Provider-defined world-space rotation.
`fieldOfView`:Perspective vertical field of view in degrees.
`nearClip`:Positive near clipping distance.
`farClip`:Far clipping distance greater than nearClip. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.farClip { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L448) | Gets or sets the far clipping distance greater than the near distance. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.fieldOfView { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L418) | Gets or sets the perspective vertical field of view in degrees. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.focusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L463) | Gets or sets the positive orbit and framing distance. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportNavigationState.isInitialized { get; private set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L324) | Gets whether a provider or restored panel state initialized this state. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportNavigationMode Inno.Editor.Rendering.EditorViewportNavigationState.mode { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L344) | Gets or sets the active host navigation mode. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.movementSpeed { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L478) | Gets or sets the positive base movement speed in world units per second. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.nearClip { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L433) | Gets or sets the positive near clipping distance. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.orthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L403) | Gets or sets the positive orthographic half-height in provider world units. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationState.pivot { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L389) | Gets or sets the world-space orbit and framing pivot. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationState.position { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L359) | Gets or sets the provider-defined world-space view position. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportProjection Inno.Editor.Rendering.EditorViewportNavigationState.projection { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L329) | Gets or sets the current projection family. | — | — | -| [`public Inno.Core.Mathematics.Quaternion Inno.Editor.Rendering.EditorViewportNavigationState.rotation { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L373) | Gets or sets the normalized provider-defined world-space view rotation. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportNavigationState`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L303) | Stores rendering-model-neutral navigation state for one Editor viewport across provider reloads. | — | — | - -### `Inno.Editor.Rendering.EditorViewportOutput` - @@ -829,115 +408,6 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportOutput.EditorViewportOutput(string viewportId, Inno.Adapter.Presentation.PresentationTextureHandle texture, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L32) | Creates an editor viewport output snapshot.
`viewportId`:Stable viewport identity.
`texture`:Opaque presentation token, or an invalid token while warming up.
`pixelWidth`:Current target width.
`pixelHeight`:Current target height. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportOutput.Equals(Inno.Editor.Rendering.EditorViewportOutput other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportOutput.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportOutput.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportOutput.operator ==(Inno.Editor.Rendering.EditorViewportOutput left, Inno.Editor.Rendering.EditorViewportOutput right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportOutput.operator !=(Inno.Editor.Rendering.EditorViewportOutput left, Inno.Editor.Rendering.EditorViewportOutput right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportOutput.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportOutput.isReady { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L70) | Gets whether a completed target can be drawn. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportOutput.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L65) | Gets the current target height. | — | — | -| [`public int Inno.Editor.Rendering.EditorViewportOutput.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L60) | Gets the current target width. | — | — | -| [`public Inno.Adapter.Presentation.PresentationTextureHandle Inno.Editor.Rendering.EditorViewportOutput.texture { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L55) | Gets the opaque presentation token. | — | — | -| [`public string Inno.Editor.Rendering.EditorViewportOutput.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L50) | Gets the stable viewport identity. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportOutput : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Identifies a renderer-owned editor viewport image through an opaque presentation token. | — | — | - -### `Inno.Editor.Rendering.EditorViewportPointerContext` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public int Inno.Editor.Rendering.EditorViewportPointerContext.button { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L414) | Gets the platform-independent pointer button index. | — | — | -| [`public Inno.Editor.Rendering.EditorViewportContext Inno.Editor.Rendering.EditorViewportPointerContext.viewport { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L399) | Gets the owning frame-only viewport context. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportPointerContext.x { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L404) | Gets normalized horizontal pointer position. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportPointerContext.y { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L409) | Gets normalized vertical pointer position. | — | — | -| [`public sealed class Inno.Editor.Rendering.EditorViewportPointerContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L382) | Supplies normalized pointer interaction over one rendered viewport. | — | — | - -### `Inno.Editor.Rendering.EditorViewportPresentation` - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Rendering.EditorViewportPresentation.EditorViewportPresentation(Inno.Core.Mathematics.Color backgroundColor, float pixelDensity = 1)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L220) | Creates presentation preferences for one Editor viewport.
`backgroundColor`:Linear clear color preferred by the host panel.
`pixelDensity`:Physical render pixels per logical presentation unit. | — | — | -| [`public bool Inno.Editor.Rendering.EditorViewportPresentation.Equals(Inno.Editor.Rendering.EditorViewportPresentation other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Rendering.EditorViewportPresentation.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Rendering.EditorViewportPresentation.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportPresentation.operator ==(Inno.Editor.Rendering.EditorViewportPresentation left, Inno.Editor.Rendering.EditorViewportPresentation right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Rendering.EditorViewportPresentation.operator !=(Inno.Editor.Rendering.EditorViewportPresentation left, Inno.Editor.Rendering.EditorViewportPresentation right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Rendering.EditorViewportPresentation.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public Inno.Core.Mathematics.Color Inno.Editor.Rendering.EditorViewportPresentation.backgroundColor { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L233) | Gets the linear clear color preferred by the host panel. | — | — | -| [`public float Inno.Editor.Rendering.EditorViewportPresentation.pixelDensity { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L238) | Gets physical render pixels per logical presentation unit. | — | — | -| [`public readonly record struct Inno.Editor.Rendering.EditorViewportPresentation : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Describes host-selected presentation preferences without prescribing rendering behavior. | — | — | - -### `Inno.Editor.Rendering.EditorViewportProjection` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Rendering.EditorViewportProjection.Orthographic = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L15) | Uses a parallel projection controlled by an orthographic half-height. | — | — | -| [`Inno.Editor.Rendering.EditorViewportProjection.Perspective = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L20) | Uses a perspective projection controlled by a vertical field of view. | — | — | -| [`public enum Inno.Editor.Rendering.EditorViewportProjection : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L10) | Identifies the projection family represented by neutral Editor viewport navigation state. | — | — | - -### `Inno.Editor.Rendering.IEditorGizmoSink` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Rendering.IEditorGizmoSink.Icon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L76) | Adds a visible, selectable icon at an object's world position.
`owner`:The scene object selected when the icon is clicked.
`position`:The icon position in world space.
`iconId`:A stable semantic icon ID. Unknown IDs use the generic scene icon. | — | — | -| [`void Inno.Editor.Rendering.IEditorGizmoSink.Line(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L91) | Adds a non-interactive world-space line, normally for selected bounds.
`start`:The first world-space endpoint.
`end`:The second world-space endpoint. | — | — | -| [`public interface Inno.Editor.Rendering.IEditorGizmoSink`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L62) | Receives transient world-space gizmo geometry from independent Editor extensions. | — | — | - -### `Inno.Editor.Rendering.IEditorPreviewService` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Rendering.IEditorPreviewService.Draw(Inno.Editor.Rendering.EditorPreviewHandle handle, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L156) | Draws one current-generation preview into the active presentation surface.
`handle`:Preview handle returned by this service.
`logicalSize`:Positive destination size in logical pixels. | — | — | -| [`bool Inno.Editor.Rendering.IEditorPreviewService.Release(Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L170) | Releases one cached preview registration.
返回:true when the handle was active and released.
`handle`:Preview handle returned by this service. | — | — | -| [`void Inno.Editor.Rendering.IEditorPreviewService.ReleaseAll()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L175) | Releases every cached preview registration. | — | — | -| [`void Inno.Editor.Rendering.IEditorPreviewService.ReleaseRendered(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L103) | Releases an offscreen preview, including one which has not produced its first handle.
`viewportId`:The same consumer-qualified identity submitted in the composition. | — | — | -| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryGetTexture(Inno.Rendering.Assets.TextureAsset texture, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L117) | Tries to resolve a standalone texture preview without blocking target compilation.
返回:true when the preview is ready.
`texture`:Texture asset to preview.
`handle`:Receives a generation-scoped handle when ready. | — | — | -| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryGetTextureArtifact(Inno.Rendering.Assets.RenderTextureArtifactReference texture, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L140) | Tries to resolve a named texture artifact preview without blocking target compilation.
返回:true when the preview is ready.
`texture`:Stable named texture artifact reference.
`pixelWidth`:Positive source width.
`pixelHeight`:Positive source height.
`handle`:Receives a generation-scoped handle when ready. | — | — | -| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryRender(Inno.Editor.Rendering.EditorViewportComposition composition, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L92) | Submits an isolated rendering composition through the shared offscreen viewport bridge.
返回:True when the completed previous output can be drawn; false during initial allocation or resize.
`composition`:Invocation-local layers; the host retains no contributor instances after the frame.
`handle`:Receives a current-generation preview once its target is resident. | — | — | -| [`uint Inno.Editor.Rendering.IEditorPreviewService.deviceGeneration { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L78) | Gets the active rendering-device generation. | — | — | -| [`public interface Inno.Editor.Rendering.IEditorPreviewService`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L73) | Provides one shared preview texture bridge for browsers, inspectors, and document canvases. | — | — | - -### `Inno.Editor.Rendering.IEditorRenderingHost` - @@ -945,78 +415,388 @@ Host 创建或 resize `RenderTexture`,首个目标写入成功录制后再将 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Rendering.IEditorRenderingHost.Draw(Inno.Editor.Rendering.EditorViewportOutput output, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L108) | Draws a ready viewport output inside the current ImGui window.
`output`:Output returned by Inno.Editor.Rendering.IEditorRenderingHost.Submit(Inno.Editor.Rendering.EditorViewportComposition).
`logicalSize`:Destination size in logical UI pixels. | — | — | -| [`void Inno.Editor.Rendering.IEditorRenderingHost.Release(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L119) | Releases one viewport and queues its GPU target for frame-safe destruction.
`viewportId`:Stable viewport identity. | — | — | -| [`void Inno.Editor.Rendering.IEditorRenderingHost.ReleaseAll()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L124) | Releases every viewport owned by this editor host service. | — | — | -| [`Inno.Editor.Rendering.EditorViewportOutput Inno.Editor.Rendering.IEditorRenderingHost.Submit(Inno.Editor.Rendering.EditorViewportComposition composition)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L97) | Submits or updates one composed offscreen editor viewport.
返回:The current presentation output, which can be warming up for one frame.
`composition`:Complete ordered model composition for the current frame. | — | — | -| [`ulong Inno.Editor.Rendering.IEditorRenderingHost.currentFrameIndex { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L86) | Gets the current output frame index shared by Editor views. | — | — | -| [`Inno.Rendering.Runtime.IViewContentCollector Inno.Editor.Rendering.IEditorRenderingHost.viewContent { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L81) | Gets the active generation's model-independent world-content collector. | — | — | -| [`public interface Inno.Editor.Rendering.IEditorRenderingHost`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L76) | Bridges editor extensions to a host-owned render request sink and opaque texture presenter. | — | — | - -### `Inno.Editor.Rendering.IEditorShaderArtifactValidator` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Editor.Rendering.IEditorShaderArtifactValidator.Release(System.Guid documentId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L59) | Forgets pending and completed validation state owned by one document.
`documentId`:Closing or invalidated document identity. | — | — | -| [`Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot Inno.Editor.Rendering.IEditorShaderArtifactValidator.Request(System.Guid documentId, ulong revision, Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L47) | Requests or reads validation for an exact document revision and immutable artifact.
返回:Current detached validation state.
`documentId`:Stable Editor document identity.
`revision`:Exact document revision that produced the artifact.
`artifact`:Complete immutable candidate compiled for the active device. | — | — | -| [`Inno.Rendering.GraphicsCapabilities Inno.Editor.Rendering.IEditorShaderArtifactValidator.capabilities { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L30) | Gets the capability snapshot used to compile candidates for this validator. | — | — | -| [`public interface Inno.Editor.Rendering.IEditorShaderArtifactValidator`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L25) | Queues immutable shader candidates for validation at the active device's next frame safety point. | — | — | - -### `Inno.Editor.Rendering.PipelineDocuments` - + + + + + + + + + + + + + + + + - - - + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public void Inno.Editor.Rendering.PipelineDocuments.Commit(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L84) | Completes one gesture without saving the source.
`assetId`:Open Pipeline identity. | — | — | -| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L133) | Initializes this feature when its owning runtime becomes active.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L147) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L140) | Advances this feature using the current runtime state.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public System.Guid Inno.Editor.Rendering.PipelineDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L46) | Opens a native Pipeline source in the shared document service, including failed imports.
返回:The persistent source identity used by draft operations.
`path`:Mounted source path. | — | — | -| [`public Inno.Rendering.Assets.RenderPipelineAsset Inno.Editor.Rendering.PipelineDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L57) | Reads a detached current-generation Pipeline value; its referenced canonical assets must not be mutated.
返回:The unsaved native Pipeline value.
`assetId`:Open Pipeline identity. | — | — | -| [`public void Inno.Editor.Rendering.PipelineDocuments.Replace(System.Guid assetId, Inno.Rendering.Assets.RenderPipelineAsset candidate, bool finishGesture = true)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L71) | Changes a draft through shared History without publishing it to Scene/Game.
`assetId`:Open Pipeline identity.
`candidate`:Detached edited value.
`finishGesture`:Whether this sample completes the gesture. | — | — | -| [`public void Inno.Editor.Rendering.PipelineDocuments.ReplaceSettings(System.Guid assetId, TSettings settings, bool finishGesture = true) where TSettings : class, Inno.Core.Serialization.ISerializable`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L101) | Captures typed Pipeline settings with this source owner's complete reference and dependency context.
`assetId`:Open Pipeline identity.
`settings`:Detached edited settings.
`finishGesture`:Whether this sample completes the gesture. | — | — | -| [`public sealed class Inno.Editor.Rendering.PipelineDocuments : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L19) | Edits Pipeline sources and typed extension settings without changing canonical rendering assets before Save. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorRenderTargetArtifactProvider : Inno.Rendering.Runtime.IRenderTargetArtifactProvider, System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L23) | Produces target-specific render artifacts from imported authoring assets without exposing source access to the backend-neutral render runtime. | — | — | +| [`public Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.EditorRenderTargetArtifactProvider(Inno.Assets.Pipeline.AssetPipeline assets, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Extensibility.Types.TypeCatalog types, Inno.Rendering.Assets.Authoring.ShaderCompiler shaderCompiler, Inno.Rendering.Assets.Authoring.ITextureTargetCompiler textureCompiler, Inno.Core.Diagnostics.IDiagnosticReporter diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L63) | Creates an Editor artifact provider backed by explicit shader and texture toolchains.
`shaderCompiler`: The compiler that turns backend-neutral shader IR into device target programs.
`assets`: The authoring asset pipeline that owns source mounts.
`serialization`: The serialization registry that owns active Shader contracts.
`textureCompiler`: The compiler that turns artist texture sources into portable KTX artifacts.
`diagnostics`: The sink that receives compilation and last-good fallback diagnostics.
`types`: The shared authoring type generation owner for graph and source extensions. | — | `System.ArgumentNullException`: Thrown when any required service is null. | +| [`public Inno.Rendering.Runtime.RenderTargetArtifactStatus Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.GetShaderArtifact(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant, Inno.Rendering.GraphicsCapabilities capabilities, out Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L104) | Returns a matching compiled shader when available and schedules a new candidate when source state changed.
`shader`: The imported backend-neutral shader asset.
`variant`: The exact material keyword selection.
`capabilities`: The active device capability snapshot used to select the target compiler profile.
`artifact`: Receives the current candidate or the last-good artifact while a replacement is compiling. | when a current or last-good artifact is usable; otherwise, the exact pending or failed state of the first candidate. | `System.ObjectDisposedException`: Thrown when this provider has been disposed.
`System.ArgumentNullException`: Thrown when or is . | +| [`public Inno.Rendering.Assets.ShaderDefinition Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.ReadShaderDefinition(Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L154) | Reads and validates the shader definition value from its authoritative source.
`artifact`: The resolved immutable artifact payload returned to the caller. | The validated shader definition that represents the completed operation. | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.RequestShaderCompilation(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant, Inno.Rendering.GraphicsCapabilities capabilities)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L177) | Schedules compilation if required and reports saving-independent state for the exact shader, variant and device target.
`shader`: Current imported shader.
`variant`: Selected keyword variant.
`capabilities`: Current device capabilities. | A detached status and diagnostic snapshot; last-good is explicit and never implies current-source success. | — | +| [`public Inno.Rendering.Runtime.RenderTargetArtifactStatus Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.GetTextureArtifact(Inno.Rendering.Assets.RenderTextureArtifactReference texture, out System.ReadOnlyMemory artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L212) | Returns a matching compiled texture when available and schedules a replacement when source state changed.
`texture`: The imported texture asset whose authoring source is compiled.
`artifact`: Receives the current candidate or the last-good KTX artifact while a replacement is compiling. | when a current or last-good KTX artifact is usable; otherwise, the exact pending or failed state of the first candidate. | `System.ObjectDisposedException`: Thrown when this provider has been disposed.
`System.ArgumentNullException`: Thrown when is . | +| [`public void Inno.Editor.Rendering.EditorRenderTargetArtifactProvider.Dispose()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorRenderTargetArtifactProvider.cs#L240) | Cancels pending toolchain work and releases every cached authoring artifact. | — | — | +| [`public sealed record Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Contains active-device validation state for one immutable Editor shader candidate.
`state`: Pending, successful, or failed validation state.
`diagnostics`: Detached device validation diagnostics. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.EditorShaderArtifactValidationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Contains active-device validation state for one immutable Editor shader candidate.
`state`: Pending, successful, or failed validation state.
`diagnostics`: Detached device validation diagnostics. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L18) | Pending, successful, or failed validation state. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L19) | Detached device validation diagnostics. | — | — | +| [`public override string Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? left, Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? left, Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public interface Inno.Editor.Rendering.IEditorShaderArtifactValidator`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L25) | Queues immutable shader candidates for validation at the active device's next frame safety point. | — | — | +| [`Inno.Rendering.GraphicsCapabilities Inno.Editor.Rendering.IEditorShaderArtifactValidator.capabilities { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L30) | Gets the capability snapshot used to compile candidates for this validator. | — | — | +| [`Inno.Editor.Rendering.EditorShaderArtifactValidationSnapshot Inno.Editor.Rendering.IEditorShaderArtifactValidator.Request(System.Guid documentId, ulong revision, Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L47) | Requests or reads validation for an exact document revision and immutable artifact.
`documentId`: Stable Editor document identity.
`revision`: Exact document revision that produced the artifact.
`artifact`: Complete immutable candidate compiled for the active device. | Current detached validation state. | — | +| [`void Inno.Editor.Rendering.IEditorShaderArtifactValidator.Release(System.Guid documentId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderArtifactValidation.cs#L59) | Forgets pending and completed validation state owned by one document.
`documentId`: Closing or invalidated document identity. | — | — | +| [`public enum Inno.Editor.Rendering.EditorShaderCompilationState : int`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L13) | Identifies native compilation state independently of document persistence. | — | — | +| [`Inno.Editor.Rendering.EditorShaderCompilationState.Compiling = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L18) | The latest semantic candidate is being compiled. | — | — | +| [`Inno.Editor.Rendering.EditorShaderCompilationState.Succeeded = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L22) | The latest semantic candidate compiled successfully. | — | — | +| [`Inno.Editor.Rendering.EditorShaderCompilationState.Failed = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L26) | The latest semantic candidate failed. | — | — | +| [`public sealed record Inno.Editor.Rendering.EditorShaderCompilationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Contains detached compiler state without retaining assets, tasks or extension instances.
`state`: Latest requested semantic candidate state.
`usingLastGood`: Whether rendering still uses a previous successful candidate.
`diagnostics`: Immutable latest completed compiler diagnostics. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot.EditorShaderCompilationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, bool usingLastGood, System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Contains detached compiler state without retaining assets, tasks or extension instances.
`state`: Latest requested semantic candidate state.
`usingLastGood`: Whether rendering still uses a previous successful candidate.
`diagnostics`: Immutable latest completed compiler diagnostics. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderCompilationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L42) | Latest requested semantic candidate state. | — | — | +| [`public bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.usingLastGood { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L43) | Whether rendering still uses a previous successful candidate. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderCompilationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L44) | Immutable latest completed compiler diagnostics. | — | — | +| [`public override string Inno.Editor.Rendering.EditorShaderCompilationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorShaderCompilationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderCompilationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Rendering.EditorShaderCompilationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out bool usingLastGood, out System.Collections.Generic.IReadOnlyList diagnostics)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L41) | Inherited or compiler-provided contract. | — | — | +| [`public sealed record Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Contains a preview-only immutable candidate, never registered as a canonical asset artifact.
`state`: Latest draft compilation state.
`usingLastGood`: Whether the preview artifact belongs to an earlier successful draft.
`diagnostics`: Latest detached preview diagnostics, separate from project diagnostics.
`artifact`: Preview-only complete shader candidate, or null before the first success. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.EditorShaderDraftCompilationSnapshot(Inno.Editor.Rendering.EditorShaderCompilationState state, bool usingLastGood, System.Collections.Generic.IReadOnlyList diagnostics, Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Contains a preview-only immutable candidate, never registered as a canonical asset artifact.
`state`: Latest draft compilation state.
`usingLastGood`: Whether the preview artifact belongs to an earlier successful draft.
`diagnostics`: Latest detached preview diagnostics, separate from project diagnostics.
`artifact`: Preview-only complete shader candidate, or null before the first success. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationState Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.state { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L63) | Latest draft compilation state. | — | — | +| [`public bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.usingLastGood { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L64) | Whether the preview artifact belongs to an earlier successful draft. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.diagnostics { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L65) | Latest detached preview diagnostics, separate from project diagnostics. | — | — | +| [`public Inno.Rendering.Assets.RenderShaderArtifact? Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.artifact { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L66) | Preview-only complete shader candidate, or null before the first success. | — | — | +| [`public override string Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.operator !=(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.operator ==(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? left, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? right)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Equals(object? obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Equals(Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot? other)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot.Deconstruct(out Inno.Editor.Rendering.EditorShaderCompilationState state, out bool usingLastGood, out System.Collections.Generic.IReadOnlyList diagnostics, out Inno.Rendering.Assets.RenderShaderArtifact? artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L62) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorShaderCompilation`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L72) | Schedules Editor shader compilation for the active device without exposing native handles to authoring panels. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilation.EditorShaderCompilation(Inno.Editor.Rendering.EditorRenderTargetArtifactProvider provider, Inno.Rendering.GraphicsCapabilities capabilities)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L86) | Pairs the authoring artifact owner with the active device's immutable capability snapshot.
`provider`: Host-owned shader compilation service.
`capabilities`: Current device capability snapshot. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilation.EditorShaderCompilation(Inno.Editor.Rendering.EditorRenderTargetArtifactProvider provider, Inno.Editor.Rendering.IEditorShaderArtifactValidator validator)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L103) | Pairs the authoring artifact owner with active-device validation at frame safety points.
`provider`: Host-owned shader compilation service.
`validator`: Host-owned active-device artifact validator. | — | — | +| [`public Inno.Editor.Rendering.EditorShaderCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.Request(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L123) | Requests current compilation and reads its explicit last-good status.
`shader`: Current generation shader asset, not retained by the service.
`variant`: Exact static keyword selection. | A detached status snapshot. | — | +| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.RequestArtifact(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L141) | Reads the canonical Shader candidate for an isolated Material preview without modifying Material values.
`shader`: Current owner Shader reference.
`variant`: Exact Material keyword selection. | Compilation status and immutable last-good artifact when available. | — | +| [`public Inno.Rendering.Assets.ShaderDefinition Inno.Editor.Rendering.EditorShaderCompilation.ReadDefinition(Inno.Rendering.Assets.RenderShaderArtifact artifact)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L159) | Decodes the exact contract captured with a candidate using the current owner's references.
`artifact`: Complete candidate, not the latest uncompiled asset definition. | A detached current-generation definition. | — | +| [`public Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot Inno.Editor.Rendering.EditorShaderCompilation.RequestDraft(System.Guid documentId, Inno.Core.Graphs.GraphDocument graph, ulong revision, Inno.Rendering.Assets.RenderShaderVariant variant)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L179) | Compiles a detached document through the same graph toolchain in an isolated preview cache.
`documentId`: Preview owner identity, independent of canonical artifact lookup.
`graph`: Current draft, never modified or retained.
`revision`: Monotonic draft revision, including Undo/Redo.
`variant`: Exact static keyword selection. | Preview-only state and artifact; calling this method cannot publish to Scene/Game. | — | +| [`public void Inno.Editor.Rendering.EditorShaderCompilation.ReleaseDraft(System.Guid documentId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Compilation/EditorShaderCompilation.cs#L216) | Cancels a document's preview work and retires its cached candidate without touching canonical artifacts.
`documentId`: Closing preview owner identity. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorGizmoContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L12) | Provides scene content and selection for transient Editor gizmos. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoContext.EditorGizmoContext(Inno.References.ContentReadScope content, Inno.Core.Identity.RuntimeIdentity? selected, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L29) | Creates a frame-scoped gizmo request.
`content`: The scenes presented by the Scene viewport.
`selected`: The selected live object, when available.
`pixelWidth`: The Scene viewport width.
`pixelHeight`: The Scene viewport height. | — | — | +| [`public Inno.References.ContentReadScope Inno.Editor.Rendering.EditorGizmoContext.content { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L44) | Gets the scene roots visible in this Editor viewport. | — | — | +| [`public Inno.Core.Identity.RuntimeIdentity? Inno.Editor.Rendering.EditorGizmoContext.selected { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L48) | Gets the transient selected identity. | — | — | +| [`public int Inno.Editor.Rendering.EditorGizmoContext.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L52) | Gets the viewport width in physical pixels. | — | — | +| [`public int Inno.Editor.Rendering.EditorGizmoContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L56) | Gets the viewport height in physical pixels. | — | — | +| [`public interface Inno.Editor.Rendering.IEditorGizmoSink`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L62) | Receives transient world-space gizmo geometry from independent Editor extensions. | — | — | +| [`void Inno.Editor.Rendering.IEditorGizmoSink.Icon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L76) | Adds a visible, selectable icon at an object's world position.
`owner`: The scene object selected when the icon is clicked.
`position`: The icon position in world space.
`iconId`: A stable semantic icon ID. Unknown IDs use the generic scene icon. | — | — | +| [`void Inno.Editor.Rendering.IEditorGizmoSink.Line(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L91) | Adds a non-interactive world-space line, normally for selected bounds.
`start`: The first world-space endpoint.
`end`: The second world-space endpoint. | — | — | +| [`public abstract class Inno.Editor.Rendering.EditorGizmoProvider`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L100) | Contributes Editor-only icons and bounds without modifying a rendering model. | — | — | +| [`public abstract void Inno.Editor.Rendering.EditorGizmoProvider.Collect(Inno.Editor.Rendering.EditorGizmoContext context, Inno.Editor.Rendering.IEditorGizmoSink sink)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L111) | Submits transient icons and lines for the presented scenes.
`context`: The current Scene viewport request.
`sink`: The collector that owns submitted primitives for this frame. | — | — | +| [`protected Inno.Editor.Rendering.EditorGizmoProvider.EditorGizmoProvider()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L100) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L121) | Marks one reloadable Editor gizmo provider. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute.EditorGizmoProviderExtensionAttribute(string id)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L129) | Creates a provider declaration with a stable identifier.
`id`: The globally unique provider identifier. | — | — | +| [`public string Inno.Editor.Rendering.EditorGizmoProviderExtensionAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L138) | Gets the stable provider identifier. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorGizmoIcon : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | One selectable Editor icon.
`owner`: The object that owns the resulting lifetime and state.
`position`: The vector3 value used to initialize this instance.
`iconId`: The stable visual identity resolved by the Scene View presentation. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoIcon.EditorGizmoIcon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | One selectable Editor icon.
`owner`: The object that owns the resulting lifetime and state.
`position`: The vector3 value used to initialize this instance.
`iconId`: The stable visual identity resolved by the Scene View presentation. | — | — | +| [`public Inno.Core.Identity.Identity Inno.Editor.Rendering.EditorGizmoIcon.owner { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L154) | The object that owns the resulting lifetime and state. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoIcon.position { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L155) | The vector3 value used to initialize this instance. | — | — | +| [`public string Inno.Editor.Rendering.EditorGizmoIcon.iconId { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L156) | The stable visual identity resolved by the Scene View presentation. | — | — | +| [`public override string Inno.Editor.Rendering.EditorGizmoIcon.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorGizmoIcon.operator !=(Inno.Editor.Rendering.EditorGizmoIcon left, Inno.Editor.Rendering.EditorGizmoIcon right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorGizmoIcon.operator ==(Inno.Editor.Rendering.EditorGizmoIcon left, Inno.Editor.Rendering.EditorGizmoIcon right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorGizmoIcon.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorGizmoIcon.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorGizmoIcon.Equals(Inno.Editor.Rendering.EditorGizmoIcon other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Rendering.EditorGizmoIcon.Deconstruct(out Inno.Core.Identity.Identity owner, out Inno.Core.Mathematics.Vector3 position, out string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L153) | Inherited or compiler-provided contract. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorGizmoLine : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | One non-interactive Editor line.
`start`: The vector3 value used to initialize this instance.
`end`: The vector3 value used to initialize this instance. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoLine.EditorGizmoLine(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | One non-interactive Editor line.
`start`: The vector3 value used to initialize this instance.
`end`: The vector3 value used to initialize this instance. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoLine.start { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L169) | The vector3 value used to initialize this instance. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorGizmoLine.end { get; init; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L170) | The vector3 value used to initialize this instance. | — | — | +| [`public override string Inno.Editor.Rendering.EditorGizmoLine.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorGizmoLine.operator !=(Inno.Editor.Rendering.EditorGizmoLine left, Inno.Editor.Rendering.EditorGizmoLine right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorGizmoLine.operator ==(Inno.Editor.Rendering.EditorGizmoLine left, Inno.Editor.Rendering.EditorGizmoLine right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorGizmoLine.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorGizmoLine.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorGizmoLine.Equals(Inno.Editor.Rendering.EditorGizmoLine other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Editor.Rendering.EditorGizmoLine.Deconstruct(out Inno.Core.Mathematics.Vector3 start, out Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L168) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorGizmoFrame : Inno.Editor.Rendering.IEditorGizmoSink`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L176) | Holds the transient gizmo primitives for one Scene viewport frame. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorGizmoFrame.icons { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L184) | Gets the ordered selectable icons. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorGizmoFrame.lines { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L188) | Gets the ordered non-interactive lines. | — | — | +| [`public void Inno.Editor.Rendering.EditorGizmoFrame.Icon(Inno.Core.Identity.Identity owner, Inno.Core.Mathematics.Vector3 position, string iconId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L202) | Appends a selectable scene icon to the current gizmo frame.
`owner`: The object that owns the resulting lifetime and state.
`position`: The position consumed by icon; ownership remains with the caller unless explicitly stated otherwise.
`iconId`: The stable icon identity resolved by the Scene View. | — | — | +| [`public void Inno.Editor.Rendering.EditorGizmoFrame.Line(Inno.Core.Mathematics.Vector3 start, Inno.Core.Mathematics.Vector3 end)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L220) | Appends a world space line to the current gizmo frame.
`start`: The start consumed by line; ownership remains with the caller unless explicitly stated otherwise.
`end`: The end consumed by line; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoFrame.EditorGizmoFrame()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorGizmoProvider.cs#L176) | Inherited or compiler-provided contract. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorPreviewHandle : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Identifies one preview texture owned by a specific rendering-device generation. | — | — | +| [`public Inno.Editor.Rendering.EditorPreviewHandle.EditorPreviewHandle(ulong value, uint deviceGeneration, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L28) | Creates one generation-scoped preview handle.
`value`: Non-zero preview identity.
`deviceGeneration`: Non-zero rendering-device generation.
`pixelWidth`: Positive source width.
`pixelHeight`: Positive source height. | — | — | +| [`public ulong Inno.Editor.Rendering.EditorPreviewHandle.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L47) | Gets the opaque preview identity. | — | — | +| [`public uint Inno.Editor.Rendering.EditorPreviewHandle.deviceGeneration { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L52) | Gets the rendering-device generation that owns this handle. | — | — | +| [`public int Inno.Editor.Rendering.EditorPreviewHandle.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L57) | Gets source width in pixels. | — | — | +| [`public int Inno.Editor.Rendering.EditorPreviewHandle.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L62) | Gets source height in pixels. | — | — | +| [`public bool Inno.Editor.Rendering.EditorPreviewHandle.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L67) | Gets whether this handle identifies a usable generation-scoped preview. | — | — | +| [`public override string Inno.Editor.Rendering.EditorPreviewHandle.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorPreviewHandle.operator !=(Inno.Editor.Rendering.EditorPreviewHandle left, Inno.Editor.Rendering.EditorPreviewHandle right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorPreviewHandle.operator ==(Inno.Editor.Rendering.EditorPreviewHandle left, Inno.Editor.Rendering.EditorPreviewHandle right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorPreviewHandle.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorPreviewHandle.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorPreviewHandle.Equals(Inno.Editor.Rendering.EditorPreviewHandle other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L11) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public interface Inno.Editor.Rendering.IEditorPreviewService`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L73) | Provides one shared preview texture bridge for browsers, inspectors, and document canvases. | — | — | +| [`uint Inno.Editor.Rendering.IEditorPreviewService.deviceGeneration { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L78) | Gets the active rendering-device generation. | — | — | +| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryRender(Inno.Editor.Rendering.EditorViewportComposition composition, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L92) | Submits an isolated rendering composition through the shared offscreen viewport bridge.
`composition`: Invocation-local layers; the host retains no contributor instances after the frame.
`handle`: Receives a current-generation preview once its target is resident. | True when the completed previous output can be drawn; false during initial allocation or resize. | — | +| [`void Inno.Editor.Rendering.IEditorPreviewService.ReleaseRendered(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L103) | Releases an offscreen preview, including one which has not produced its first handle.
`viewportId`: The same consumer-qualified identity submitted in the composition. | — | — | +| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryGetTexture(Inno.Rendering.Assets.TextureAsset texture, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L117) | Tries to resolve a standalone texture preview without blocking target compilation.
`texture`: Texture asset to preview.
`handle`: Receives a generation-scoped handle when ready. | when the preview is ready. | — | +| [`bool Inno.Editor.Rendering.IEditorPreviewService.TryGetTextureArtifact(Inno.Rendering.Assets.RenderTextureArtifactReference texture, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L140) | Tries to resolve a named texture artifact preview without blocking target compilation.
`texture`: Stable named texture artifact reference.
`pixelWidth`: Positive source width.
`pixelHeight`: Positive source height.
`handle`: Receives a generation-scoped handle when ready. | when the preview is ready. | — | +| [`void Inno.Editor.Rendering.IEditorPreviewService.Draw(Inno.Editor.Rendering.EditorPreviewHandle handle, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L156) | Draws one current-generation preview into the active presentation surface.
`handle`: Preview handle returned by this service.
`logicalSize`: Positive destination size in logical pixels. | — | — | +| [`bool Inno.Editor.Rendering.IEditorPreviewService.Release(Inno.Editor.Rendering.EditorPreviewHandle handle)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L170) | Releases one cached preview registration.
`handle`: Preview handle returned by this service. | when the handle was active and released. | — | +| [`void Inno.Editor.Rendering.IEditorPreviewService.ReleaseAll()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorPreviewService.cs#L175) | Releases every cached preview registration. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorRenderingModule : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L22) | Composes reloadable rendering-model contributors while retaining only opaque presentation outputs. | — | — | +| [`public Inno.Editor.Rendering.EditorRenderingModule.EditorRenderingModule(Inno.Editor.Rendering.IEditorRenderingHost host, Inno.Editor.Interactions.EditorInteractions interactions, Inno.Extensibility.Types.TypeCatalog types)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L56) | Creates the composition host around stable rendering and interaction services.
`host`: Host-owned target and opaque texture bridge.
`interactions`: Shared Editor interaction and selection service.
`types`: The type catalog that owns viewport contributor generations. | — | — | +| [`public Inno.Editor.Rendering.EditorGizmoFrame Inno.Editor.Rendering.EditorRenderingModule.CollectGizmos(string viewportId, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L84) | Collects Editor-only icons and bounds for the current Scene viewport.
`viewportId`: The Scene viewport identity.
`pixelWidth`: The viewport width.
`pixelHeight`: The viewport height. | Transient gizmo primitives for the submitted frame. | — | +| [`public bool Inno.Editor.Rendering.EditorRenderingModule.HasContributors(Inno.Editor.Rendering.EditorViewportKindId kind)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L110) | Gets whether the current extension generation contributes to one viewport purpose.
`kind`: Open viewport purpose. | when at least one contributor is registered. | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetOutputInput(string viewportId, Inno.Rendering.Runtime.RenderOutputInput input)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L121) | Supplies viewport-local input captured by the output panel for this frame.
`viewportId`: Stable output identity.
`input`: Physical-pixel input snapshot. | — | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetOutputRoute(string viewportId, Inno.Rendering.Runtime.RenderOutputRoute? route)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L138) | Sets the exact model contributor order for an output with several applicable models.
`viewportId`: Stable output identity.
`route`: Explicit contributor route, or null for single-model selection. | — | — | +| [`public string? Inno.Editor.Rendering.EditorRenderingModule.GetCompositionError(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L158) | Gets the most recent isolated contribution or composition failure for one stable viewport.
`viewportId`: Stable panel viewport identity. | The failure message, or null when no current failure exists. | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationState Inno.Editor.Rendering.EditorRenderingModule.GetNavigationState(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L174) | Gets the host-owned neutral navigation state for one stable Editor viewport.
`viewportId`: Stable panel viewport identity. | The reusable navigation state owned by the Editor host. | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetContentScope(string viewportId, Inno.References.ContentReadScope content)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L195) | Sets the explicit ordered host content visible to one Editor viewport.
`viewportId`: Stable panel viewport identity.
`content`: Current frame-safe content scope. | — | — | +| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TryConfigureNavigation(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorViewportNavigationProfile profile)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L227) | Queries the selected controller's navigation contract before viewport input is processed.
`kind`: Open viewport purpose.
`viewportId`: Stable panel viewport identity.
`pixelWidth`: Positive target width.
`pixelHeight`: Positive target height.
`profile`: Receives the selected controller profile. | True when a contributor returned a usable navigation profile and became the controller. | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.SetPresentation(string viewportId, Inno.Editor.Rendering.EditorViewportPresentation presentation)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L275) | Sets presentation preferences supplied to every contributor for one Editor viewport.
`viewportId`: Stable panel viewport identity.
`presentation`: Current host-owned presentation preferences. | — | — | +| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TryGetManipulationSpace(string viewportId, out Inno.Editor.Rendering.EditorViewportManipulationSpace space)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L296) | Tries to get the manipulation space from the selected controller's latest contribution.
`viewportId`: Stable panel viewport identity.
`space`: Receives the latest exact view/projection contract. | when the selected controller supplied a manipulation space. | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.DrawControllerToolbar(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L319) | Draws toolbar controls owned by the selected model controller.
`kind`: Open viewport purpose.
`viewportId`: Stable panel viewport identity.
`pixelWidth`: Current target width.
`pixelHeight`: Current target height. | — | — | +| [`public bool Inno.Editor.Rendering.EditorRenderingModule.TrySubmit(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, out Inno.Editor.Rendering.EditorViewportOutput output)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L368) | Builds, composes, submits, and returns one host-owned offscreen viewport.
`kind`: Open viewport purpose.
`viewportId`: Stable panel viewport identity.
`pixelWidth`: Positive target width.
`pixelHeight`: Positive target height.
`output`: Receives the opaque viewport output. | when at least one model contribution was accepted. | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.HandlePointer(Inno.Editor.Rendering.EditorViewportKindId kind, string viewportId, int pixelWidth, int pixelHeight, float x, float y, int button)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L518) | Forwards a normalized click to the selected model controller.
`kind`: Open viewport purpose.
`viewportId`: Stable panel viewport identity.
`pixelWidth`: Current target width.
`pixelHeight`: Current target height.
`x`: Normalized horizontal position.
`y`: Normalized vertical position.
`button`: Platform-independent pointer button index. | — | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.Draw(Inno.Editor.Rendering.EditorViewportOutput output, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L558) | Draws a ready output in the current panel.
`output`: Opaque output returned by .
`logicalSize`: Destination size in logical UI pixels. | — | — | +| [`public void Inno.Editor.Rendering.EditorRenderingModule.Release(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L569) | Stops retaining one viewport target.
`viewportId`: Stable viewport identity. | — | — | +| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L587) | Initializes this feature when its owning runtime becomes active.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L599) | Advances this feature using the current runtime state.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L654) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Rendering.EditorRenderingModule.OnDispose()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorRenderingModule.cs#L672) | Releases resources retained by this feature after it has stopped. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportKindId : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Identifies one open Editor viewport purpose without prescribing rendering semantics. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportKindId.EditorViewportKindId(string value)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L24) | Creates a stable viewport purpose identifier.
`value`: Globally stable viewport purpose. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportKindId.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L33) | Gets the stable viewport purpose. | — | — | +| [`public bool Inno.Editor.Rendering.EditorViewportKindId.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L38) | Gets whether this identifier is usable. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportKindId.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L46) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportKindId.operator !=(Inno.Editor.Rendering.EditorViewportKindId left, Inno.Editor.Rendering.EditorViewportKindId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportKindId.operator ==(Inno.Editor.Rendering.EditorViewportKindId left, Inno.Editor.Rendering.EditorViewportKindId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportKindId.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportKindId.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportKindId.Equals(Inno.Editor.Rendering.EditorViewportKindId other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L16) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L53) | Marks a reloadable rendering-model contributor for one Editor viewport purpose. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.EditorViewportContributorExtensionAttribute(string id, string kind, int order = 0, int controllerPriority = 0)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L70) | Creates a viewport contributor declaration.
`id`: Globally stable contributor identity.
`kind`: Open viewport purpose handled by the contributor.
`order`: Ascending model-composition order within the viewport.
`controllerPriority`: Priority used to select the contributor that owns navigation, tools, and pointer interaction. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L87) | Gets the globally stable contributor identity. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportKindId Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.kind { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L92) | Gets the open viewport purpose handled by the contributor. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.order { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L97) | Gets ascending model-composition order. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportContributorExtensionAttribute.controllerPriority { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L102) | Gets the priority used to select the viewport interaction controller. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L108) | Supplies frame-only Editor interaction and output dimensions to a contributor. | — | — | +| [`public Inno.Editor.Core.EditorContext Inno.Editor.Rendering.EditorViewportContext.editor { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L149) | Gets the current Editor context. | — | — | +| [`public Inno.Editor.Interactions.EditorInteractions Inno.Editor.Rendering.EditorViewportContext.interactions { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L154) | Gets the shared Editor interaction and selection service. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportKindId Inno.Editor.Rendering.EditorViewportContext.kind { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L159) | Gets the open viewport purpose. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportContext.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L164) | Gets the stable panel viewport identity. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportContext.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L169) | Gets target width in physical pixels. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L174) | Gets target height in physical pixels. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationState Inno.Editor.Rendering.EditorViewportContext.navigation { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L179) | Gets host-owned neutral navigation state that the selected controller can map to its camera model. | — | — | +| [`public Inno.References.ContentReadScope Inno.Editor.Rendering.EditorViewportContext.content { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L184) | Gets the explicit ordered host content visible to this viewport. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportPresentation Inno.Editor.Rendering.EditorViewportContext.presentation { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L189) | Gets host-selected presentation preferences for this viewport. | — | — | +| [`public Inno.Rendering.Runtime.IViewContentCollector Inno.Editor.Rendering.EditorViewportContext.viewContent { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L194) | Gets the active generation's model-independent world-content collector. | — | — | +| [`public ulong Inno.Editor.Rendering.EditorViewportContext.frameIndex { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L199) | Gets the frame index shared by all Editor views in this output frame. | — | — | +| [`public Inno.Rendering.Runtime.RenderOutputInput Inno.Editor.Rendering.EditorViewportContext.input { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L203) | Gets input located in this viewport's physical pixels. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportPresentation : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Describes host-selected presentation preferences without prescribing rendering behavior. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportPresentation.EditorViewportPresentation(Inno.Core.Mathematics.Color backgroundColor, float pixelDensity = 1)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L220) | Creates presentation preferences for one Editor viewport.
`backgroundColor`: Linear clear color preferred by the host panel.
`pixelDensity`: Physical render pixels per logical presentation unit. | — | — | +| [`public Inno.Core.Mathematics.Color Inno.Editor.Rendering.EditorViewportPresentation.backgroundColor { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L233) | Gets the linear clear color preferred by the host panel. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportPresentation.pixelDensity { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L238) | Gets physical render pixels per logical presentation unit. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportPresentation.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportPresentation.operator !=(Inno.Editor.Rendering.EditorViewportPresentation left, Inno.Editor.Rendering.EditorViewportPresentation right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportPresentation.operator ==(Inno.Editor.Rendering.EditorViewportPresentation left, Inno.Editor.Rendering.EditorViewportPresentation right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportPresentation.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportPresentation.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportPresentation.Equals(Inno.Editor.Rendering.EditorViewportPresentation other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L209) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public enum Inno.Editor.Rendering.EditorViewportManipulationPlane : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L244) | Selects whether transform handles use all axes or a contributor-declared planar orientation. | — | — | +| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.Spatial = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L249) | Keeps the full three-dimensional translation, rotation, and scale controls. | — | — | +| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.XY = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L254) | Uses the X/Y translation and scale axes with the Z rotation handle. | — | — | +| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.XZ = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L259) | Uses the X/Z translation and scale axes with the Y rotation handle. | — | — | +| [`Inno.Editor.Rendering.EditorViewportManipulationPlane.YZ = 3`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L264) | Uses the Y/Z translation and scale axes with the X rotation handle. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportManipulationSpace : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Describes the exact backend-neutral view and projection used to draw a viewport so host tools can manipulate selected scene transforms without knowing the controller's camera model. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportManipulationSpace.EditorViewportManipulationSpace(Inno.Core.Mathematics.Matrix viewMatrix, Inno.Core.Mathematics.Matrix projectionMatrix, bool isOrthographic, Inno.Editor.Rendering.EditorViewportManipulationPlane plane = Inno.Editor.Rendering.EditorViewportManipulationPlane.Spatial)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L291) | Creates a manipulation space matching one rendered viewport frame.
`viewMatrix`: World-to-view matrix used by the submitted frame.
`projectionMatrix`: View-to-clip matrix used by the submitted frame.
`isOrthographic`: Whether the projection is orthographic; this does not imply planar manipulation.
`plane`: The contributor-selected transform handle orientation, independent of projection. | — | `System.ArgumentOutOfRangeException`: The plane is not one of the defined neutral manipulation orientations. | +| [`public Inno.Core.Mathematics.Matrix Inno.Editor.Rendering.EditorViewportManipulationSpace.viewMatrix { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L308) | Gets the world-to-view matrix used by the submitted frame. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Editor.Rendering.EditorViewportManipulationSpace.projectionMatrix { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L313) | Gets the view-to-clip matrix used by the submitted frame. | — | — | +| [`public bool Inno.Editor.Rendering.EditorViewportManipulationSpace.isOrthographic { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L318) | Gets whether the submitted frame used an orthographic projection. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportManipulationPlane Inno.Editor.Rendering.EditorViewportManipulationSpace.plane { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L323) | Gets the contributor-declared neutral manipulation plane, or Spatial for all axes. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportManipulationSpace.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportManipulationSpace.operator !=(Inno.Editor.Rendering.EditorViewportManipulationSpace left, Inno.Editor.Rendering.EditorViewportManipulationSpace right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportManipulationSpace.operator ==(Inno.Editor.Rendering.EditorViewportManipulationSpace left, Inno.Editor.Rendering.EditorViewportManipulationSpace right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportManipulationSpace.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportManipulationSpace.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportManipulationSpace.Equals(Inno.Editor.Rendering.EditorViewportManipulationSpace other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L271) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportContribution`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L329) | Returns one rendering-model contribution without exposing a GPU backend. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportContribution.EditorViewportContribution(Inno.Rendering.Runtime.RenderFrameData data, Inno.Rendering.Assets.RenderPipelineAsset? pipeline = null, Inno.Rendering.RenderTextureFormat targetFormat = Inno.Rendering.RenderTextureFormat.RGBA8Srgb, Inno.Editor.Rendering.EditorViewportManipulationSpace? manipulationSpace = null)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L346) | Creates a viewport model contribution.
`data`: Pipeline-defined frame-only data.
`pipeline`: Contributor-selected pipeline, or null for the project default.
`targetFormat`: Presentation target format expected by the pipeline.
`manipulationSpace`: Optional exact view/projection contract for host-owned transform manipulation tools. | — | — | +| [`public Inno.Rendering.Runtime.RenderFrameData Inno.Editor.Rendering.EditorViewportContribution.data { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L361) | Gets pipeline-defined frame-only data. | — | — | +| [`public Inno.Rendering.Assets.RenderPipelineAsset? Inno.Editor.Rendering.EditorViewportContribution.pipeline { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L366) | Gets the selected pipeline, or null for the project default. | — | — | +| [`public Inno.Rendering.RenderTextureFormat Inno.Editor.Rendering.EditorViewportContribution.targetFormat { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L371) | Gets the presentation target format expected by the pipeline. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportManipulationSpace? Inno.Editor.Rendering.EditorViewportContribution.manipulationSpace { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L376) | Gets the optional exact view/projection contract used by host-owned transform manipulation tools. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportPointerContext`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L382) | Supplies normalized pointer interaction over one rendered viewport. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportContext Inno.Editor.Rendering.EditorViewportPointerContext.viewport { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L399) | Gets the owning frame-only viewport context. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportPointerContext.x { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L404) | Gets normalized horizontal pointer position. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportPointerContext.y { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L409) | Gets normalized vertical pointer position. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportPointerContext.button { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L414) | Gets the platform-independent pointer button index. | — | — | +| [`public abstract class Inno.Editor.Rendering.EditorViewportContributor`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L420) | Contributes one rendering model while the host owns viewport composition, targets, and presentation. | — | — | +| [`protected Inno.Editor.Rendering.EditorViewportContributor.EditorViewportContributor()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L425) | Creates a parameterless reloadable viewport contributor. | — | — | +| [`public abstract bool Inno.Editor.Rendering.EditorViewportContributor.CanContribute(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L438) | Determines whether this rendering model participates in the supplied viewport content.
`context`: Frame-only Editor, navigation, content, and viewport context. | when this contributor can build a valid model contribution. | — | +| [`public virtual Inno.Editor.Rendering.EditorViewportNavigationProfile Inno.Editor.Rendering.EditorViewportContributor.ConfigureNavigation(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L449) | Configures neutral host navigation before input is processed and a request is built.
`context`: Frame-only Editor, navigation, content, and viewport context. | The controller's current navigation capabilities and optional selection focus bound. | — | +| [`public abstract Inno.Editor.Rendering.EditorViewportContribution Inno.Editor.Rendering.EditorViewportContributor.Build(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L461) | Builds one model-neutral render contribution for the current frame.
`context`: Frame-only Editor and viewport context. | Contributor-selected pipeline and frame data. | — | +| [`public virtual void Inno.Editor.Rendering.EditorViewportContributor.DrawToolbar(Inno.Editor.Rendering.EditorViewportContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L469) | Draws optional controller-specific toolbar controls when this contributor owns viewport interaction.
`context`: Frame-only Editor and viewport context. | — | — | +| [`public virtual void Inno.Editor.Rendering.EditorViewportContributor.HandlePointer(Inno.Editor.Rendering.EditorViewportPointerContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportContributor.cs#L479) | Handles one pointer click when this contributor owns viewport interaction.
`context`: Normalized frame-only pointer context. | — | — | +| [`public enum Inno.Editor.Rendering.EditorViewportProjection : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L10) | Identifies the projection family represented by neutral Editor viewport navigation state. | — | — | +| [`Inno.Editor.Rendering.EditorViewportProjection.Orthographic = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L15) | Uses a parallel projection controlled by an orthographic half-height. | — | — | +| [`Inno.Editor.Rendering.EditorViewportProjection.Perspective = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L20) | Uses a perspective projection controlled by a vertical field of view. | — | — | +| [`public enum Inno.Editor.Rendering.EditorViewportNavigationMode : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L26) | Identifies the active interaction model without prescribing a rendering camera type. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Planar = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L31) | Pans and zooms over a provider-defined plane. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Orbit = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L36) | Rotates around a focus pivot and dollies along the view direction. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationMode.Fly = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L41) | Moves and looks freely from the current view position. | — | — | +| [`public enum Inno.Editor.Rendering.EditorViewportNavigationCapabilities : int`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L48) | Declares navigation operations supported by one viewport interaction controller. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.None = 0`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L53) | Disables host-owned navigation. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Pan = 1`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L58) | Allows translating the view parallel to its image plane. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Zoom = 2`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L63) | Allows changing orthographic size or perspective focus distance. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Planar = Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Pan \| Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Zoom`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L68) | Allows the complete plane-oriented pan and zoom interaction model. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Orbit = 4`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L73) | Allows pivot-oriented orbit and dolly. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.Fly = 8`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L78) | Allows free-look movement. | — | — | +| [`Inno.Editor.Rendering.EditorViewportNavigationCapabilities.FrameSelection = 16`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L83) | Allows framing a provider-supplied or host-derived selection bound. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportNavigationProfileId : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Identifies a provider-defined navigation profile across reload generations. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationProfileId.EditorViewportNavigationProfileId(string value)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L97) | Creates a stable profile identifier.
`value`: Globally stable profile identity. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportNavigationProfileId.value { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L106) | Gets the stable profile identity. | — | — | +| [`public bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.isValid { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L111) | Gets whether this identifier contains a usable value. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportNavigationProfileId.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L119) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.operator !=(Inno.Editor.Rendering.EditorViewportNavigationProfileId left, Inno.Editor.Rendering.EditorViewportNavigationProfileId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.operator ==(Inno.Editor.Rendering.EditorViewportNavigationProfileId left, Inno.Editor.Rendering.EditorViewportNavigationProfileId right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportNavigationProfileId.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportNavigationProfileId.Equals(Inno.Editor.Rendering.EditorViewportNavigationProfileId other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L89) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportFocusBounds : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Describes a world-space sphere that can be framed by host navigation. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportFocusBounds.EditorViewportFocusBounds(Inno.Core.Mathematics.Vector3 center, float radius)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L142) | Creates a finite focus bound.
`center`: World-space focus center.
`radius`: Non-negative world-space radius. | — | `System.ArgumentException`: Thrown when the center contains a non-finite value.
`System.ArgumentOutOfRangeException`: Thrown when the radius is negative or non-finite. | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportFocusBounds.center { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L157) | Gets the world-space focus center. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportFocusBounds.radius { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L162) | Gets the non-negative world-space radius. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportFocusBounds.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportFocusBounds.operator !=(Inno.Editor.Rendering.EditorViewportFocusBounds left, Inno.Editor.Rendering.EditorViewportFocusBounds right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportFocusBounds.operator ==(Inno.Editor.Rendering.EditorViewportFocusBounds left, Inno.Editor.Rendering.EditorViewportFocusBounds right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportFocusBounds.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportFocusBounds.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportFocusBounds.Equals(Inno.Editor.Rendering.EditorViewportFocusBounds other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L125) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportNavigationProfile`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L170) | Describes how the host may navigate one provider-owned viewport without exposing the provider's camera model. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationProfile.EditorViewportNavigationProfile(Inno.Editor.Rendering.EditorViewportNavigationProfileId id, Inno.Editor.Rendering.EditorViewportNavigationCapabilities capabilities, Inno.Editor.Rendering.EditorViewportNavigationMode defaultMode)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L184) | Creates a provider-defined navigation profile.
`id`: Stable profile identity.
`capabilities`: Operations accepted by the provider.
`defaultMode`: Preferred mode when current state is unsupported. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationProfileId Inno.Editor.Rendering.EditorViewportNavigationProfile.id { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L214) | Gets the stable profile identity. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationCapabilities Inno.Editor.Rendering.EditorViewportNavigationProfile.capabilities { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L219) | Gets the operations accepted by this provider. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationMode Inno.Editor.Rendering.EditorViewportNavigationProfile.defaultMode { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L224) | Gets the mode selected when current state is not supported. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationProfile.worldUp { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L229) | Gets or sets the provider-defined world-up direction. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportFocusBounds? Inno.Editor.Rendering.EditorViewportNavigationProfile.focusBounds { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L234) | Gets or sets the optional current selection bound. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.rotationSensitivity { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L239) | Gets or sets pointer rotation sensitivity in radians per pixel. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.zoomSensitivity { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L244) | Gets or sets exponential wheel zoom sensitivity. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.minimumOrthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L249) | Gets or sets minimum orthographic half-height. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.maximumOrthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L254) | Gets or sets maximum orthographic half-height. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.minimumFocusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L259) | Gets or sets minimum orbit or framing distance. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.maximumFocusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L264) | Gets or sets maximum orbit or framing distance. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.fastMovementMultiplier { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L269) | Gets or sets the multiplier used while fast fly movement is requested. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationProfile.framePadding { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L274) | Gets or sets additional framing space around selection bounds. | — | — | +| [`public static Inno.Editor.Rendering.EditorViewportNavigationProfile Inno.Editor.Rendering.EditorViewportNavigationProfile.disabled { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L279) | Gets a disabled fallback profile used when no provider navigation contract exists. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportNavigationState`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L303) | Stores rendering-model-neutral navigation state for one Editor viewport across provider reloads. | — | — | +| [`public bool Inno.Editor.Rendering.EditorViewportNavigationState.isInitialized { get; private set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L324) | Gets whether a provider or restored panel state initialized this state. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportProjection Inno.Editor.Rendering.EditorViewportNavigationState.projection { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L329) | Gets or sets the current projection family. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationMode Inno.Editor.Rendering.EditorViewportNavigationState.mode { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L344) | Gets or sets the active host navigation mode. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationState.position { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L359) | Gets or sets the provider-defined world-space view position. | — | — | +| [`public Inno.Core.Mathematics.Quaternion Inno.Editor.Rendering.EditorViewportNavigationState.rotation { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L373) | Gets or sets the normalized provider-defined world-space view rotation. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Editor.Rendering.EditorViewportNavigationState.pivot { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L389) | Gets or sets the world-space orbit and framing pivot. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.orthographicSize { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L403) | Gets or sets the positive orthographic half-height in provider world units. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.fieldOfView { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L418) | Gets or sets the perspective vertical field of view in degrees. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.nearClip { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L433) | Gets or sets the positive near clipping distance. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.farClip { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L448) | Gets or sets the far clipping distance greater than the near distance. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.focusDistance { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L463) | Gets or sets the positive orbit and framing distance. | — | — | +| [`public float Inno.Editor.Rendering.EditorViewportNavigationState.movementSpeed { get; set; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L478) | Gets or sets the positive base movement speed in world units per second. | — | — | +| [`public void Inno.Editor.Rendering.EditorViewportNavigationState.ConfigureOrthographic(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, float orthographicSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L501) | Atomically initializes an orthographic navigation view.
`position`: Provider-defined world-space position.
`rotation`: Provider-defined world-space rotation.
`orthographicSize`: Positive orthographic half-height. | — | — | +| [`public void Inno.Editor.Rendering.EditorViewportNavigationState.ConfigurePerspective(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, float fieldOfView, float nearClip, float farClip)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L538) | Atomically initializes a perspective navigation view.
`position`: Provider-defined world-space position.
`rotation`: Provider-defined world-space rotation.
`fieldOfView`: Perspective vertical field of view in degrees.
`nearClip`: Positive near clipping distance.
`farClip`: Far clipping distance greater than . | — | — | +| [`public Inno.Editor.Rendering.EditorViewportNavigationState.EditorViewportNavigationState()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportNavigation.cs#L303) | Inherited or compiler-provided contract. | — | — | +| [`public readonly record struct Inno.Editor.Rendering.EditorViewportOutput : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Identifies a renderer-owned editor viewport image through an opaque presentation token. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportOutput.EditorViewportOutput(string viewportId, Inno.Adapter.Presentation.PresentationTextureHandle texture, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L32) | Creates an editor viewport output snapshot.
`viewportId`: Stable viewport identity.
`texture`: Opaque presentation token, or an invalid token while warming up.
`pixelWidth`: Current target width.
`pixelHeight`: Current target height. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportOutput.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L50) | Gets the stable viewport identity. | — | — | +| [`public Inno.Adapter.Presentation.PresentationTextureHandle Inno.Editor.Rendering.EditorViewportOutput.texture { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L55) | Gets the opaque presentation token. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportOutput.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L60) | Gets the current target width. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportOutput.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L65) | Gets the current target height. | — | — | +| [`public bool Inno.Editor.Rendering.EditorViewportOutput.isReady { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L70) | Gets whether a completed target can be drawn. | — | — | +| [`public override string Inno.Editor.Rendering.EditorViewportOutput.ToString()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportOutput.operator !=(Inno.Editor.Rendering.EditorViewportOutput left, Inno.Editor.Rendering.EditorViewportOutput right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Rendering.EditorViewportOutput.operator ==(Inno.Editor.Rendering.EditorViewportOutput left, Inno.Editor.Rendering.EditorViewportOutput right)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Rendering.EditorViewportOutput.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Rendering.EditorViewportOutput.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Rendering.EditorViewportOutput.Equals(Inno.Editor.Rendering.EditorViewportOutput other)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L15) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public interface Inno.Editor.Rendering.IEditorRenderingHost`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L76) | Bridges editor extensions to a host-owned render request sink and opaque texture presenter. | — | — | +| [`Inno.Rendering.Runtime.IViewContentCollector Inno.Editor.Rendering.IEditorRenderingHost.viewContent { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L81) | Gets the active generation's model-independent world-content collector. | — | — | +| [`ulong Inno.Editor.Rendering.IEditorRenderingHost.currentFrameIndex { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L86) | Gets the current output frame index shared by Editor views. | — | — | +| [`Inno.Editor.Rendering.EditorViewportOutput Inno.Editor.Rendering.IEditorRenderingHost.Submit(Inno.Editor.Rendering.EditorViewportComposition composition)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L97) | Submits or updates one composed offscreen editor viewport.
`composition`: Complete ordered model composition for the current frame. | The current presentation output, which can be warming up for one frame. | — | +| [`void Inno.Editor.Rendering.IEditorRenderingHost.Draw(Inno.Editor.Rendering.EditorViewportOutput output, System.Numerics.Vector2 logicalSize)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L108) | Draws a ready viewport output inside the current ImGui window.
`output`: Output returned by .
`logicalSize`: Destination size in logical UI pixels. | — | — | +| [`void Inno.Editor.Rendering.IEditorRenderingHost.Release(string viewportId)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L119) | Releases one viewport and queues its GPU target for frame-safe destruction.
`viewportId`: Stable viewport identity. | — | — | +| [`void Inno.Editor.Rendering.IEditorRenderingHost.ReleaseAll()`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L124) | Releases every viewport owned by this editor host service. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportLayer`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L130) | Describes one model-neutral layer in an Editor viewport composition. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportLayer.EditorViewportLayer(string contributorId, Inno.Rendering.Assets.RenderPipelineAsset? pipeline, Inno.Rendering.Runtime.RenderFrameData data, int order)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L147) | Creates one immutable model-neutral viewport layer.
`contributorId`: Stable identity of the rendering-model contributor that produced the layer.
`pipeline`: Contributor-selected pipeline, or null for the project default.
`data`: Contributor-defined frame-only data.
`order`: Ascending model-composition order. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportLayer.contributorId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L164) | Gets the stable identity of the rendering-model contributor. | — | — | +| [`public Inno.Rendering.Assets.RenderPipelineAsset? Inno.Editor.Rendering.EditorViewportLayer.pipeline { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L169) | Gets the contributor-selected pipeline, or null for the project default. | — | — | +| [`public Inno.Rendering.Runtime.RenderFrameData Inno.Editor.Rendering.EditorViewportLayer.data { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L174) | Gets contributor-defined frame-only data. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportLayer.order { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L179) | Gets ascending model-composition order. | — | — | +| [`public sealed class Inno.Editor.Rendering.EditorViewportComposition`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L185) | Describes a complete ordered set of rendering-model layers targeting one Editor viewport. | — | — | +| [`public Inno.Editor.Rendering.EditorViewportComposition.EditorViewportComposition(string viewportId, int pixelWidth, int pixelHeight, Inno.Rendering.RenderTextureFormat targetFormat, System.Collections.Generic.IEnumerable layers)`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L207) | Creates an immutable viewport composition.
`viewportId`: Stable panel viewport identity.
`pixelWidth`: Positive target width.
`pixelHeight`: Positive target height.
`targetFormat`: Shared presentation target format required by every layer.
`layers`: Ordered non-empty rendering-model layer collection. | — | — | +| [`public string Inno.Editor.Rendering.EditorViewportComposition.viewportId { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L241) | Gets the stable viewport identity. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportComposition.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L246) | Gets the target width. | — | — | +| [`public int Inno.Editor.Rendering.EditorViewportComposition.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L251) | Gets the target height. | — | — | +| [`public Inno.Rendering.RenderTextureFormat Inno.Editor.Rendering.EditorViewportComposition.targetFormat { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L256) | Gets the shared presentation target format. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Rendering.EditorViewportComposition.layers { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/EditorViewportOutput.cs#L261) | Gets the ordered rendering-model layers. | — | — | +| [`public sealed class Inno.Editor.Rendering.PipelineDocuments : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L18) | Edits Pipeline sources and typed extension settings without changing canonical rendering assets before Save. | — | — | +| [`public Inno.Assets.Pipeline.AssetPipeline Inno.Editor.Rendering.PipelineDocuments.assets { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L26) | Gets the authoritative asset pipeline used by source inspectors. | — | — | +| [`public Inno.Core.Serialization.SerializationRegistry Inno.Editor.Rendering.PipelineDocuments.serialization { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L32) | Gets the current owner serialization registry. | — | — | +| [`public Inno.Editor.Assets.AssetDraftDocuments Inno.Editor.Rendering.PipelineDocuments.drafts { get; }`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L38) | Gets the source draft owner shared by Pipeline inspectors. | — | — | +| [`public System.Guid Inno.Editor.Rendering.PipelineDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L61) | Opens a native Pipeline source in the shared document service, including failed imports.
`path`: Mounted source path. | The persistent source identity used by draft operations. | — | +| [`public Inno.Rendering.Assets.RenderPipelineAsset Inno.Editor.Rendering.PipelineDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L71) | Reads a detached current-generation Pipeline value; its referenced canonical assets must not be mutated.
`assetId`: Open Pipeline identity. | The unsaved native Pipeline value. | — | +| [`public void Inno.Editor.Rendering.PipelineDocuments.Replace(System.Guid assetId, Inno.Rendering.Assets.RenderPipelineAsset candidate, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L84) | Changes a draft through shared History without publishing it to Scene/Game.
`assetId`: Open Pipeline identity.
`candidate`: Detached edited value.
`mergeKey`: The current gesture key, or null for a discrete edit. | — | — | +| [`public void Inno.Editor.Rendering.PipelineDocuments.ReplaceSettings(System.Guid assetId, TSettings settings, string? mergeKey = null) where TSettings : class, Inno.Core.Serialization.ISerializable`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L104) | Captures typed Pipeline settings with this source owner's complete reference and dependency context.
`assetId`: Open Pipeline identity.
`settings`: Detached edited settings.
`mergeKey`: The current gesture key, or null for a discrete edit.
`TSettings`: Current plugin settings contract. | — | — | +| [`public Inno.Core.Serialization.ISerializable? Inno.Editor.Rendering.PipelineDocuments.RestoreSettings(Inno.Rendering.Assets.SerializedRenderExtensionState state)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L126) | Restores detached settings using the current type and serialization generation.
`state`: Neutral settings retaining their stable type identity and properties. | The detached settings, or null when the extension is temporarily unavailable. | — | +| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L145) | Initializes this feature when its owning runtime becomes active.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L152) | Advances this feature using the current runtime state.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Rendering.PipelineDocuments.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs#L159) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | ## 项目依赖 -- [Inno.Editor.Assets](Inno.Editor.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Inspection](Inno.Editor.Inspection.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Runtime](../rendering/Inno.Rendering.Runtime.md):公开引用边界由实际签名核对。 -- [Inno.Core.Mathematics](../core/Inno.Core.Mathematics.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Presentation](../platform/Inno.Adapter.Presentation.md):公开引用边界由实际签名核对。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.References](../references/Inno.References.md):公开引用边界由实际签名核对。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Core.Graphs](../core/Inno.Core.Graphs.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Rendering.Assets](../rendering/Inno.Rendering.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Rendering.Assets.Authoring](../rendering/Inno.Rendering.Assets.Authoring.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Adapter.Presentation` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.References` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Core.Graphs` | 公开签名依赖 | +| `Inno.Core.Identity` | 公开签名依赖 | +| `Inno.Core.Mathematics` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Rendering.Assets.Authoring` | 公开签名依赖 | +| `Inno.Rendering.Assets` | 公开签名依赖 | +| `Inno.Rendering.Runtime` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.Editor.Assets` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Scene.md b/docs/editor/Inno.Editor.Scene.md index 3667e6825..01de5adae 100644 --- a/docs/editor/Inno.Editor.Scene.md +++ b/docs/editor/Inno.Editor.Scene.md @@ -26,6 +26,12 @@ flowchart LR - Scene feature 通过 [Inno.Editor.Core](Inno.Editor.Core.md) 中 `EditorReloadCoordinator` 的中立 participant contract 接入 assembly reload,并自行拥有 Scene migration、Coroutine 清理和 Missing/reload diagnostics;`Inno.Editor.Scripting` 不引用 Scene 项目或 Scene 类型。 - `EditorReloadCoordinator.Register` 返回的 registration lease 强持有 participant,而 Coordinator 只保留弱引用。只要 Workspace 持有 lease,GC 就不能移除 Scene migration;Workspace Dispose 会注销并释放 participant,避免新 TypeCache 激活后仍遗留旧 collectible `Type` 的 Component/System。该所有权契约适用于所有 Editor reload feature,不是 Scene 私有补丁。 +## 已分配源暂时不可用 + +已保存文档的源文件或当前 Asset 候选暂时不可用时,Workspace 保留其 persistent asset identity、已知源路径、内存数据及保存基线,并发布 `SCENE-SOURCE-MISSING` 诊断。此时 `IsDirty` 保守返回 `true`,`TryGetSourcePath` 仍返回原分配路径;不会把 Missing 误改成未分配的新文档。普通 Save 拒绝覆盖不可用的原分配源,显式保存到另一个目录仍使用既有 SaveToDirectory 工作流。 + +相同 identity 的源恢复后自动清除诊断,重新比较真实内容;原本的未保存修改仍保留。后续源重命名、保存和关闭重开沿用统一资产与文档协议,不引入另一套路径解析器或恢复缓存。 + ## 公共 API ### IEditorSceneWorkspace @@ -144,56 +150,17 @@ EditorScripts 显式 `using InnoEditor.Scene;` 后只看到 `IEditorSceneWorkspa ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Scene.EditorSceneWorkspaceFactory` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Editor.Scene.EditorSceneWorkspaceHost Inno.Editor.Scene.EditorSceneWorkspaceFactory.Create(Inno.Runtime.RuntimeSession runtimeSession, Inno.Assets.Pipeline.AssetPipeline assets, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Logging.LogRouter logs, Inno.Editor.Interactions.IEditorSelectionCoordinator? selection = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceFactory.cs#L44) | Creates an unattached workspace over explicitly owned Edit-session services.
返回:A host that owns both the document and Play Mode interfaces of the workspace.
`runtimeSession`:The Edit session that owns the workspace scene world.
`assets`:The authoring asset pipeline used for scene and prefab documents.
`types`:The host type catalog used for scene extension generations.
`serialization`:The host serialization registry used for scene snapshots.
`logs`:The application log router used for workspace lifecycle diagnostics.
`selection`:The optional selection coordinator used by the standalone document workspace. | — | `System.ArgumentNullException`:Thrown when a required owner is null. | -| [`public static class Inno.Editor.Scene.EditorSceneWorkspaceFactory`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceFactory.cs#L15) | Creates explicitly owned editor scene workspaces for embedded editor hosts and command-line tooling. | — | — | - -### `Inno.Editor.Scene.EditorSceneWorkspaceHost` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Scene.EditorSceneWorkspaceHost.Dispose()`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L58) | Releases the workspace and any isolated scene session that it still owns. | — | — | -| [`public Inno.Editor.Scene.IEditorGameScenePresentation Inno.Editor.Scene.EditorSceneWorkspaceHost.gamePresentation { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L48) | Gets the Edit-or-Play rendering presentation boundary owned by this host. | — | — | -| [`public Inno.Editor.Scene.IEditorScenePlayMode Inno.Editor.Scene.EditorSceneWorkspaceHost.playMode { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L53) | Gets the isolated Play Mode scene-session boundary owned by this host. | — | — | -| [`public Inno.Editor.Scene.IEditorSceneWorkspace Inno.Editor.Scene.EditorSceneWorkspaceHost.workspace { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L43) | Gets the Edit-or-Play scene presentation and persistence boundary owned by this host. | — | — | -| [`public sealed class Inno.Editor.Scene.EditorSceneWorkspaceHost : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L17) | Owns an editor scene workspace created outside the attribute-discovered editor application. | — | — | - -### `Inno.Editor.Scene.IEditorGameScenePresentation` - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.References.ContentReadScope Inno.Editor.Scene.IEditorGameScenePresentation.Capture()`](../../src/composition/editor/features/Inno.Editor.Scene/Presentation/IEditorGameScenePresentation.cs#L19) | Captures one coherent game-scene presentation for the current Editor frame.
返回:A caller-owned identity scope for the Edit scenes while not playing and the isolated Play scenes after Play Mode commits. Dispose it after consumption; it retains no scene instances and stale identities never resolve to a replacement session or generation. | — | — | -| [`public interface Inno.Editor.Scene.IEditorGameScenePresentation`](../../src/composition/editor/features/Inno.Editor.Scene/Presentation/IEditorGameScenePresentation.cs#L9) | Supplies the scene set that represents the game to Editor viewport consumers without exposing runtime-session ownership. | — | — | - -### `Inno.Editor.Scene.IEditorScenePlayMode` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`System.IDisposable Inno.Editor.Scene.IEditorScenePlayMode.BeginPlayMode(Inno.Runtime.RuntimeSession runtimeSession)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorScenePlayMode.cs#L30) | Captures the editable scene set and materializes independent runtime copies in the supplied session.
返回:A lease that presents the runtime copies to editor features, prevents their persistence, and restores the Edit presentation when disposed.
`runtimeSession`:The isolated Play Mode session that receives the immutable start snapshot and owns editor-side operations performed against the runtime copies. | — | `System.ArgumentNullException`:Thrown when runtimeSession is null.
`System.InvalidOperationException`:Thrown when another Play Mode lease is active, the target world is not empty, or the snapshot cannot be materialized atomically. | -| [`public interface Inno.Editor.Scene.IEditorScenePlayMode`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorScenePlayMode.cs#L10) | Creates isolated runtime scene sessions from the current editable scene set. | — | — | - -### `Inno.Editor.Scene.IEditorSceneWorkspace` - @@ -206,24 +173,6 @@ EditorScripts 显式 `using InnoEditor.Scene;` 后只看到 `IEditorSceneWorkspa - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.CanEdit(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L37) | Gets whether a loaded scene may be changed in the current Edit or isolated Play world.
返回:true for a writable Project scene or transient scene in the presented world. Changes made during Play remain in the runtime copy and are discarded on stop.
`scene`:The scene to inspect. | — | — | -| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.IsDirty(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L63) | Gets whether an Edit scene contains unsaved serialized changes.
返回:true when an Edit scene differs from its saved baseline; runtime copies always return false because they cannot be persisted.
`scene`:The scene to inspect. | — | — | -| [`Inno.Scene.GameScene Inno.Editor.Scene.IEditorSceneWorkspace.Open(string relativePath)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L77) | Opens a scene asset additively as the active editor scene.
返回:The existing loaded instance or newly loaded scene.
`relativePath`:The source-relative scene asset path. | — | `System.InvalidOperationException`:Thrown while Play Mode runtime copies are active or when the asset cannot be opened. | -| [`string Inno.Editor.Scene.IEditorSceneWorkspace.Save(Inno.Scene.GameScene scene, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L94) | Saves a scene to its existing path or into a fallback directory.
返回:The saved source-relative path.
`scene`:The scene to save.
`currentDirectory`:The fallback asset directory for an unsaved scene. | — | `System.InvalidOperationException`:Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | -| [`string Inno.Editor.Scene.IEditorSceneWorkspace.SavePrefab(Inno.Scene.GameObject gameObject, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L134) | Captures a game object subtree as a prefab in the requested directory.
返回:The saved source-relative path.
`gameObject`:The prefab root.
`currentDirectory`:The target asset directory. | — | `System.InvalidOperationException`:Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | -| [`string Inno.Editor.Scene.IEditorSceneWorkspace.SaveToDirectory(Inno.Scene.GameScene scene, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L114) | Saves a scene into the requested asset directory.
返回:The saved source-relative path.
`scene`:The scene to save.
`currentDirectory`:The target asset directory. | — | `System.InvalidOperationException`:Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | -| [`void Inno.Editor.Scene.IEditorSceneWorkspace.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L51) | Makes one presented scene active without changing scene order.
`scene`:The loaded scene to activate. | — | `System.ArgumentNullException`:Thrown when scene is null.
`System.InvalidOperationException`:Thrown when the scene is not loaded by this workspace. | -| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.TryGetSourcePath(Inno.Scene.GameScene scene, out string relativePath)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L151) | Tries to get the current source-relative asset path of a saved scene.
返回:true when the scene is backed by a scene asset.
`scene`:The scene whose path is requested.
`relativePath`:The saved path when available. | — | — | -| [`Inno.Scene.GameScene? Inno.Editor.Scene.IEditorSceneWorkspace.activeScene { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L20) | Gets the active scene from the currently presented Edit or Play world. | — | — | -| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.canPersist { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L25) | Gets whether the currently presented scenes are authoring documents that may be persisted. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Editor.Scene.IEditorSceneWorkspace.scenes { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L15) | Gets the Edit scenes outside Play Mode or the isolated runtime copies while Play Mode is active. | — | — | -| [`public interface Inno.Editor.Scene.IEditorSceneWorkspace`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L10) | Exposes the active Edit or Play scene presentation and the persistence operations available to it. | — | — | - -### `Inno.Editor.Scene.SceneEdits` - @@ -234,8 +183,8 @@ EditorScripts 显式 `using InnoEditor.Scene;` 后只看到 `IEditorSceneWorkspa - - + + @@ -248,61 +197,90 @@ EditorScripts 显式 `using InnoEditor.Scene;` 后只看到 `IEditorSceneWorkspa - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.GameComponent Inno.Editor.Scene.SceneEdits.AddComponent(Inno.Scene.GameObject owner, System.Type componentType, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L33) | Adds one component and records its identity, stable type, index, and persistent properties.
返回:The newly attached component.
`owner`:The live GameObject receiving the component.
`componentType`:The concrete component type to create.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public Inno.Scene.GameSystem Inno.Editor.Scene.SceneEdits.AddSystem(Inno.Scene.GameScene scene, System.Type systemType, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L283) | Adds one scene system and records its identity, stable type, index, and persistent properties.
返回:The newly registered system.
`scene`:The loaded scene receiving the system.
`systemType`:The concrete system type to create.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.CanEdit(Inno.Scene.EngineObject target)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L63) | Gets whether a scene object belongs to an editable presented scene.
返回:true when its owning scene is writable.
`target`:The scene object to inspect. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.CanEdit(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L52) | Gets whether the scene is editable in the current Edit or isolated Play world.
返回:true when scene commands may change this scene. Play changes use a temporary history branch and are discarded when the Play session ends.
`scene`:The loaded scene to inspect. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.ChangeHierarchy(Inno.Scene.GameObject gameObject, System.Action mutation, string historyName = "Move GameObject", System.Collections.Generic.IReadOnlyCollection? relatedObjects = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Hierarchy.cs#L80) | Applies a hierarchy mutation and records only the affected parent and sibling-index tuples.
返回:true when at least one placement changed.
`gameObject`:The primary GameObject being moved.
`mutation`:The hierarchy mutation to execute through the world-owned operation context.
`historyName`:The user-facing history entry name.
`relatedObjects`:Additional objects whose placements the mutation may change, such as promoted children. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.ChangeProperty(Inno.Scene.EngineObject target, string propertyName, System.Action mutation, string historyName, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L210) | Applies a mutation to one serializable scene property and records only its before and after values.
返回:true when the property value changed and a history entry was recorded.
`target`:The live scene object containing the root serialized property.
`propertyName`:The exact root serialized member key.
`mutation`:The mutation that assigns the new value.
`historyName`:The user-facing history entry name.
`mergeKey`:An optional stable key for coalescing adjacent continuous edits. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.CloseScene(Inno.Scene.GameScene scene, string historyName = "Close Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L130) | Closes one loaded scene without deleting its source asset and records a reversible document change.
返回:true when the scene was closed and recorded.
`scene`:The loaded scene to close.
`historyName`:The user-facing history entry name. | — | — | -| [`public Inno.Scene.GameObject Inno.Editor.Scene.SceneEdits.CreateGameObject(Inno.Scene.GameScene scene, Inno.Scene.Components.Transform? parent = null, string historyName = "Create GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L33) | Creates a GameObject, optionally parents it, and records only the new subtree state.
返回:The newly created GameObject.
`scene`:The loaded scene that will own the object.
`parent`:The optional parent transform.
`historyName`:The user-facing history entry name. | — | — | -| [`public Inno.Scene.GameScene Inno.Editor.Scene.SceneEdits.CreateScene(string historyName = "Create Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L87) | Creates an additive scene and records a reversible document change.
返回:The newly created active scene.
`historyName`:The user-facing history entry name. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.DeleteGameObject(Inno.Scene.GameObject gameObject, string historyName = "Delete GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L150) | Deletes a GameObject subtree and records only that subtree plus incoming serialized references.
返回:true when the subtree was deleted and recorded.
`gameObject`:The live subtree root to delete.
`historyName`:The user-facing history entry name. | — | — | -| [`public Inno.Scene.GameObject Inno.Editor.Scene.SceneEdits.InstantiatePrefab(Inno.Scene.PrefabAsset prefab, Inno.Scene.GameScene scene, Inno.Scene.Components.Transform? parent = null, string historyName = "Instantiate Prefab")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L91) | Instantiates a prefab into a loaded scene and records the created subtree as one reversible edit.
返回:The instantiated prefab root.
`prefab`:The imported prefab asset to instantiate.
`scene`:The loaded scene that will own the instance.
`parent`:The optional parent transform for the instantiated root.
`historyName`:The user-facing history entry name. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.RemoveComponent(Inno.Scene.GameComponent component, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L90) | Removes the component from its scene owner and records a reversible serialized history change.
返回:true when the component was removed and recorded.
`component`:The attached non-Transform component to remove.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public bool Inno.Editor.Scene.SceneEdits.RemoveSystem(Inno.Scene.GameScene scene, Inno.Scene.GameSystem system, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L341) | Removes the system from its scene and records a reversible serialized history change.
返回:true when the system was removed and recorded.
`scene`:The scene currently owning the system.
`system`:The registered system to remove.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.RenameGameObject(Inno.Scene.GameObject gameObject, string name, string historyName = "Rename GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L61) | Renames a live GameObject and records the two display strings.
`gameObject`:The live GameObject to rename.
`name`:The new display name.
`historyName`:The user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.RenameScene(Inno.Scene.GameScene scene, string name, string historyName = "Rename Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L30) | Renames a loaded scene and records the two display strings.
`scene`:The loaded scene to rename.
`name`:The new display name.
`historyName`:The user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.ResetComponent(Inno.Scene.GameComponent component, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L165) | Resets one component and records its compact property state before and after Reset.
`component`:The attached component to reset.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.ResetSystem(Inno.Scene.GameScene scene, Inno.Scene.GameSystem system, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L419) | Resets one scene system and records its compact property state before and after Reset.
`scene`:The scene currently owning the system.
`system`:The registered system to reset.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.SetComponentIndex(Inno.Scene.GameComponent component, int componentIndex, string historyName = "Move Component")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L225) | Moves an attached component and records only its two attachment indices.
`component`:The attached component to move, including the mandatory Transform.
`componentIndex`:The requested attachment index.
`historyName`:The user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectActive(Inno.Scene.GameObject gameObject, bool active, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L92) | Changes the explicit active state of a GameObject and records the two Boolean values.
`gameObject`:The live GameObject whose active state should change.
`active`:The requested explicit active state.
`historyName`:An optional user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectLayer(Inno.Scene.GameObject gameObject, Inno.Scene.Layers.GameLayer layer, string historyName = "Set GameObject Layer")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L169) | Changes the layer of a live GameObject and records the two stable numeric layer slots.
`gameObject`:The live GameObject whose layer should change.
`layer`:The requested project layer slot.
`historyName`:The user-facing history entry name. | — | `System.ArgumentNullException`:Thrown when gameObject is null.
`System.ArgumentException`:Thrown when historyName is empty. | -| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectTag(Inno.Scene.GameObject gameObject, string tag, string historyName = "Set GameObject Tag")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L130) | Changes the tag of a live GameObject and records the two ordinal tag strings.
`gameObject`:The live GameObject whose tag should change.
`tag`:The requested non-empty tag.
`historyName`:The user-facing history entry name. | — | `System.ArgumentNullException`:Thrown when gameObject is null.
`System.ArgumentException`:Thrown when tag or historyName is empty. | -| [`public void Inno.Editor.Scene.SceneEdits.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex, string historyName = "Reorder Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Hierarchy.cs#L30) | Moves a loaded scene to a hierarchy index and records the two integer positions.
`scene`:The loaded scene to reorder.
`sceneIndex`:The requested hierarchy index.
`historyName`:The user-facing history entry name. | — | — | -| [`public void Inno.Editor.Scene.SceneEdits.SetSystemIndex(Inno.Scene.GameScene scene, Inno.Scene.GameSystem system, int systemIndex, string historyName = "Move System")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L483) | Moves a registered system and records only its two display indices.
`scene`:The scene currently owning the system.
`system`:The registered system to move.
`systemIndex`:The requested display index.
`historyName`:The user-facing history entry name. | — | — | -| [`public sealed class Inno.Editor.Scene.SceneEdits : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L20) | Applies scene-document mutations and records compact, reload-safe inverse data in editor history. | — | — | - -### `Inno.Editor.Scene.SceneHierarchyEdit` - + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public void Inno.Editor.Scene.SceneHierarchyEdit.MoveToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneHierarchyEdit.cs#L34) | Moves a live GameObject subtree into another scene owned by the current editor world.
`gameObject`:The live root object to move.
`destination`:The loaded destination scene. | — | `System.ArgumentNullException`:Thrown when an argument is null.
`System.InvalidOperationException`:Thrown when either scene is not loaded by the editor world. | +| [`public static class Inno.Editor.Scene.EditorSceneWorkspaceFactory`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceFactory.cs#L15) | Creates explicitly owned editor scene workspaces for embedded editor hosts and command-line tooling. | — | — | +| [`public static Inno.Editor.Scene.EditorSceneWorkspaceHost Inno.Editor.Scene.EditorSceneWorkspaceFactory.Create(Inno.Runtime.RuntimeSession runtimeSession, Inno.Assets.Pipeline.AssetPipeline assets, Inno.Extensibility.Types.TypeCatalog types, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Logging.LogRouter logs, Inno.Editor.Interactions.IEditorSelectionCoordinator? selection = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceFactory.cs#L44) | Creates an unattached workspace over explicitly owned Edit-session services.
`runtimeSession`: The Edit session that owns the workspace scene world.
`assets`: The authoring asset pipeline used for scene and prefab documents.
`types`: The host type catalog used for scene extension generations.
`serialization`: The host serialization registry used for scene snapshots.
`logs`: The application log router used for workspace lifecycle diagnostics.
`selection`: The optional selection coordinator used by the standalone document workspace. | A host that owns both the document and Play Mode interfaces of the workspace. | `System.ArgumentNullException`: Thrown when a required owner is . | +| [`public sealed class Inno.Editor.Scene.EditorSceneWorkspaceHost : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L17) | Owns an editor scene workspace created outside the attribute-discovered editor application. | — | — | +| [`public Inno.Editor.Scene.IEditorSceneWorkspace Inno.Editor.Scene.EditorSceneWorkspaceHost.workspace { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L43) | Gets the Edit-or-Play scene presentation and persistence boundary owned by this host. | — | — | +| [`public Inno.Editor.Scene.IEditorGameScenePresentation Inno.Editor.Scene.EditorSceneWorkspaceHost.gamePresentation { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L48) | Gets the Edit-or-Play rendering presentation boundary owned by this host. | — | — | +| [`public Inno.Editor.Scene.IEditorScenePlayMode Inno.Editor.Scene.EditorSceneWorkspaceHost.playMode { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L53) | Gets the isolated Play Mode scene-session boundary owned by this host. | — | — | +| [`public void Inno.Editor.Scene.EditorSceneWorkspaceHost.Dispose()`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspaceHost.cs#L58) | Releases the workspace and any isolated scene session that it still owns. | — | — | +| [`public interface Inno.Editor.Scene.IEditorScenePlayMode`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorScenePlayMode.cs#L10) | Creates isolated runtime scene sessions from the current editable scene set. | — | — | +| [`System.IDisposable Inno.Editor.Scene.IEditorScenePlayMode.BeginPlayMode(Inno.Runtime.RuntimeSession runtimeSession)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorScenePlayMode.cs#L30) | Captures the editable scene set and materializes independent runtime copies in the supplied session.
`runtimeSession`: The isolated Play Mode session that receives the immutable start snapshot and owns editor-side operations performed against the runtime copies. | A lease that presents the runtime copies to editor features, prevents their persistence, and restores the Edit presentation when disposed. | `System.ArgumentNullException`: Thrown when is null.
`System.InvalidOperationException`: Thrown when another Play Mode lease is active, the target world is not empty, or the snapshot cannot be materialized atomically. | +| [`public interface Inno.Editor.Scene.IEditorSceneWorkspace`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L10) | Exposes the active Edit or Play scene presentation and the persistence operations available to it. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Editor.Scene.IEditorSceneWorkspace.scenes { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L15) | Gets the Edit scenes outside Play Mode or the isolated runtime copies while Play Mode is active. | — | — | +| [`Inno.Scene.GameScene? Inno.Editor.Scene.IEditorSceneWorkspace.activeScene { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L20) | Gets the active scene from the currently presented Edit or Play world. | — | — | +| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.canPersist { get; }`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L25) | Gets whether the currently presented scenes are authoring documents that may be persisted. | — | — | +| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.CanEdit(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L37) | Gets whether a loaded scene may be changed in the current Edit or isolated Play world.
`scene`: The scene to inspect. | for a writable Project scene or transient scene in the presented world. Changes made during Play remain in the runtime copy and are discarded on stop. | — | +| [`void Inno.Editor.Scene.IEditorSceneWorkspace.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L51) | Makes one presented scene active without changing scene order.
`scene`: The loaded scene to activate. | — | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the scene is not loaded by this workspace. | +| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.IsDirty(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L63) | Gets whether an Edit scene contains unsaved serialized changes.
`scene`: The scene to inspect. | when an Edit scene differs from its saved baseline; runtime copies always return because they cannot be persisted. | — | +| [`Inno.Scene.GameScene Inno.Editor.Scene.IEditorSceneWorkspace.Open(string relativePath)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L77) | Opens a scene asset additively as the active editor scene.
`relativePath`: The source-relative scene asset path. | The existing loaded instance or newly loaded scene. | `System.InvalidOperationException`: Thrown while Play Mode runtime copies are active or when the asset cannot be opened. | +| [`string Inno.Editor.Scene.IEditorSceneWorkspace.Save(Inno.Scene.GameScene scene, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L94) | Saves a scene to its existing path or into a fallback directory.
`scene`: The scene to save.
`currentDirectory`: The fallback asset directory for an unsaved scene. | The saved source-relative path. | `System.InvalidOperationException`: Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | +| [`string Inno.Editor.Scene.IEditorSceneWorkspace.SaveToDirectory(Inno.Scene.GameScene scene, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L114) | Saves a scene into the requested asset directory.
`scene`: The scene to save.
`currentDirectory`: The target asset directory. | The saved source-relative path. | `System.InvalidOperationException`: Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | +| [`string Inno.Editor.Scene.IEditorSceneWorkspace.SavePrefab(Inno.Scene.GameObject gameObject, string currentDirectory)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L134) | Captures a game object subtree as a prefab in the requested directory.
`gameObject`: The prefab root.
`currentDirectory`: The target asset directory. | The saved source-relative path. | `System.InvalidOperationException`: Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. | +| [`bool Inno.Editor.Scene.IEditorSceneWorkspace.TryGetSourcePath(Inno.Scene.GameScene scene, out string relativePath)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs#L151) | Tries to get the current source-relative asset path of a saved scene.
`scene`: The scene whose path is requested.
`relativePath`: The saved path when available. | when the scene is backed by a scene asset. | — | +| [`public sealed class Inno.Editor.Scene.SceneEdits : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L20) | Applies scene-document mutations and records compact, reload-safe inverse data in editor history. | — | — | +| [`public bool Inno.Editor.Scene.SceneEdits.CanEdit(Inno.Scene.GameScene scene)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L52) | Gets whether the scene is editable in the current Edit or isolated Play world.
`scene`: The loaded scene to inspect. | when scene commands may change this scene. Play changes use a temporary history branch and are discarded when the Play session ends. | — | +| [`public bool Inno.Editor.Scene.SceneEdits.CanEdit(Inno.Scene.EngineObject target)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L63) | Gets whether a scene object belongs to an editable presented scene.
`target`: The scene object to inspect. | when its owning scene is writable. | — | +| [`public Inno.Scene.GameScene Inno.Editor.Scene.SceneEdits.CreateScene(string historyName = "Create Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L87) | Creates an additive scene and records a reversible document change.
`historyName`: The user-facing history entry name. | The newly created active scene. | — | +| [`public bool Inno.Editor.Scene.SceneEdits.CloseScene(Inno.Scene.GameScene scene, string historyName = "Close Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.cs#L130) | Closes one loaded scene without deleting its source asset and records a reversible document change.
`scene`: The loaded scene to close.
`historyName`: The user-facing history entry name. | when the scene was closed and recorded. | — | +| [`public Inno.Scene.GameComponent Inno.Editor.Scene.SceneEdits.AddComponent(Inno.Scene.GameObject owner, System.Type componentType, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L31) | Adds one component and records its identity, stable type, index, and persistent properties.
`owner`: The live GameObject receiving the component.
`componentType`: The concrete component type to create.
`historyName`: An optional user-facing history entry name. | The newly attached component. | — | +| [`public bool Inno.Editor.Scene.SceneEdits.RemoveComponent(Inno.Scene.GameComponent component, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L87) | Removes the component from its scene owner and records a reversible serialized history change.
`component`: The attached non-Transform component to remove.
`historyName`: An optional user-facing history entry name. | when the component was removed and recorded. | — | +| [`public void Inno.Editor.Scene.SceneEdits.ResetComponent(Inno.Scene.GameComponent component, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L153) | Resets one component and records its compact property state before and after Reset.
`component`: The attached component to reset.
`historyName`: An optional user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.SetComponentIndex(Inno.Scene.GameComponent component, int componentIndex, string historyName = "Move Component")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L211) | Moves an attached component and records only its two attachment indices.
`component`: The attached component to move, including the mandatory Transform.
`componentIndex`: The requested attachment index.
`historyName`: The user-facing history entry name. | — | — | +| [`public Inno.Scene.GameSystem Inno.Editor.Scene.SceneEdits.AddSystem(Inno.Scene.GameScene scene, System.Type systemType, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L273) | Adds one scene system and records its identity, stable type, index, and persistent properties.
`scene`: The loaded scene receiving the system.
`systemType`: The concrete system type to create.
`historyName`: An optional user-facing history entry name. | The newly registered system. | — | +| [`public bool Inno.Editor.Scene.SceneEdits.RemoveSystem(Inno.Scene.GameSystem system, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L327) | Removes the system from its scene and records a reversible serialized history change.
`system`: The registered system to remove.
`historyName`: An optional user-facing history entry name. | when the system was removed and recorded. | — | +| [`public void Inno.Editor.Scene.SceneEdits.ResetSystem(Inno.Scene.GameSystem system, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L394) | Resets one scene system and records its compact property state before and after Reset.
`system`: The registered system to reset.
`historyName`: An optional user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.SetSystemIndex(Inno.Scene.GameScene scene, Inno.Scene.GameSystem system, int systemIndex, string historyName = "Move System")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs#L455) | Moves a registered system and records only its two display indices.
`scene`: The scene currently owning the system.
`system`: The registered system to move.
`systemIndex`: The requested display index.
`historyName`: The user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex, string historyName = "Reorder Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Hierarchy.cs#L30) | Moves a loaded scene to a hierarchy index and records the two integer positions.
`scene`: The loaded scene to reorder.
`sceneIndex`: The requested hierarchy index.
`historyName`: The user-facing history entry name. | — | — | +| [`public bool Inno.Editor.Scene.SceneEdits.ChangeHierarchy(Inno.Scene.GameObject gameObject, System.Action mutation, string historyName = "Move GameObject", System.Collections.Generic.IReadOnlyCollection? relatedObjects = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Hierarchy.cs#L80) | Applies a hierarchy mutation and records only the affected parent and sibling-index tuples.
`gameObject`: The primary GameObject being moved.
`mutation`: The hierarchy mutation to execute through the world-owned operation context.
`historyName`: The user-facing history entry name.
`relatedObjects`: Additional objects whose placements the mutation may change, such as promoted children. | when at least one placement changed. | — | +| [`public Inno.Scene.GameObject Inno.Editor.Scene.SceneEdits.CreateGameObject(Inno.Scene.GameScene scene, Inno.Scene.Components.Transform? parent = null, string historyName = "Create GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L33) | Creates a GameObject, optionally parents it, and records only the new subtree state.
`scene`: The loaded scene that will own the object.
`parent`: The optional parent transform.
`historyName`: The user-facing history entry name. | The newly created GameObject. | — | +| [`public Inno.Scene.GameObject Inno.Editor.Scene.SceneEdits.InstantiatePrefab(Inno.Scene.PrefabAsset prefab, Inno.Scene.GameScene scene, Inno.Scene.Components.Transform? parent = null, string historyName = "Instantiate Prefab")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L91) | Instantiates a prefab into a loaded scene and records the created subtree as one reversible edit.
`prefab`: The imported prefab asset to instantiate.
`scene`: The loaded scene that will own the instance.
`parent`: The optional parent transform for the instantiated root.
`historyName`: The user-facing history entry name. | The instantiated prefab root. | — | +| [`public bool Inno.Editor.Scene.SceneEdits.DeleteGameObject(Inno.Scene.GameObject gameObject, string historyName = "Delete GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Objects.cs#L150) | Deletes a GameObject subtree and records only that subtree plus incoming serialized references.
`gameObject`: The live subtree root to delete.
`historyName`: The user-facing history entry name. | when the subtree was deleted and recorded. | — | +| [`public void Inno.Editor.Scene.SceneEdits.RenameScene(Inno.Scene.GameScene scene, string name, string historyName = "Rename Scene")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L30) | Renames a loaded scene and records the two display strings.
`scene`: The loaded scene to rename.
`name`: The new display name.
`historyName`: The user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.RenameGameObject(Inno.Scene.GameObject gameObject, string name, string historyName = "Rename GameObject")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L61) | Renames a live GameObject and records the two display strings.
`gameObject`: The live GameObject to rename.
`name`: The new display name.
`historyName`: The user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectActive(Inno.Scene.GameObject gameObject, bool active, string? historyName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L92) | Changes the explicit active state of a GameObject and records the two Boolean values.
`gameObject`: The live GameObject whose active state should change.
`active`: The requested explicit active state.
`historyName`: An optional user-facing history entry name. | — | — | +| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectTag(Inno.Scene.GameObject gameObject, string tag, string historyName = "Set GameObject Tag")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L130) | Changes the tag of a live GameObject and records the two ordinal tag strings.
`gameObject`: The live GameObject whose tag should change.
`tag`: The requested non-empty tag.
`historyName`: The user-facing history entry name. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when or is empty. | +| [`public void Inno.Editor.Scene.SceneEdits.SetGameObjectLayer(Inno.Scene.GameObject gameObject, Inno.Scene.Layers.GameLayer layer, string historyName = "Set GameObject Layer")`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L169) | Changes the layer of a live GameObject and records the two stable numeric layer slots.
`gameObject`: The live GameObject whose layer should change.
`layer`: The requested project layer slot.
`historyName`: The user-facing history entry name. | — | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when is empty. | +| [`public bool Inno.Editor.Scene.SceneEdits.ChangeProperty(Inno.Scene.EngineObject target, string propertyName, System.Action mutation, string historyName, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Properties.cs#L210) | Applies a mutation to one serializable scene property and records only its before and after values.
`target`: The live scene object containing the root serialized property.
`propertyName`: The exact root serialized member key.
`mutation`: The mutation that assigns the new value.
`historyName`: The user-facing history entry name.
`mergeKey`: An optional stable key for coalescing adjacent continuous edits. | when the property value changed and a history entry was recorded. | — | +| [`public void Inno.Editor.Scene.SceneEdits.DeleteGameObjects(System.Collections.Generic.IReadOnlyList targets)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs#L22) | Deletes selected subtrees atomically, omitting descendants already covered by a selected ancestor.
`targets`: Live objects to delete; every owner is validated before the first mutation. | — | `System.InvalidOperationException`: An owner is unavailable, read-only, or rollback fails. | +| [`public bool Inno.Editor.Scene.SceneEdits.ChangeProperties(System.Collections.Generic.IReadOnlyList targets, string propertyName, System.Action mutation, string historyName, string? mergeKey = null, string? componentName = null)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs#L72) | Applies one primary property edit to a selection using owner-resolved serialized values and one atomic History transaction.
`targets`: Ordered compatible scene objects; the first object supplies the edited value.
`propertyName`: The common root serialized property.
`mutation`: Changes the primary value synchronously; no callback is retained.
`historyName`: The user-facing batch edit name.
`mergeKey`: An optional active gesture identity; null keeps discrete edits separate.
`componentName`: An optional public scalar value component; Euler axes use euler.x, euler.y or euler.z. | True when at least one object changed; false when all values already matched. | `System.ArgumentException`: The selection is empty or a required common property is absent.
`System.InvalidOperationException`: An owner is unavailable, read-only, or rollback fails. | +| [`public bool Inno.Editor.Scene.SceneEdits.HaveSamePropertyValues(System.Collections.Generic.IReadOnlyList targets, string propertyName)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs#L141) | Compares common serialized property values using the complete owner serialization context.
`targets`: Live compatible scene objects to compare.
`propertyName`: The required common serialized member. | True when every encoded value matches, including an empty selection. | `System.InvalidOperationException`: A selected object does not expose the requested property. | +| [`public void Inno.Editor.Scene.SceneEdits.MoveGameObjects(System.Collections.Generic.IReadOnlyList targets, Inno.Scene.GameScene destination, Inno.Scene.GameObject? parent = null, int insertionIndex = 2147483647)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs#L184) | Moves minimal selected roots together, preserving descendants and recording only affected placements.
`targets`: Selected live objects in their required relative order.
`destination`: The loaded editable destination scene.
`parent`: The new parent, or null for scene roots.
`insertionIndex`: The insertion index after removing the selected roots; the default appends them. | — | `System.InvalidOperationException`: An owner is read-only, unavailable, or the move would form a cycle.
`System.ArgumentException`: The parent belongs to a different scene. | | [`public sealed class Inno.Editor.Scene.SceneHierarchyEdit`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneHierarchyEdit.cs#L10) | Exposes world-owned hierarchy operations inside one atomic scene history mutation. | — | — | +| [`public void Inno.Editor.Scene.SceneHierarchyEdit.MoveToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/composition/editor/features/Inno.Editor.Scene/Documents/SceneHierarchyEdit.cs#L34) | Moves a live GameObject subtree into another scene owned by the current editor world.
`gameObject`: The live root object to move.
`destination`: The loaded destination scene. | — | `System.ArgumentNullException`: Thrown when an argument is .
`System.InvalidOperationException`: Thrown when either scene is not loaded by the editor world. | +| [`public interface Inno.Editor.Scene.IEditorGameScenePresentation`](../../src/composition/editor/features/Inno.Editor.Scene/Presentation/IEditorGameScenePresentation.cs#L9) | Supplies the scene set that represents the game to Editor viewport consumers without exposing runtime-session ownership. | — | — | +| [`Inno.References.ContentReadScope Inno.Editor.Scene.IEditorGameScenePresentation.Capture()`](../../src/composition/editor/features/Inno.Editor.Scene/Presentation/IEditorGameScenePresentation.cs#L19) | Captures one coherent game-scene presentation for the current Editor frame. | A caller-owned identity scope for the Edit scenes while not playing and the isolated Play scenes after Play Mode commits. Dispose it after consumption; it retains no scene instances and stale identities never resolve to a replacement session or generation. | — | ## 项目依赖 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Coroutines](../core/Inno.Core.Coroutines.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scene.Assets](../scene/Inno.Scene.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Scene](../scene/Inno.Scene.md):公开引用边界由实际签名核对。 -- [Inno.Runtime](../runtime/Inno.Runtime.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):公开引用边界由实际签名核对。 -- [Inno.References](../references/Inno.References.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.Core.Coroutines` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Scene.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Scene` | 公开签名依赖 | +| `Inno.Runtime` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Core.Logging` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Editor.Interactions` | 公开签名依赖 | +| `Inno.References` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Scripting.md b/docs/editor/Inno.Editor.Scripting.md index 24742ec61..2b523c8b5 100644 --- a/docs/editor/Inno.Editor.Scripting.md +++ b/docs/editor/Inno.Editor.Scripting.md @@ -250,62 +250,28 @@ Serialized state 使用逐成员兼容迁移:成员类型保持兼容时恢复 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Scripting.EditorScriptCompilationState` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Scripting.EditorScriptCompilationState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L18) | A requested script generation is compiling, activating, or completing unload verification. | — | — | -| [`Inno.Editor.Scripting.EditorScriptCompilationState.Failed = 3`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L28) | Compilation, activation, or generation retirement failed, so game simulation must not start. | — | — | -| [`Inno.Editor.Scripting.EditorScriptCompilationState.Initializing = 0`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L13) | The script service has not produced its initial active generation. | — | — | -| [`Inno.Editor.Scripting.EditorScriptCompilationState.Ready = 2`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L23) | The most recent compilation succeeded, its generation is active, and retired generations passed GC verification. | — | — | -| [`public enum Inno.Editor.Scripting.EditorScriptCompilationState : int`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L8) | Identifies whether the active project script generation can safely start game simulation. | — | — | - -### `Inno.Editor.Scripting.IEditorScriptCompilation` - + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Editor.Scripting.IScriptCompilationTicket Inno.Editor.Scripting.IEditorScriptCompilation.RequestCompilation()`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L43) | Queues a fresh cache-aware compilation for a build or simulation workflow.
返回:A ticket bound to the exact request. Success requires activation and completed retirement verification; a faulted host returns a failed ticket and requires restart. | — | — | -| [`Inno.Editor.Scripting.IScriptCompilationTicket? Inno.Editor.Scripting.IEditorScriptCompilation.currentTicket { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L48) | Gets the most recently requested compilation ticket, or null when no explicit request exists. | — | — | -| [`Inno.Scripting.Compiler.ScriptCompilationResult? Inno.Editor.Scripting.IEditorScriptCompilation.lastCompilation { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L63) | Gets the most recently completed compilation, or null before the first attempt completes. | — | — | -| [`Inno.Editor.Scripting.EditorScriptCompilationState Inno.Editor.Scripting.IEditorScriptCompilation.state { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L53) | Gets the current script-generation readiness state. | — | — | -| [`string Inno.Editor.Scripting.IEditorScriptCompilation.status { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L58) | Gets a human-readable description of current compiler work or readiness. | — | — | -| [`public interface Inno.Editor.Scripting.IEditorScriptCompilation`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L34) | Exposes the minimal script-generation readiness contract required by editor workflows. | — | — | - -### `Inno.Editor.Scripting.IScriptCompilationTicket` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`bool Inno.Editor.Scripting.IScriptCompilationTicket.isCompleted { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L33) | Gets whether no further state transition is valid for this request. | — | — | -| [`long Inno.Editor.Scripting.IScriptCompilationTicket.requestId { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L13) | Gets the producer-local monotonic identifier of this request. | — | — | -| [`Inno.Scripting.Compiler.ScriptCompilationResult? Inno.Editor.Scripting.IScriptCompilationTicket.result { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L28) | Gets the completed compiler result, or null before the compiler finishes. | — | — | -| [`Inno.Editor.Scripting.ScriptCompilationTicketState Inno.Editor.Scripting.IScriptCompilationTicket.state { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L18) | Gets the current lifecycle state of this exact request. | — | — | -| [`string Inno.Editor.Scripting.IScriptCompilationTicket.status { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L23) | Gets a human-readable description of the current or terminal state. | — | — | -| [`public interface Inno.Editor.Scripting.IScriptCompilationTicket`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L8) | Exposes the immutable observer view of one exact script compilation and activation request. | — | — | - -### `Inno.Editor.Scripting.ScriptCompilationTicketState` - @@ -314,30 +280,50 @@ Serialized state 使用逐成员兼容迁移:成员类型保持兼容时恢复 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Canceled = 4`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L31) | The request was explicitly canceled before activation. | — | — | -| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L16) | The request owns the compiler pipeline or is activating its candidate generation. | — | — | -| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Failed = 3`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L26) | Compilation or candidate activation failed without replacing the active generation. | — | — | +| [`public enum Inno.Editor.Scripting.EditorScriptCompilationState : int`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L7) | Identifies whether the active project script generation can safely start game simulation. | — | — | +| [`Inno.Editor.Scripting.EditorScriptCompilationState.Initializing = 0`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L12) | The script service has not produced its initial active generation. | — | — | +| [`Inno.Editor.Scripting.EditorScriptCompilationState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L16) | A requested script generation is compiling, activating, or completing unload verification. | — | — | +| [`Inno.Editor.Scripting.EditorScriptCompilationState.Ready = 2`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L20) | The most recent compilation succeeded, its generation is active, and retired generations passed GC verification. | — | — | +| [`Inno.Editor.Scripting.EditorScriptCompilationState.Failed = 3`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L24) | Compilation, activation, or generation retirement failed, so game simulation must not start. | — | — | +| [`public interface Inno.Editor.Scripting.IEditorScriptCompilation`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L30) | Exposes the minimal script-generation readiness contract required by editor workflows. | — | — | +| [`bool Inno.Editor.Scripting.IEditorScriptCompilation.requiresExclusiveProgress { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L35) | Gets whether compiler or activation work currently requires a blocking progress presentation. | — | — | +| [`float Inno.Editor.Scripting.IEditorScriptCompilation.progress { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L40) | Gets the most recent compiler progress fraction in the inclusive range zero to one. | — | — | +| [`void Inno.Editor.Scripting.IEditorScriptCompilation.CancelCompilation()`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L45) | Requests cancellation while retaining ownership until compilation and retirement finish. | — | — | +| [`Inno.Editor.Scripting.IScriptCompilationTicket Inno.Editor.Scripting.IEditorScriptCompilation.RequestCompilation()`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L53) | Queues a fresh cache-aware compilation for a build or simulation workflow. | A ticket bound to the exact request. Success requires activation and completed retirement verification; a faulted host returns a failed ticket and requires restart. | — | +| [`Inno.Editor.Scripting.IScriptCompilationTicket? Inno.Editor.Scripting.IEditorScriptCompilation.currentTicket { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L57) | Gets the most recently requested compilation ticket, or when no explicit request exists. | — | — | +| [`Inno.Editor.Scripting.EditorScriptCompilationState Inno.Editor.Scripting.IEditorScriptCompilation.state { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L62) | Gets the current script-generation readiness state. | — | — | +| [`string Inno.Editor.Scripting.IEditorScriptCompilation.status { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L67) | Gets a human-readable description of current compiler work or readiness. | — | — | +| [`Inno.Scripting.Compiler.ScriptCompilationResult? Inno.Editor.Scripting.IEditorScriptCompilation.lastCompilation { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs#L72) | Gets the most recently completed compilation, or before the first attempt completes. | — | — | +| [`public interface Inno.Editor.Scripting.IScriptCompilationTicket`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L8) | Exposes the immutable observer view of one exact script compilation and activation request. | — | — | +| [`long Inno.Editor.Scripting.IScriptCompilationTicket.requestId { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L13) | Gets the producer-local monotonic identifier of this request. | — | — | +| [`Inno.Editor.Scripting.ScriptCompilationTicketState Inno.Editor.Scripting.IScriptCompilationTicket.state { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L18) | Gets the current lifecycle state of this exact request. | — | — | +| [`string Inno.Editor.Scripting.IScriptCompilationTicket.status { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L23) | Gets a human-readable description of the current or terminal state. | — | — | +| [`Inno.Scripting.Compiler.ScriptCompilationResult? Inno.Editor.Scripting.IScriptCompilationTicket.result { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L28) | Gets the completed compiler result, or before the compiler finishes. | — | — | +| [`bool Inno.Editor.Scripting.IScriptCompilationTicket.isCompleted { get; }`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/IScriptCompilationTicket.cs#L33) | Gets whether no further state transition is valid for this request. | — | — | +| [`public enum Inno.Editor.Scripting.ScriptCompilationTicketState : int`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L6) | Identifies the lifecycle state of one exact script compilation request. | — | — | | [`Inno.Editor.Scripting.ScriptCompilationTicketState.Queued = 0`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L11) | The request is queued behind the active compiler operation. | — | — | +| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Compiling = 1`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L16) | The request owns the compiler pipeline or is activating its candidate generation. | — | — | | [`Inno.Editor.Scripting.ScriptCompilationTicketState.Succeeded = 2`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L21) | The request compiled and atomically activated its candidate generation. | — | — | +| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Failed = 3`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L26) | Compilation or candidate activation failed without replacing the active generation. | — | — | +| [`Inno.Editor.Scripting.ScriptCompilationTicketState.Canceled = 4`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L31) | The request was explicitly canceled before activation. | — | — | | [`Inno.Editor.Scripting.ScriptCompilationTicketState.Superseded = 5`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L36) | A newer request replaced this request before its candidate could activate. | — | — | -| [`public enum Inno.Editor.Scripting.ScriptCompilationTicketState : int`](../../src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationTicketState.cs#L6) | Identifies the lifecycle state of one exact script compilation request. | — | — | ## 项目依赖 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Plugins.Authoring](../plugins/Inno.Plugins.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Reload](../scripting/Inno.Scripting.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Core](Inno.Editor.Core.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Compiler](../scripting/Inno.Scripting.Compiler.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Plugins.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Core.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Core` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Compiler` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Settings.md b/docs/editor/Inno.Editor.Settings.md index 105460607..9d5bfcb79 100644 --- a/docs/editor/Inno.Editor.Settings.md +++ b/docs/editor/Inno.Editor.Settings.md @@ -136,9 +136,7 @@ History 事务;这允许一个较大的运行时设置协议在 UI 中拆成 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Settings.EditorSetting` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -153,25 +151,6 @@ History 事务;这允许一个较大的运行时设置协议在 UI 中拆成 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Settings.EditorSetting.EditorSetting()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L23) | Creates a Settings definition. A field supplies its persisted default through Inno.Editor.Settings.EditorSetting.defaultValue; a page keeps the base implementation. | — | — | -| [`public bool Inno.Editor.Settings.EditorSetting.Draw(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L85) | Draws this field inside the frontend-managed content container.
返回:true when the staged object differs from the value it held when this frontend first drew that object.
`setting`:The isolated mutable object being edited. | — | `System.ArgumentNullException`:Thrown when setting is null. | -| [`public bool Inno.Editor.Settings.EditorSetting.IsDefault(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L110) | Determines whether a staged field object equals this definition's bound default value.
返回:true when every stored property equals the field default.
`setting`:The isolated staged object to compare. | — | `System.ArgumentNullException`:Thrown when setting is null.
`System.InvalidOperationException`:Thrown when this definition describes a page rather than a field. | -| [`protected virtual void Inno.Editor.Settings.EditorSetting.OnDraw(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L126) | Draws the field using the staged JSON object owned by the Settings frontend. A type that does not override this method describes a page.
`setting`:The isolated mutable object being edited. | — | — | -| [`public virtual Inno.Editor.Settings.EditorSettingObject Inno.Editor.Settings.EditorSetting.defaultValue { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L59) | Creates the default object for a field. Page definitions keep the base implementation, whose internal value is null. | Field implementations must return a newly owned object on every access. | — | -| [`public virtual string Inno.Editor.Settings.EditorSetting.description { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L70) | Gets the page or field explanation displayed by the Settings frontend. | — | — | -| [`public bool Inno.Editor.Settings.EditorSetting.hasValue { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L50) | Gets whether this definition owns one persisted JSON object. | — | — | -| [`public string Inno.Editor.Settings.EditorSetting.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L40) | Gets the display label derived from the final path segment. | — | — | -| [`public int Inno.Editor.Settings.EditorSetting.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L45) | Gets the stable order among fields with the same section and label. | — | — | -| [`public string Inno.Editor.Settings.EditorSetting.pagePath { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L35) | Gets the page that owns this field, or this page's own path for a page definition. | — | — | -| [`public string Inno.Editor.Settings.EditorSetting.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L30) | Gets the complete slash-delimited identity and placement path. | — | — | -| [`public virtual string Inno.Editor.Settings.EditorSetting.section { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L65) | Gets the section heading used to group fields alphabetically. Definitions without a section keep the base implementation, whose internal value is null. | — | — | -| [`public abstract class Inno.Editor.Settings.EditorSetting`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L10) | Defines one path-addressed Settings page or one custom-drawn Settings field. | — | — | - -### `Inno.Editor.Settings.EditorSettingObject` - @@ -202,76 +181,10 @@ History 事务;这允许一个较大的运行时设置协议在 UI 中拆成 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Settings.EditorSettingObject.EditorSettingObject()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L21) | Creates an empty Settings object. | — | — | -| [`public bool Inno.Editor.Settings.EditorSettingObject.GetAsBoolean(string name, bool defaultValue = false)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L42) | Gets a Boolean property, or a fallback when the property is absent.
返回:The stored Boolean or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public bool[] Inno.Editor.Settings.EditorSettingObject.GetAsBooleanArray(string name, bool[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L304) | Gets a Boolean array property, or a copied fallback when the property is absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public double Inno.Editor.Settings.EditorSettingObject.GetAsDouble(string name, double defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L235) | Gets a double-precision property, or a fallback when the property is absent.
返回:The stored number or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public double[] Inno.Editor.Settings.EditorSettingObject.GetAsDoubleArray(string name, double[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L428) | Gets a double-precision array property, or a copied fallback when absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public int Inno.Editor.Settings.EditorSettingObject.GetAsInt32(string name, int defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L73) | Gets a 32-bit signed integer property, or a fallback when the property is absent.
返回:The stored integer or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public int[] Inno.Editor.Settings.EditorSettingObject.GetAsInt32Array(string name, int[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L335) | Gets a 32-bit signed integer array property, or a copied fallback when absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public long Inno.Editor.Settings.EditorSettingObject.GetAsInt64(string name, long defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L135) | Gets a 64-bit signed integer property, or a fallback when the property is absent.
返回:The stored integer or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public float Inno.Editor.Settings.EditorSettingObject.GetAsSingle(string name, float defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L197) | Gets a single-precision property, or a fallback when the property is absent.
返回:The stored number or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public float[] Inno.Editor.Settings.EditorSettingObject.GetAsSingleArray(string name, float[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L397) | Gets a single-precision array property, or a copied fallback when absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public string? Inno.Editor.Settings.EditorSettingObject.GetAsString(string name, string? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L273) | Gets a string property, or a fallback when the property is absent or null.
返回:The stored string or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent or null. | — | — | -| [`public string?[] Inno.Editor.Settings.EditorSettingObject.GetAsStringArray(string name, string?[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L459) | Gets a nullable string array property, or a copied fallback when absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public uint Inno.Editor.Settings.EditorSettingObject.GetAsUInt32(string name, uint defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L104) | Gets a 32-bit unsigned integer property, or a fallback when the property is absent.
返回:The stored integer or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public uint[] Inno.Editor.Settings.EditorSettingObject.GetAsUInt32Array(string name, uint[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L366) | Gets a 32-bit unsigned integer array property, or a copied fallback when absent.
返回:An independently owned array.
`name`:The stable property name.
`defaultValue`:The array copied when the property is absent. | — | — | -| [`public ulong Inno.Editor.Settings.EditorSettingObject.GetAsUInt64(string name, ulong defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L166) | Gets a 64-bit unsigned integer property, or a fallback when the property is absent.
返回:The stored integer or defaultValue.
`name`:The stable property name.
`defaultValue`:The value returned when the property is absent. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsBoolean(string name, bool value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L56) | Sets a Boolean property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsBooleanArray(string name, bool[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L318) | Sets a Boolean array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsDouble(string name, double value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L252) | Sets a finite double-precision property.
`name`:The stable property name.
`value`:The finite value to store. | — | `System.ArgumentOutOfRangeException`:Thrown when value is not finite. | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsDoubleArray(string name, double[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L442) | Sets a double-precision array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt32(string name, int value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L87) | Sets a 32-bit signed integer property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt32Array(string name, int[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L349) | Sets a 32-bit signed integer array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt64(string name, long value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L149) | Sets a 64-bit signed integer property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsSingle(string name, float value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L214) | Sets a finite single-precision property.
`name`:The stable property name.
`value`:The finite value to store. | — | `System.ArgumentOutOfRangeException`:Thrown when value is not finite. | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsSingleArray(string name, float[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L411) | Sets a single-precision array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsString(string name, string? value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L287) | Sets a nullable string property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsStringArray(string name, string?[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L473) | Sets a nullable string array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt32(string name, uint value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L118) | Sets a 32-bit unsigned integer property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt32Array(string name, uint[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L380) | Sets a 32-bit unsigned integer array property by value.
`name`:The stable property name.
`value`:The array copied into the object. | — | — | -| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt64(string name, ulong value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L180) | Sets a 64-bit unsigned integer property.
`name`:The stable property name.
`value`:The value to store. | — | — | -| [`public sealed class Inno.Editor.Settings.EditorSettingObject : Inno.Core.Serialization.ISerializable`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L13) | Represents one isolated, strongly typed object exposed to a Settings field and its consumers. | — | — | - -### `Inno.Editor.Settings.EditorSettingPathAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Settings.EditorSettingPathAttribute.EditorSettingPathAttribute(string path, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L24) | Creates a path placement. A definition that overrides its drawing method becomes a field; a definition that keeps the default drawing method describes the page at the complete path.
`path`:The slash-delimited page and field path.
`order`:The stable order among fields with the same section and label. | — | `System.ArgumentException`:Thrown when path contains an empty segment or is outside the Editor root. | -| [`public int Inno.Editor.Settings.EditorSettingPathAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L48) | Gets the stable order among fields with the same section and label. | — | — | -| [`public string Inno.Editor.Settings.EditorSettingPathAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L43) | Gets the normalized path including the field label. | — | — | -| [`public sealed class Inno.Editor.Settings.EditorSettingPathAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L9) | Places an Inno.Editor.Settings.EditorSetting at an arbitrary string Settings path. | — | — | - -### `Inno.Editor.Settings.EditorSettings` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public System.Action? Inno.Editor.Settings.EditorSettings.changed`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L72) | Occurs after a complete Settings Apply, Undo, or Redo changes the effective document. The committed Inno.Editor.Settings.EditorSettings service is the event's only argument. | — | — | -| [`public bool Inno.Editor.Settings.EditorSettings.Apply(System.Collections.Generic.IReadOnlyDictionary values, System.Collections.Generic.IReadOnlySet? resets = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L131) | Atomically applies staged field objects as one shared Undo and Redo history entry.
返回:true when the persisted document changed and one history entry was recorded.
`values`:The complete or partial path-addressed objects to apply from the active catalog.
`resets`:Optional paths whose persisted overrides are removed during the same Apply. | — | `System.ArgumentNullException`:Thrown when values is null or contains a null object.
`System.ArgumentException`:Thrown when a supplied path is invalid, absent, or describes a page.
`System.IO.IOException`:Thrown when the project-root document or its history payload cannot be written.
`System.UnauthorizedAccessException`:Thrown when the project-root document is inaccessible. | -| [`public Inno.Editor.Settings.EditorSettingObject Inno.Editor.Settings.EditorSettings.Get(string path)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L96) | Reads an isolated effective object from one complete Settings path.
返回:The stored object, or an isolated copy of the field's default object.
`path`:The slash-delimited Settings field path. | — | `System.ArgumentException`:Thrown when path is invalid, missing, or describes a page. | -| [`protected override void Inno.Editor.Settings.EditorSettings.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L209) | Releases resources retained by this feature after it has stopped. | — | — | -| [`public long Inno.Editor.Settings.EditorSettings.catalogRevision { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L77) | Gets the current discovered type-catalog revision. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Settings.EditorSettings.definitions { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L82) | Gets all discovered path definitions in deterministic path order. | — | — | -| [`public sealed class Inno.Editor.Settings.EditorSettings : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L17) | Owns the discovered Settings catalog and the project-root Settings document. | — | — | - -### `Inno.Editor.Settings.ProjectSettingEditor` - @@ -283,49 +196,20 @@ History 事务;这允许一个较大的运行时设置协议在 UI 中拆成 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Settings.ProjectSettingEditor.ProjectSettingEditor()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L13) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public bool Inno.Editor.Settings.ProjectSettingEditor.Draw(Inno.Core.Serialization.ISerializable value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L71) | Draws one isolated staged value through this presentation.
返回:true when the value differs from its first drawn state.
`value`:The exact setting type owned by this presentation. | — | `System.ArgumentException`:Thrown when value has another runtime type. | -| [`public bool Inno.Editor.Settings.ProjectSettingEditor.ValuesEqual(Inno.Core.Serialization.ISerializable left, Inno.Core.Serialization.ISerializable right)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L89) | Compares two exact-type values through their native serialized property data.
返回:true when both values contain equal serialized properties.
`left`:The first setting value.
`right`:The second setting value. | — | `System.ArgumentException`:Thrown when either value has another runtime type. | -| [`public virtual string Inno.Editor.Settings.ProjectSettingEditor.description { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L56) | Gets the explanation displayed by the unified Settings frontend. | — | — | -| [`public string Inno.Editor.Settings.ProjectSettingEditor.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L41) | Gets the display label derived from the final path segment. | — | — | -| [`public int Inno.Editor.Settings.ProjectSettingEditor.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L46) | Gets the stable order among fields in the same section. | — | — | -| [`public string Inno.Editor.Settings.ProjectSettingEditor.pagePath { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L36) | Gets the page that owns this field. | — | — | -| [`public string Inno.Editor.Settings.ProjectSettingEditor.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L31) | Gets the complete slash-delimited placement path. | — | — | -| [`public virtual string Inno.Editor.Settings.ProjectSettingEditor.section { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L51) | Gets the section heading used to group this field. | — | — | -| [`public Inno.Core.Settings.ProjectSettingId Inno.Editor.Settings.ProjectSettingEditor.settingId { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L26) | Gets the stable runtime project setting protocol read from the target setting type's Inno.Core.Settings.ProjectSettingDefinitionAttribute by the Editor catalog. | — | — | -| [`public abstract class Inno.Editor.Settings.ProjectSettingEditor`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L13) | Describes one Editor presentation for a strongly typed runtime project setting. | — | — | - -### `Inno.Editor.Settings.ProjectSettingEditor`1` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Settings.ProjectSettingEditor.ProjectSettingEditor()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L173) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected abstract void Inno.Editor.Settings.ProjectSettingEditor.OnDraw(TSetting setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L184) | Draws the isolated staged value. Mutations remain local until the Settings frontend applies them.
`setting`:The isolated current-generation setting snapshot. | — | — | -| [`public abstract class Inno.Editor.Settings.ProjectSettingEditor where TSetting : class, Inno.Core.Serialization.ISerializable : Inno.Editor.Settings.ProjectSettingEditor`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L173) | Provides a typed drawing extension for one runtime project setting protocol. | — | — | - -### `Inno.Editor.Settings.ProjectSettingPathAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Settings.ProjectSettingPathAttribute.ProjectSettingPathAttribute(string path, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L23) | Creates a project setting placement.
`path`:The slash-delimited field path beginning with Project/.
`order`:The stable order among fields in the same section. | — | `System.ArgumentException`:Thrown when path is outside the Project root or contains an empty segment. | -| [`public int Inno.Editor.Settings.ProjectSettingPathAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L46) | Gets the stable order among fields in the same section. | — | — | -| [`public string Inno.Editor.Settings.ProjectSettingPathAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L41) | Gets the normalized complete placement path. | — | — | -| [`public sealed class Inno.Editor.Settings.ProjectSettingPathAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L9) | Places a strongly typed project setting editor under the Project root of the unified Settings window. | — | — | - -### `Inno.Editor.Settings.ProjectSettingsEditor` - + + + + + + + @@ -334,24 +218,99 @@ History 事务;这允许一个较大的运行时设置协议在 UI 中拆成 -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public bool Inno.Editor.Settings.ProjectSettingsEditor.Apply(System.Collections.Generic.IReadOnlyDictionary values, System.Collections.Generic.IReadOnlySet? resets = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L137) | Atomically applies project-authored overrides and records one stable history entry.
返回:true when the project override document changed.
`values`:Staged exact-type values keyed by stable setting identity.
`resets`:Setting identities whose project overrides should be removed. | — | `System.ArgumentNullException`:Thrown when values is null. | -| [`public Inno.Core.Serialization.ISerializable Inno.Editor.Settings.ProjectSettingsEditor.Get(Inno.Editor.Settings.ProjectSettingEditor definition)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L81) | Creates an isolated editable snapshot for one registered presentation.
返回:An isolated current-generation value.
`definition`:The active presentation whose runtime protocol is requested. | — | `System.ArgumentNullException`:Thrown when definition is null.
`System.InvalidOperationException`:Thrown when the runtime setting definition is unavailable. | -| [`public Inno.Core.Serialization.ISerializable Inno.Editor.Settings.ProjectSettingsEditor.GetComposedDefault(Inno.Editor.Settings.ProjectSettingEditor definition)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L108) | Creates the composed host and Plugin default without the project override.
返回:An isolated composed default value.
`definition`:The active presentation whose default is requested. | — | `System.ArgumentNullException`:Thrown when definition is null.
`System.InvalidOperationException`:Thrown when the runtime setting definition is unavailable. | -| [`protected override void Inno.Editor.Settings.ProjectSettingsEditor.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L162) | Releases resources retained by this feature after it has stopped. | — | — | +| [`public abstract class Inno.Editor.Settings.EditorSetting`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L10) | Defines one path-addressed Settings page or one custom-drawn Settings field. | — | — | +| [`protected Inno.Editor.Settings.EditorSetting.EditorSetting()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L23) | Creates a Settings definition. A field supplies its persisted default through ; a page keeps the base implementation. | — | — | +| [`public string Inno.Editor.Settings.EditorSetting.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L30) | Gets the complete slash-delimited identity and placement path. | — | — | +| [`public string Inno.Editor.Settings.EditorSetting.pagePath { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L35) | Gets the page that owns this field, or this page's own path for a page definition. | — | — | +| [`public string Inno.Editor.Settings.EditorSetting.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L40) | Gets the display label derived from the final path segment. | — | — | +| [`public int Inno.Editor.Settings.EditorSetting.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L45) | Gets the stable order among fields with the same section and label. | — | — | +| [`public bool Inno.Editor.Settings.EditorSetting.hasValue { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L50) | Gets whether this definition owns one persisted JSON object. | — | — | +| [`public virtual Inno.Editor.Settings.EditorSettingObject Inno.Editor.Settings.EditorSetting.defaultValue { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L59) | Creates the default object for a field. Page definitions keep the base implementation, whose internal value is . | — | — | +| [`public virtual string Inno.Editor.Settings.EditorSetting.section { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L65) | Gets the section heading used to group fields alphabetically. Definitions without a section keep the base implementation, whose internal value is . | — | — | +| [`public virtual string Inno.Editor.Settings.EditorSetting.description { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L70) | Gets the page or field explanation displayed by the Settings frontend. | — | — | +| [`public bool Inno.Editor.Settings.EditorSetting.Draw(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L85) | Draws this field inside the frontend-managed content container.
`setting`: The isolated mutable object being edited. | when the staged object differs from the value it held when this frontend first drew that object. | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Editor.Settings.EditorSetting.IsDefault(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L110) | Determines whether a staged field object equals this definition's bound default value.
`setting`: The isolated staged object to compare. | when every stored property equals the field default. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when this definition describes a page rather than a field. | +| [`protected virtual void Inno.Editor.Settings.EditorSetting.OnDraw(Inno.Editor.Settings.EditorSettingObject setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSetting.cs#L126) | Draws the field using the staged JSON object owned by the Settings frontend. A type that does not override this method describes a page.
`setting`: The isolated mutable object being edited. | — | — | +| [`public sealed class Inno.Editor.Settings.EditorSettingObject : Inno.Core.Serialization.ISerializable`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L13) | Represents one isolated, strongly typed object exposed to a Settings field and its consumers. | — | — | +| [`public Inno.Editor.Settings.EditorSettingObject.EditorSettingObject()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L21) | Creates an empty Settings object. | — | — | +| [`public bool Inno.Editor.Settings.EditorSettingObject.GetAsBoolean(string name, bool defaultValue = false)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L42) | Gets a Boolean property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored Boolean or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsBoolean(string name, bool value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L56) | Sets a Boolean property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public int Inno.Editor.Settings.EditorSettingObject.GetAsInt32(string name, int defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L73) | Gets a 32-bit signed integer property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored integer or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt32(string name, int value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L87) | Sets a 32-bit signed integer property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public uint Inno.Editor.Settings.EditorSettingObject.GetAsUInt32(string name, uint defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L104) | Gets a 32-bit unsigned integer property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored integer or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt32(string name, uint value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L118) | Sets a 32-bit unsigned integer property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public long Inno.Editor.Settings.EditorSettingObject.GetAsInt64(string name, long defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L135) | Gets a 64-bit signed integer property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored integer or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt64(string name, long value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L149) | Sets a 64-bit signed integer property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public ulong Inno.Editor.Settings.EditorSettingObject.GetAsUInt64(string name, ulong defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L166) | Gets a 64-bit unsigned integer property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored integer or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt64(string name, ulong value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L180) | Sets a 64-bit unsigned integer property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public float Inno.Editor.Settings.EditorSettingObject.GetAsSingle(string name, float defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L197) | Gets a single-precision property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored number or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsSingle(string name, float value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L214) | Sets a finite single-precision property.
`name`: The stable property name.
`value`: The finite value to store. | — | `System.ArgumentOutOfRangeException`: Thrown when is not finite. | +| [`public double Inno.Editor.Settings.EditorSettingObject.GetAsDouble(string name, double defaultValue = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L235) | Gets a double-precision property, or a fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent. | The stored number or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsDouble(string name, double value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L252) | Sets a finite double-precision property.
`name`: The stable property name.
`value`: The finite value to store. | — | `System.ArgumentOutOfRangeException`: Thrown when is not finite. | +| [`public string? Inno.Editor.Settings.EditorSettingObject.GetAsString(string name, string? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L273) | Gets a string property, or a fallback when the property is absent or null.
`name`: The stable property name.
`defaultValue`: The value returned when the property is absent or null. | The stored string or . | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsString(string name, string? value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L287) | Sets a nullable string property.
`name`: The stable property name.
`value`: The value to store. | — | — | +| [`public bool[] Inno.Editor.Settings.EditorSettingObject.GetAsBooleanArray(string name, bool[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L304) | Gets a Boolean array property, or a copied fallback when the property is absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsBooleanArray(string name, bool[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L318) | Sets a Boolean array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public int[] Inno.Editor.Settings.EditorSettingObject.GetAsInt32Array(string name, int[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L335) | Gets a 32-bit signed integer array property, or a copied fallback when absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsInt32Array(string name, int[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L349) | Sets a 32-bit signed integer array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public uint[] Inno.Editor.Settings.EditorSettingObject.GetAsUInt32Array(string name, uint[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L366) | Gets a 32-bit unsigned integer array property, or a copied fallback when absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsUInt32Array(string name, uint[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L380) | Sets a 32-bit unsigned integer array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public float[] Inno.Editor.Settings.EditorSettingObject.GetAsSingleArray(string name, float[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L397) | Gets a single-precision array property, or a copied fallback when absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsSingleArray(string name, float[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L411) | Sets a single-precision array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public double[] Inno.Editor.Settings.EditorSettingObject.GetAsDoubleArray(string name, double[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L428) | Gets a double-precision array property, or a copied fallback when absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsDoubleArray(string name, double[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L442) | Sets a double-precision array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public string?[] Inno.Editor.Settings.EditorSettingObject.GetAsStringArray(string name, string?[]? defaultValue = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L459) | Gets a nullable string array property, or a copied fallback when absent.
`name`: The stable property name.
`defaultValue`: The array copied when the property is absent. | An independently owned array. | — | +| [`public void Inno.Editor.Settings.EditorSettingObject.SetAsStringArray(string name, string?[] value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingObject.cs#L473) | Sets a nullable string array property by value.
`name`: The stable property name.
`value`: The array copied into the object. | — | — | +| [`public sealed class Inno.Editor.Settings.EditorSettingPathAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L9) | Places an at an arbitrary string Settings path. | — | — | +| [`public Inno.Editor.Settings.EditorSettingPathAttribute.EditorSettingPathAttribute(string path, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L24) | Creates a path placement. A definition that overrides its drawing method becomes a field; a definition that keeps the default drawing method describes the page at the complete path.
`path`: The slash-delimited page and field path.
`order`: The stable order among fields with the same section and label. | — | `System.ArgumentException`: Thrown when contains an empty segment or is outside the Editor root. | +| [`public string Inno.Editor.Settings.EditorSettingPathAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L43) | Gets the normalized path including the field label. | — | — | +| [`public int Inno.Editor.Settings.EditorSettingPathAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/EditorSettingPathAttribute.cs#L48) | Gets the stable order among fields with the same section and label. | — | — | +| [`public abstract class Inno.Editor.Settings.ProjectSettingEditor`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L13) | Describes one Editor presentation for a strongly typed runtime project setting. | — | — | +| [`public Inno.Core.Settings.ProjectSettingId Inno.Editor.Settings.ProjectSettingEditor.settingId { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L26) | Gets the stable runtime project setting protocol read from the target setting type's by the Editor catalog. | — | — | +| [`public string Inno.Editor.Settings.ProjectSettingEditor.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L31) | Gets the complete slash-delimited placement path. | — | — | +| [`public string Inno.Editor.Settings.ProjectSettingEditor.pagePath { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L36) | Gets the page that owns this field. | — | — | +| [`public string Inno.Editor.Settings.ProjectSettingEditor.label { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L41) | Gets the display label derived from the final path segment. | — | — | +| [`public int Inno.Editor.Settings.ProjectSettingEditor.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L46) | Gets the stable order among fields in the same section. | — | — | +| [`public virtual string Inno.Editor.Settings.ProjectSettingEditor.section { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L51) | Gets the section heading used to group this field. | — | — | +| [`public virtual string Inno.Editor.Settings.ProjectSettingEditor.description { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L56) | Gets the explanation displayed by the unified Settings frontend. | — | — | +| [`public bool Inno.Editor.Settings.ProjectSettingEditor.Draw(Inno.Core.Serialization.ISerializable value)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L71) | Draws one isolated staged value through this presentation.
`value`: The exact setting type owned by this presentation. | when the value differs from its first drawn state. | `System.ArgumentException`: Thrown when has another runtime type. | +| [`public bool Inno.Editor.Settings.ProjectSettingEditor.ValuesEqual(Inno.Core.Serialization.ISerializable left, Inno.Core.Serialization.ISerializable right)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L89) | Compares two exact-type values through their native serialized property data.
`left`: The first setting value.
`right`: The second setting value. | when both values contain equal serialized properties. | `System.ArgumentException`: Thrown when either value has another runtime type. | +| [`protected Inno.Editor.Settings.ProjectSettingEditor.ProjectSettingEditor()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L13) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Editor.Settings.ProjectSettingEditor where TSetting : class, Inno.Core.Serialization.ISerializable : Inno.Editor.Settings.ProjectSettingEditor`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L173) | Provides a typed drawing extension for one runtime project setting protocol.
`TSetting`: The exact serializable setting type registered by the runtime Plugin or host. | — | — | +| [`protected abstract void Inno.Editor.Settings.ProjectSettingEditor.OnDraw(TSetting setting)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L184) | Draws the isolated staged value. Mutations remain local until the Settings frontend applies them.
`setting`: The isolated current-generation setting snapshot. | — | — | +| [`protected Inno.Editor.Settings.ProjectSettingEditor.ProjectSettingEditor()`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingEditor.cs#L173) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Settings.ProjectSettingPathAttribute : System.Attribute`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L9) | Places a strongly typed project setting editor under the Project root of the unified Settings window. | — | — | +| [`public Inno.Editor.Settings.ProjectSettingPathAttribute.ProjectSettingPathAttribute(string path, int order = 0)`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L23) | Creates a project setting placement.
`path`: The slash-delimited field path beginning with Project/.
`order`: The stable order among fields in the same section. | — | `System.ArgumentException`: Thrown when is outside the Project root or contains an empty segment. | +| [`public string Inno.Editor.Settings.ProjectSettingPathAttribute.path { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L41) | Gets the normalized complete placement path. | — | — | +| [`public int Inno.Editor.Settings.ProjectSettingPathAttribute.order { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Definitions/ProjectSettingPathAttribute.cs#L46) | Gets the stable order among fields in the same section. | — | — | +| [`public sealed class Inno.Editor.Settings.EditorSettings : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L16) | Owns the discovered Settings catalog and the project-root Settings document. | — | — | +| [`public System.IDisposable Inno.Editor.Settings.EditorSettings.ListenChanges(System.Action observer)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L80) | Observes committed Apply, Undo and Redo changes through an owned Core Event hub.
`observer`: Synchronous callback reading the current committed settings; observer failures are logged and isolated. | A subscription owner that must be disposed before its feature or extension generation retires. | `System.ArgumentNullException`: The observer is null. | +| [`public long Inno.Editor.Settings.EditorSettings.catalogRevision { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L103) | Gets the current discovered type-catalog revision. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Settings.EditorSettings.definitions { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L107) | Gets all discovered path definitions in deterministic path order. | — | — | +| [`public Inno.Editor.Settings.EditorSettingObject Inno.Editor.Settings.EditorSettings.Get(string path)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L121) | Reads an isolated effective object from one complete Settings path.
`path`: The slash-delimited Settings field path. | The stored object, or an isolated copy of the field's default object. | `System.ArgumentException`: Thrown when is invalid, missing, or describes a page. | +| [`public bool Inno.Editor.Settings.EditorSettings.Apply(System.Collections.Generic.IReadOnlyDictionary values, System.Collections.Generic.IReadOnlySet? resets = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L154) | Atomically applies staged field objects as one shared Undo and Redo history entry.
`values`: The complete or partial path-addressed objects to apply from the active catalog.
`resets`: Optional paths whose persisted overrides are removed during the same Apply. | when the persisted document changed and one history entry was recorded. | `System.ArgumentNullException`: Thrown when is or contains a null object.
`System.ArgumentException`: Thrown when a supplied path is invalid, absent, or describes a page.
`System.IO.IOException`: Thrown when the project-root document or its history payload cannot be written.
`System.UnauthorizedAccessException`: Thrown when the project-root document is inaccessible. | +| [`protected override void Inno.Editor.Settings.EditorSettings.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs#L231) | Releases resources retained by this feature after it has stopped. | — | — | +| [`public sealed class Inno.Editor.Settings.ProjectSettingsEditor : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L17) | Owns the reloadable Editor presentations and history-aware project override workflow for runtime settings. | — | — | | [`public long Inno.Editor.Settings.ProjectSettingsEditor.catalogRevision { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L59) | Gets the active project setting Editor catalog revision. | — | — | | [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Settings.ProjectSettingsEditor.definitions { get; }`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L64) | Gets all active strongly typed project setting presentations. | — | — | -| [`public sealed class Inno.Editor.Settings.ProjectSettingsEditor : Inno.Editor.Core.EditorModule`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L17) | Owns the reloadable Editor presentations and history-aware project override workflow for runtime settings. | — | — | +| [`public Inno.Core.Serialization.ISerializable Inno.Editor.Settings.ProjectSettingsEditor.Get(Inno.Editor.Settings.ProjectSettingEditor definition)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L81) | Creates an isolated editable snapshot for one registered presentation.
`definition`: The active presentation whose runtime protocol is requested. | An isolated current-generation value. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the runtime setting definition is unavailable. | +| [`public Inno.Core.Serialization.ISerializable Inno.Editor.Settings.ProjectSettingsEditor.GetComposedDefault(Inno.Editor.Settings.ProjectSettingEditor definition)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L108) | Creates the composed host and Plugin default without the project override.
`definition`: The active presentation whose default is requested. | An isolated composed default value. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the runtime setting definition is unavailable. | +| [`public bool Inno.Editor.Settings.ProjectSettingsEditor.Apply(System.Collections.Generic.IReadOnlyDictionary values, System.Collections.Generic.IReadOnlySet? resets = null)`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L137) | Atomically applies project-authored overrides and records one stable history entry.
`values`: Staged exact-type values keyed by stable setting identity.
`resets`: Setting identities whose project overrides should be removed. | when the project override document changed. | `System.ArgumentNullException`: Thrown when is . | +| [`protected override void Inno.Editor.Settings.ProjectSettingsEditor.OnDispose()`](../../src/composition/editor/framework/Inno.Editor.Settings/Runtime/ProjectSettingsEditor.cs#L162) | Releases resources retained by this feature after it has stopped. | — | — | ## 项目依赖 -- [Inno.Core.Logging](../core/Inno.Core.Logging.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization.Generators](../core/Inno.Core.Serialization.Generators.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Events` | 实现依赖;不传递 compile surface | +| `Inno.Core.Logging` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Core.Serialization.Generators` | 编译器 Analyzer;不进入运行时 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Core.Settings` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Shaders.ImGui.md b/docs/editor/Inno.Editor.Shaders.ImGui.md new file mode 100644 index 000000000..2333141e4 --- /dev/null +++ b/docs/editor/Inno.Editor.Shaders.ImGui.md @@ -0,0 +1,151 @@ +# Inno.Editor.Shaders.ImGui + +[Editor 索引](README.md) · [Wiki 首页](../README.md) · [UI 架构](../architecture/EDITOR_UI_ARCHITECTURE.md) + +## 职责与边界 + +Shader 与 Material 的可复用检查显示。依赖 headless Shaders/Rendering/Assets 以及 Inspection、ImGui;不引用 Inspector 或 ShaderEditor Panel。 + +## 依赖与初始化 + +产品通过 `EditorProduct.props` 显式部署此程序集。由当前 TypeRegistry/Editor Registry 发现声明并在候选成功后切换;不扫描目录、不建立独立注册表或全局状态。 + +## API 与扩展点 + +ShaderNodeDrawerAttribute、ShaderNodeDrawer、ShaderNodeDrawContext、ShaderNodePresentation 与 registry 描述节点显示;Material inspectors 使用共享草稿;ShaderPropertyInspector 处理参数类型和混合值。ShaderPreviews 只显示当前 preview service 的结果。 + +## 工作流 + +扩展继承 ShaderNodeDrawer,Attribute 携带稳定节点 ID。通过 DrawProperty 使用统一属性手势;自定义连续编辑通过当前手势 mergeKey 记录。不要持有 draw context 或 canonical asset。 + +## 错误、生命周期与热重载 + +控件只在当前 EditorUiSession 的 owner thread、作用域与原生上下文中使用。跨帧只保留稳定 ID 和中立状态;Missing、只读和未赋值状态保持区别。手势失败保留 History 所有权和诊断;扩展在所属 generation 退休,不保留插件 Type、delegate 或实例。 + +所有公开签名的当前映射见本页 API 清单;内部实现不构成稳定扩展契约。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public static class Inno.Editor.Shaders.ImGui.ShaderPropertyInspector`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs#L13) | Uses the shared Inspector drawers for both Shader defaults and Material overrides. | — | — | +| [`public static void Inno.Editor.Shaders.ImGui.ShaderPropertyInspector.Draw(Inno.Editor.Inspection.InspectionDrawContext context, object owner, string path, Inno.Rendering.Assets.ShaderPropertyDefinition property, Inno.Rendering.Assets.MaterialValue value, System.Action setter, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly, Inno.Editor.Shaders.ShaderParameterPresentation? presentation = null)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs#L45) | Draws a typed value without choosing its persistence or history policy.
`context`: Current Inspector invocation.
`owner`: Detached draft owner used for widget identity.
`path`: Stable draft property path.
`property`: Current Shader declaration.
`value`: Current default or override, never mutated.
`setter`: Writes only the host's detached draft.
`edits`: Host-owned gesture and history service.
`readOnly`: Whether this source is editable.
`presentation`: Optional Editor-only description and scalar bounds; it never changes inherited or stored values. | — | — | +| [`public static bool Inno.Editor.Shaders.ImGui.ShaderPropertyInspector.Compatible(Inno.Rendering.ShaderPropertyType type, Inno.Rendering.Assets.MaterialValueKind kind)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs#L95) | Tests whether a stored override has the exact representation expected by a declaration.
`type`: Declared material type.
`kind`: Stored representation. | True only for supported matching kinds; no lossy conversion is performed. | — | +| [`public static Inno.Rendering.Assets.MaterialValue Inno.Editor.Shaders.ImGui.ShaderPropertyInspector.ApplyEdit(Inno.Rendering.ShaderPropertyType type, Inno.Rendering.Assets.MaterialValue before, Inno.Rendering.Assets.MaterialValue edited, Inno.Rendering.Assets.MaterialValue target)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs#L125) | Applies the edited components of a representative value without replacing unrelated mixed components.
`type`: Common exact Shader property type.
`before`: Representative value before the control gesture sample.
`edited`: Representative value after this sample.
`target`: Another selected Material's current effective value. | A detached value preserving the target's untouched channels, texture or sampler. | — | +| [`public sealed class Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute : System.Attribute`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L13) | Registers Editor-only node controls independently from a node's shader compiler. | — | — | +| [`public Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.ShaderNodeDrawerAttribute(string definitionId, string displayName = "", string createPath = "", int createOrder = 0, bool separatorBefore = false)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L33) | Associates the drawer with one stable node definition.
`definitionId`: The compiler-independent graph node identity.
`displayName`: Optional artist-facing name used in node headers and creation menus.
`createPath`: Optional slash-delimited path below Create. Empty uses the host's built-in classification.
`createOrder`: Stable ordering value among sibling creation groups and nodes.
`separatorBefore`: Whether the contributed creation group is preceded by a separator at its own level. | — | — | +| [`public string Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.definitionId { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L51) | Gets the stable node identity handled by this drawer. | — | — | +| [`public string Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.displayName { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L55) | Gets the optional presentation-only name; it does not participate in Shader compilation or identity. | — | — | +| [`public string Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.createPath { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L59) | Gets the optional creation-menu path below Create. | — | — | +| [`public int Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.createOrder { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L63) | Gets the creation-menu order. | — | — | +| [`public bool Inno.Editor.Shaders.ImGui.ShaderNodeDrawerAttribute.separatorBefore { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L67) | Gets whether the contributed creation group starts a new visual section. | — | — | +| [`public readonly record struct Inno.Editor.Shaders.ImGui.ShaderNodePresentation : System.IEquatable`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Immutable authoring presentation contributed for one Shader node definition. | — | — | +| [`public Inno.Editor.Shaders.ImGui.ShaderNodePresentation.ShaderNodePresentation(string displayName, string createPath, int createOrder, bool separatorBefore)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L90) | Creates presentation metadata for one Shader node definition.
`displayName`: Optional artist-facing node name.
`createPath`: Optional slash-delimited creation-menu path.
`createOrder`: Stable ordering value within the creation menu.
`separatorBefore`: Whether the contributed group starts a visual section. | — | — | +| [`public string Inno.Editor.Shaders.ImGui.ShaderNodePresentation.displayName { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L105) | Gets the optional artist-facing node name. | — | — | +| [`public string Inno.Editor.Shaders.ImGui.ShaderNodePresentation.createPath { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L109) | Gets the optional slash-delimited creation-menu path. | — | — | +| [`public int Inno.Editor.Shaders.ImGui.ShaderNodePresentation.createOrder { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L113) | Gets the stable ordering value within the creation menu. | — | — | +| [`public bool Inno.Editor.Shaders.ImGui.ShaderNodePresentation.separatorBefore { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L117) | Gets whether the contributed group starts a visual section. | — | — | +| [`public override string Inno.Editor.Shaders.ImGui.ShaderNodePresentation.ToString()`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Editor.Shaders.ImGui.ShaderNodePresentation.operator !=(Inno.Editor.Shaders.ImGui.ShaderNodePresentation left, Inno.Editor.Shaders.ImGui.ShaderNodePresentation right)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Editor.Shaders.ImGui.ShaderNodePresentation.operator ==(Inno.Editor.Shaders.ImGui.ShaderNodePresentation left, Inno.Editor.Shaders.ImGui.ShaderNodePresentation right)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Editor.Shaders.ImGui.ShaderNodePresentation.GetHashCode()`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Editor.Shaders.ImGui.ShaderNodePresentation.Equals(object obj)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Editor.Shaders.ImGui.ShaderNodePresentation.Equals(Inno.Editor.Shaders.ImGui.ShaderNodePresentation other)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L73) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public abstract class Inno.Editor.Shaders.ImGui.ShaderNodeDrawer`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L123) | Provides reloadable Inspector controls; instances must not retain frame contexts or asset objects. | — | — | +| [`public abstract void Inno.Editor.Shaders.ImGui.ShaderNodeDrawer.Draw(Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext context)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L131) | Draws the selected node's controls using the shared Inspector styling.
`context`: Frame-scoped value access and undoable writes. | — | — | +| [`protected Inno.Editor.Shaders.ImGui.ShaderNodeDrawer.ShaderNodeDrawer()`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L123) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L137) | Exposes detached node values and one-gesture writes without exposing the live graph document. | — | — | +| [`public Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.ShaderNodeDrawContext(Inno.Core.Graphs.GraphNodeRecord node, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context, System.Action write, Inno.Editor.Rendering.IEditorPreviewService previews, Inno.Editor.Inspection.InspectionDrawContext inspection, bool readOnly)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L169) | Creates invocation-scoped access for a graph or Inspector host.
`node`: Detached node values for this invocation.
`serialization`: Current owner converter registry.
`context`: Complete owner reference context.
`write`: Host callback recording changes in the shared draft history.
`previews`: Current generation preview service.
`inspection`: Shared Inspector context, including native property controls and drag/drop.
`readOnly`: Whether the source belongs to an immutable installation. | — | — | +| [`public Inno.Editor.Rendering.IEditorPreviewService Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.previews { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L190) | Gets the shared generation-scoped preview service for optional inline texture previews; valid only during this draw. | — | — | +| [`public Inno.Core.Graphs.GraphNodeId Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.nodeId { get; }`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L194) | Gets the stable node identity for Editor widget IDs. | — | — | +| [`public T Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.Read(string key, T defaultValue)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L211) | Reads a detached property value; missing values use the node's declared default.
`key`: Stable node-local property key.
`defaultValue`: Default used only when the property is absent.
`T`: Native serializable property type. | The detached value. Malformed stored data is not silently replaced. | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.Write(string key, T value, string? mergeKey = null)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L230) | Records an unsaved draft edit through shared history; only an explicit document save applies it to the asset.
`key`: Stable node-local property key.
`value`: New detached value.
`mergeKey`: The current shared gesture key, or null for an independent edit.
`T`: Native serializable property type. | — | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext.DrawProperty(string key, string label, T defaultValue)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs#L255) | Draws a node property through the existing Inspector controls, including precise numbers and asset picking.
`key`: Stable node property identity.
`label`: Artist-facing property label.
`defaultValue`: Value used only when no property is stored.
`T`: Supported native property type. | — | — | +| [`public sealed class Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry : System.IDisposable`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L12) | Shares generation-scoped node presentation between graph and Inspector hosts. | — | — | +| [`public Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry.ShaderNodeDrawerRegistry(Inno.Extensibility.Types.TypeCatalog types)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L22) | Creates a presentation registry owned by the current editor feature lifetime.
`types`: Shared type catalog, which must outlive this registry. | — | — | +| [`public bool Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry.TryDraw(string definitionId, Inno.Editor.Shaders.ImGui.ShaderNodeDrawContext context)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L40) | Invokes optional node controls within one generation lease.
`definitionId`: Stable node definition identity.
`context`: Invocation-scoped host values, history callback and previews. | True when a registered drawer rendered the controls; false leaves presentation to the host. | — | +| [`public string? Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry.GetDisplayName(string definitionId)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L60) | Resolves an optional artist-facing name without executing a drawer or retaining its generation.
`definitionId`: Stable compiler-independent node identity. | The contributed name, or null when the host should use its generic presentation. | — | +| [`public bool Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry.TryGetPresentation(string definitionId, out Inno.Editor.Shaders.ImGui.ShaderNodePresentation presentation)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L78) | Gets optional creation-menu presentation without executing the drawer.
`definitionId`: Stable compiler-independent node identity.
`presentation`: Receives immutable creation-menu presentation when registered. | True when the current generation contributes presentation for the node; otherwise false. | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderNodeDrawerRegistry.Dispose()`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs#L89) | Retires the current presentation providers through the shared lifecycle. | — | — | +| [`public sealed class Inno.Editor.Shaders.ImGui.ShaderPreviews : Inno.Editor.Core.EditorModule`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L21) | Hosts isolated Shader and Material images using domain-contributed preview pipelines. | — | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderPreviews.DrawMaterial(System.Guid ownerId, Inno.Rendering.Assets.MaterialAsset material, float logicalSize)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L52) | Draws detached Material overrides using its Shader's compiled contract.
`ownerId`: Persistent document identity; no Material is retained under this identity.
`material`: Invocation-local Material values.
`logicalSize`: Positive square image size in logical pixels. | — | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderPreviews.Draw(System.Guid ownerId, Inno.Rendering.Assets.MaterialAsset material, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot compilation, float logicalSize)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L88) | Draws a compiled draft without modifying the source Shader or its canonical runtime publication.
`ownerId`: Persistent document identity, independent of the canonical Shader publication.
`material`: Detached defaults or overrides with the current Shader reference.
`compilation`: Isolated draft compiler result, including explicit last-good state.
`logicalSize`: Positive square image size in logical pixels. | — | — | +| [`public void Inno.Editor.Shaders.ImGui.ShaderPreviews.Release(System.Guid ownerId)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L149) | Releases this document's viewport and scoped GPU publication without touching other consumers.
`ownerId`: Closing or disabled preview owner. | — | — | +| [`protected override void Inno.Editor.Shaders.ImGui.ShaderPreviews.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L161) | Advances this feature using the current runtime state.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Shaders.ImGui.ShaderPreviews.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs#L174) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | + +## 项目依赖 + +| 工程 | 边界 | +| --- | --- | +| `Inno.Assets.Pipeline` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Core.Diagnostics` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Runtime` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Assets.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Core.Graphs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Rendering.Assets` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Editor.Rendering` | 公开签名依赖 | +| `Inno.Editor.Shaders` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Inspection` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Shaders.md b/docs/editor/Inno.Editor.Shaders.md index 464034f5b..ee0592ce4 100644 --- a/docs/editor/Inno.Editor.Shaders.md +++ b/docs/editor/Inno.Editor.Shaders.md @@ -1,231 +1,56 @@ # Inno.Editor.Shaders -[Editor 索引](README.md) · [Wiki 首页](../README.md) · [Shader Editor](Inno.Editor.Panel.ShaderEditor.md) · [Shader 编译](../rendering/Inno.Rendering.Shaders.md) +[Editor 索引](README.md) · [Wiki 首页](../README.md) · [Shader 显示扩展](Inno.Editor.Shaders.ImGui.md) · [Rendering](Inno.Editor.Rendering.md) ## 职责与边界 -可复用的 Shader/Material 创作功能。节点编译属于 Rendering.Shaders;呈现协议独立于具体 Panel。节点配置在统一 Inspector 编辑,画布保留摘要与连接。 +本项目拥有无界面的 Material 源草稿与 Shader 创作数据服务。绘制、节点 Drawer 和预览控件由 Shaders.ImGui 持有;本项目不引用 ImGui、Panel 或原生 UI。 -优先用 `.ishader` 的 Node Inputs / Node Outputs 定义组合型多输入、多输出节点;宿主会自动提供创建菜单、端口 Inspector、依赖、内联和循环诊断。只有新增公共 IR 无法表达的原语时才实现 `IShaderNodeCompiler`,只有节点确实需要专用交互时才增加 `ShaderNodeDrawer`。 +## 初始化与依赖 -## 初始化与生命周期 +MaterialDocuments 由 Editor Module Registry 创建,依赖 AssetPipeline、SerializationRegistry、EditorInteractions 和中立 IEditorPreviewService。底层 AssetDraftDocuments 提供编解码、恢复、dirty 状态和中立 History。Shutdown 清理本代缓存并保留未保存的 recovery。 -宿主在 TypeCatalog 与预览服务建立后创建 ShaderNodeDrawerRegistry,在 Editor feature 停止时 Dispose。 -每次查询与绘制持有共享 generation operation;候选发现重复 ID 时失败,旧 provider 按 TypeRegistry 的事务与退休协议管理。 -Drawer 不得保存 DrawContext、PreviewHandle、Asset 或历史回调到下一帧/下一代。 +## 公开 API 与工作流 -## 公开 API +Open(path) 打开源,Read(id) 返回 detached Material,Replace(id, candidate, mergeKey) 立即记录修改。ReplaceMany 预验证并原子记录多文档 sample。连续输入由共享 EditorEditGesture 定义;mergeKey 不代表保存。Save/Revert/Close 通过统一 documents 服务完成。 -| API | 语义 | -| --- | --- | -| `ShaderNodeDrawerAttribute(definitionId, displayName, createPath, createOrder, separatorBefore)` | 稳定节点 ID、可选显示名称与节点创建菜单呈现;插件可贡献自己的分组、顺序和同级分隔,不参与编译语义 | -| `ShaderNodeDrawer.Draw(context)` | 统一 Inspector 中的帧内绘制入口 | -| `ShaderNodeDrawContext(node, serialization, context, write, previews, inspection, readOnly)` | 独立节点快照、完整 owner context、统一草稿 History 写入、预览与共享 Inspector 上下文 | -| `nodeId`、`previews` | 稳定控件身份和当前 generation 预览服务 | -| `Read(key, defaultValue)` | 解码独立属性,缺失使用声明默认值,损坏明确失败 | -| `Write(key, value, continuous)` | 向宿主提交中立值;进入草稿 History,不保存或发布 Asset | -| `DrawProperty(key, label, defaultValue)` | 复用现有属性 Drawer、资产选择/拖放和数值控件;受草稿 History 与只读检查约束 | -| `ShaderNodeDrawerRegistry(types)`、`TryDraw(id, context)`、`Dispose()` | 按 generation 发现、调用和退休呈现;缺少 Drawer 时返回 false | -| `GetDisplayName(definitionId)` | 获取本代际中立显示名称;未贡献名称时返回 null,由宿主提供通用名称 | -| `TryGetPresentation(definitionId, out presentation)` | 获取本代际不可变创建菜单呈现;未贡献时由 Shader Editor 使用内置中立分类 | -| `MaterialDocuments.Open(path)`、`Read(id)`、`Replace(id, material, finishGesture)`、`Commit(id)` | 原生 Material 草稿;读回的是独立可编辑对象,显式保存前不发布 | -| `MaterialDocuments.ReplaceMany(candidates, finishGesture)`、`CommitMany(ids)` | 一组兼容材质共享一次 History 事务,不提供跨文件原子保存承诺 | -| `ShaderPropertyInspector.Draw(...)`、`Compatible(type, kind)` | Shader 默认值与 Material 覆盖复用属性 Drawer;Float/向量/线性 HDR Color/Matrix/Texture/Sampler,精确类型匹配 | -| `ShaderParameterPresentation`:`group`、`description`、`visible`、`hasRange`、`minimum`、`maximum` | Editor-only 参数展示。按稳定绑定 ID 共享,不改变已有数值,不进入运行时定义或 Player | -| `ShaderParameterPresentation.Read(graph, propertyId, serialization, context)`、`Write(...)` | 读写独立图中的原生展示元数据;调用方负责共享 History。范围必须有限且有序;不是资产保存 API | -| `ShaderPreviewProviderAttribute(contractId)`、`contractId` | 按开放 Shader Contract 注册预览;重复契约阻止候选激活 | -| `ShaderPreviewProvider.CreateLayer(context)` | 插件贡献几何、环境、Pass 输入和 Pipeline,不修改 Scene 或 canonical Material | -| `ShaderPreviewContext(resourceId, material, artifact, definition, diagnostics, pixelWidth, pixelHeight)` 及同名只读属性 | 帧内的独立发布范围、草稿值、不可变编译产物、精确接口、隔离诊断和像素尺寸;禁止跨帧保留 | -| `ShaderPreviews.DrawMaterial(ownerId, material, logicalSize)` | 对独立 Material 覆盖值执行实际 GPU 预览,不保存或应用源资产 | -| `ShaderPreviews.Draw(ownerId, material, compilation, logicalSize)` | Shader 草稿编译结果的实际 GPU 预览;失败时明确标识 last-good | -| `ShaderPreviews.Release(ownerId)` | 关闭预览并释放对应视口和 GPU 程序;未绘制的预览自动退休 | - -公共签名涉及 Graph、Serialization、Rendering Preview 与 TypeCatalog,其项目引用向下游公开。 -节点编译与脚本导出依赖仅作为实现依赖。 +ShaderParameterPresentation 存储 Editor-only 的 group、description、visible 和有限范围。它使用原生 Serialization、稳定绑定 ID 和 owner reference context;不修改 runtime Shader 接口或现有参数值。 ```csharp +using System; using InnoEditor.Shaders; -[ShaderNodeDrawer("example.surface")] -public sealed class SurfaceDrawer : ShaderNodeDrawer -{ - public override void Draw(ShaderNodeDrawContext context) - { - context.DrawProperty("gain", "Gain", 1f); - } +static void ReadCurrentDraft( + MaterialDocuments documents, + Guid assetId +) { + _ = documents.Read(assetId); } ``` -Editor 脚本导出 Drawer、预览 Provider、标记、帧内 Context、MaterialDocuments、ShaderPreviews 和 ShaderPropertyInspector,命名空间为 `InnoEditor.Shaders`。Registry 由宿主管理。 - -## 实际渲染预览 - -Material Inspector 顶部独立的 Preview 区域与 Shader Inspector 中始终可见的 Preview 共用此模块。没有可编辑属性与关键词时,Parameters 区域显示空状态说明。输出预览表示整个 Shader 的结果,不冒充任意中间节点的数值可视化。 -领域通过 `ShaderPreviewProvider` 提供与 Shader Contract 匹配的预览,使用普通 Render Graph 和独立材质覆盖绘制;预览资源由该预览作用域持有。 -不同领域通过自己的 Contract 提供网格、环境与 Pass 参数,通用引擎没有 Sprite 分支。若多种可预览 Contract 同时匹配,要求选择 Technique,不按发现顺序猜测。 - -预览 Shader 使用隔离编译产物;Material 使用当前 Shader 产物和独立覆盖。GPU 创建、反射/能力校验、绑定以及 last-good 复用正式资源链,但缓存键包含独立 owner scope,错误只进入预览 reporter。 -预览帧只保留本帧的 Provider 结果;后续状态仅保存稳定 ID、计数和中立诊断。宿主 generation 切换时统一清理视口及注册,Provider 通过 TypeRegistry 候选事务退休。 - -## Material 工作流 - -File Browser 的 Create 菜单支持创建 Material 和从选中 Shader 创建 Material。单击材质进入 Inspector;Command/Ctrl+单击可选择多个材质,兼容参数在同一 Inspector 中共同编辑。 -继承值与独立 override 分开显示,支持单项/全部重置、关键词、Technique,以及更换 Shader 后保留的未解析参数。 -颜色值按线性 RGBA、允许 HDR 输入,不对底层浮点数作显示精度截断。纹理选择/拖放与采样器使用既有属性 Drawer。 +## 生命周期、错误与热重载 -参数节点 Inspector 可编辑分组、hover 说明、材质可见性与 Float 编辑范围。多选材质只显示共同可见且类型兼容的参数,数值范围使用交集;不把改动前已超界的值静默钳制。隐藏是创作 UI 策略,不禁止运行时通过稳定 ID 设置参数。 -展示记录保存在 `inno.editor.parameter.*` 图 metadata,由同一参数的所有节点共享。语义指纹排除此保留前缀;仅改展示不重编译 GPU 程序。Material 使用已导入的 Shader 创作快照,按资产 contentVersion 缓存,不每帧重读整个图;未保存 Shader 草稿不会改变 Material Inspector。 +Canonical Material、引用资产与运行时发布值始终由 Assets owner 管理。草稿只保存中立 bytes,显式保存前不发布。源冲突保留草稿与诊断;重开文档后 History 按 persistent ID 恢复。缺少源或类型形成可恢复 barrier。不要跨 callback 保存 detached Asset、Type、preview 或 Registry 实例。 -保存复用 headless Document Service;Inspector Header 提供 Save/Revert,关闭确认和全部保存仍走同一服务,不另建状态系统。MaterialDocuments 组合 [AssetDraftDocuments](Inno.Editor.Assets.md),恢复数据在 Library/Editor/AssetDrafts/inno.material;外部修改冲突保留两份内容,不覆盖外部源。 -资产/节点多选只保存稳定 ID;Drawer 重新查询当前 generation,不把节点选择当作 File Browser 选择。 -完整渲染预览及 UI 验收仍见[当前验收](../architecture/CURRENT_ACCEPTANCE.md),不能把编译预览等同为画面预览。 +公开签名与对应源码见下方清单;其他内部类型不是稳定 API。 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Editor.Shaders.MaterialDocuments` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + - - - - + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Shaders.MaterialDocuments.Commit(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L92) | Finishes a draft gesture without saving its source.
`assetId`:Open Material source identity. | — | — | -| [`public void Inno.Editor.Shaders.MaterialDocuments.CommitMany(System.Collections.Generic.IEnumerable assetIds)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L115) | Completes a multi-document gesture as one shared History transaction.
`assetIds`:Participating persistent source identities. | — | — | -| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L172) | Initializes this feature when its owning runtime becomes active.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L186) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L179) | Advances this feature using the current runtime state.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public System.Guid Inno.Editor.Shaders.MaterialDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L54) | Opens native source, including a source with failed import, without publishing its draft.
返回:The persistent identity used for subsequent draft operations.
`path`:Mounted Material source path. | — | — | -| [`public Inno.Rendering.Assets.MaterialAsset Inno.Editor.Shaders.MaterialDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L65) | Reads a detached current-generation Material; referenced assets remain canonical read-only inputs.
返回:The current unsaved Material value.
`assetId`:Open Material source identity. | — | — | -| [`public void Inno.Editor.Shaders.MaterialDocuments.Replace(System.Guid assetId, Inno.Rendering.Assets.MaterialAsset candidate, bool finishGesture = true)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L79) | Changes a draft through shared History without modifying its source or Scene/Game.
`assetId`:Open Material source identity.
`candidate`:Detached edited Material.
`finishGesture`:Whether this sample finishes the active gesture. | — | — | -| [`public void Inno.Editor.Shaders.MaterialDocuments.ReplaceMany(System.Collections.Generic.IReadOnlyDictionary candidates, bool finishGesture = true)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L103) | Changes compatible selected drafts within one shared gesture.
`candidates`:Detached Material values keyed by persistent source identity.
`finishGesture`:Whether this sample finishes the shared gesture. | — | — | -| [`public sealed class Inno.Editor.Shaders.MaterialDocuments : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L24) | Owns Material authoring sessions; native source editing uses the shared asset draft lifecycle. | — | — | - -### `Inno.Editor.Shaders.ShaderInspectionSelection` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderInspectionSelection.ShaderInspectionSelection(System.Guid assetId, System.Collections.Generic.IEnumerable nodes)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L23) | Creates a neutral Inspector selection separate from the Asset Browser's navigation.
`assetId`:Persistent shader asset identity.
`nodes`:Selected stable node identities; empty selects document settings. | — | — | -| [`public System.Guid Inno.Editor.Shaders.ShaderInspectionSelection.assetId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L36) | Gets the persistent shader identity. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Shaders.ShaderInspectionSelection.nodes { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L40) | Gets selected stable node identities. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderInspectionSelection`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L11) | Identifies a shader document and selected nodes without retaining a graph, extension object or delegate. | — | — | - -### `Inno.Editor.Shaders.ShaderNodeDrawContext` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderNodeDrawContext.ShaderNodeDrawContext(Inno.Core.Graphs.GraphNodeRecord node, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context, System.Action write, Inno.Editor.Rendering.IEditorPreviewService previews, Inno.Editor.Inspection.InspectionDrawContext inspection, bool readOnly)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L172) | Creates invocation-scoped access for a graph or Inspector host.
`node`:Detached node values for this invocation.
`serialization`:Current owner converter registry.
`context`:Complete owner reference context.
`write`:Host callback recording changes in the shared draft history.
`previews`:Current generation preview service.
`inspection`:Shared Inspector context, including native property controls and drag/drop.
`readOnly`:Whether the source belongs to an immutable installation. | — | — | -| [`public void Inno.Editor.Shaders.ShaderNodeDrawContext.DrawProperty(string key, string label, T defaultValue)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L261) | Draws a node property through the existing Inspector controls, including precise numbers and asset picking.
`key`:Stable node property identity.
`label`:Artist-facing property label.
`defaultValue`:Value used only when no property is stored. | — | — | -| [`public T Inno.Editor.Shaders.ShaderNodeDrawContext.Read(string key, T defaultValue)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L215) | Reads a detached property value; missing values use the node's declared default.
返回:The detached value. Malformed stored data is not silently replaced.
`key`:Stable node-local property key.
`defaultValue`:Default used only when the property is absent. | — | — | -| [`public void Inno.Editor.Shaders.ShaderNodeDrawContext.Write(string key, T value, bool continuous = false)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L236) | Records an unsaved draft edit through shared history; only an explicit document save applies it to the asset.
`key`:Stable node-local property key.
`value`:New detached value.
`continuous`:Whether this sample belongs to the active text or numeric gesture. | — | — | -| [`public Inno.Core.Graphs.GraphNodeId Inno.Editor.Shaders.ShaderNodeDrawContext.nodeId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L198) | Gets the stable node identity for Editor widget IDs. | — | — | -| [`public Inno.Editor.Rendering.IEditorPreviewService Inno.Editor.Shaders.ShaderNodeDrawContext.previews { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L193) | Gets the shared generation-scoped preview service for optional inline texture previews; valid only during this draw. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderNodeDrawContext`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L140) | Exposes detached node values and one-gesture writes without exposing the live graph document. | — | — | - -### `Inno.Editor.Shaders.ShaderNodeDrawer` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Shaders.ShaderNodeDrawer.ShaderNodeDrawer()`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L126) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract void Inno.Editor.Shaders.ShaderNodeDrawer.Draw(Inno.Editor.Shaders.ShaderNodeDrawContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L134) | Draws the selected node's controls using the shared Inspector styling.
`context`:Frame-scoped value access and undoable writes. | — | — | -| [`public abstract class Inno.Editor.Shaders.ShaderNodeDrawer`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L126) | Provides reloadable Inspector controls; instances must not retain frame contexts or asset objects. | — | — | - -### `Inno.Editor.Shaders.ShaderNodeDrawerAttribute` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderNodeDrawerAttribute.ShaderNodeDrawerAttribute(string definitionId, string displayName = "", string createPath = "", int createOrder = 0, bool separatorBefore = false)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L34) | Associates the drawer with one stable node definition.
`definitionId`:The compiler-independent graph node identity.
`displayName`:Optional artist-facing name used in node headers and creation menus.
`createPath`:Optional slash-delimited path below Create. Empty uses the host's built-in classification.
`createOrder`:Stable ordering value among sibling creation groups and nodes.
`separatorBefore`:Whether the contributed creation group is preceded by a separator at its own level. | — | — | -| [`public int Inno.Editor.Shaders.ShaderNodeDrawerAttribute.createOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L63) | Gets the creation-menu order. | — | — | -| [`public string Inno.Editor.Shaders.ShaderNodeDrawerAttribute.createPath { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L59) | Gets the optional creation-menu path below Create. | — | — | -| [`public string Inno.Editor.Shaders.ShaderNodeDrawerAttribute.definitionId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L51) | Gets the stable node identity handled by this drawer. | — | — | -| [`public string Inno.Editor.Shaders.ShaderNodeDrawerAttribute.displayName { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L55) | Gets the optional presentation-only name; it does not participate in Shader compilation or identity. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderNodeDrawerAttribute.separatorBefore { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L67) | Gets whether the contributed creation group starts a new visual section. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderNodeDrawerAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L14) | Registers Editor-only node controls independently from a node's shader compiler. | — | — | - -### `Inno.Editor.Shaders.ShaderNodeDrawerRegistry` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderNodeDrawerRegistry.ShaderNodeDrawerRegistry(Inno.Extensibility.Types.TypeCatalog types)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L23) | Creates a presentation registry owned by the current editor feature lifetime.
`types`:Shared type catalog, which must outlive this registry. | — | — | -| [`public void Inno.Editor.Shaders.ShaderNodeDrawerRegistry.Dispose()`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L90) | Retires the current presentation providers through the shared lifecycle. | — | — | -| [`public string? Inno.Editor.Shaders.ShaderNodeDrawerRegistry.GetDisplayName(string definitionId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L61) | Resolves an optional artist-facing name without executing a drawer or retaining its generation.
返回:The contributed name, or null when the host should use its generic presentation.
`definitionId`:Stable compiler-independent node identity. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderNodeDrawerRegistry.TryDraw(string definitionId, Inno.Editor.Shaders.ShaderNodeDrawContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L41) | Invokes optional node controls within one generation lease.
返回:True when a registered drawer rendered the controls; false leaves presentation to the host.
`definitionId`:Stable node definition identity.
`context`:Invocation-scoped host values, history callback and previews. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderNodeDrawerRegistry.TryGetPresentation(string definitionId, out Inno.Editor.Shaders.ShaderNodePresentation presentation)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L79) | Gets optional creation-menu presentation without executing the drawer.
返回:True when the current generation contributes presentation for the node; otherwise false.
`definitionId`:Stable compiler-independent node identity.
`presentation`:Receives immutable creation-menu presentation when registered. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderNodeDrawerRegistry : System.IDisposable`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs#L12) | Shares generation-scoped node presentation between graph and Inspector hosts. | — | — | - -### `Inno.Editor.Shaders.ShaderNodePresentation` - - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderNodePresentation.ShaderNodePresentation(string displayName, string createPath, int createOrder, bool separatorBefore)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L90) | Creates presentation metadata for one Shader node definition.
`displayName`:Optional artist-facing node name.
`createPath`:Optional slash-delimited creation-menu path.
`createOrder`:Stable ordering value within the creation menu.
`separatorBefore`:Whether the contributed group starts a visual section. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderNodePresentation.Equals(Inno.Editor.Shaders.ShaderNodePresentation other)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Editor.Shaders.ShaderNodePresentation.Equals(object obj)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Editor.Shaders.ShaderNodePresentation.GetHashCode()`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Editor.Shaders.ShaderNodePresentation.operator ==(Inno.Editor.Shaders.ShaderNodePresentation left, Inno.Editor.Shaders.ShaderNodePresentation right)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Editor.Shaders.ShaderNodePresentation.operator !=(Inno.Editor.Shaders.ShaderNodePresentation left, Inno.Editor.Shaders.ShaderNodePresentation right)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Editor.Shaders.ShaderNodePresentation.ToString()`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public int Inno.Editor.Shaders.ShaderNodePresentation.createOrder { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L115) | Gets the stable ordering value within the creation menu. | — | — | -| [`public string Inno.Editor.Shaders.ShaderNodePresentation.createPath { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L110) | Gets the optional slash-delimited creation-menu path. | — | — | -| [`public string Inno.Editor.Shaders.ShaderNodePresentation.displayName { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L105) | Gets the optional artist-facing node name. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderNodePresentation.separatorBefore { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L120) | Gets whether the contributed group starts a visual section. | — | — | -| [`public readonly record struct Inno.Editor.Shaders.ShaderNodePresentation : System.IEquatable`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs#L73) | Immutable authoring presentation contributed for one Shader node definition. | — | — | - -### `Inno.Editor.Shaders.ShaderParameterPresentation` - @@ -236,22 +61,16 @@ File Browser 的 Create 菜单支持创建 Material 和从选中 Shader 创建 M - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderParameterPresentation.ShaderParameterPresentation()`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L12) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public static Inno.Editor.Shaders.ShaderParameterPresentation Inno.Editor.Shaders.ShaderParameterPresentation.Read(Inno.Core.Graphs.GraphDocument graph, Inno.Rendering.ShaderPropertyId propertyId, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L59) | Reads detached presentation for one binding, defaulting only when no presentation was authored.
返回:The authored presentation, or ordinary visible, unbounded defaults.
`graph`:Authoring graph, not a runtime Shader program.
`propertyId`:Stable parameter identity shared by all its nodes.
`serialization`:Current owner converters.
`context`:Complete owner serialization context. | — | — | -| [`public static void Inno.Editor.Shaders.ShaderParameterPresentation.Write(Inno.Core.Graphs.GraphDocument graph, Inno.Rendering.ShaderPropertyId propertyId, Inno.Editor.Shaders.ShaderParameterPresentation presentation, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L92) | Writes presentation to a detached graph without changing any default, Material, or compiled program.
`graph`:Detached graph owned by the caller's History transaction.
`propertyId`:Stable parameter identity.
`presentation`:Detached Inspector settings.
`serialization`:Current owner converters.
`context`:Complete owner serialization context. | — | `System.ArgumentException`:The identity or range is invalid, or a bound cannot be represented by a scalar Shader Float. | -| [`public string Inno.Editor.Shaders.ShaderParameterPresentation.description { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L23) | Gets or sets the hover description without adding a permanent paragraph. | — | — | -| [`public string Inno.Editor.Shaders.ShaderParameterPresentation.group { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L19) | Gets or sets the Material Inspector section; empty uses the standard Parameters section. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderParameterPresentation.hasRange { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L31) | Gets or sets whether a scalar Float control uses the declared editing bounds. | — | — | -| [`public double Inno.Editor.Shaders.ShaderParameterPresentation.maximum { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L39) | Gets or sets the inclusive editing maximum. | — | — | -| [`public double Inno.Editor.Shaders.ShaderParameterPresentation.minimum { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L35) | Gets or sets the inclusive editing minimum; existing values are not changed by presentation metadata. | — | — | -| [`public bool Inno.Editor.Shaders.ShaderParameterPresentation.visible { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L27) | Gets or sets whether this Material-owned parameter appears in ordinary Material Inspectors. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderParameterPresentation : Inno.Core.Serialization.ISerializable`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L12) | Stores Editor-only parameter presentation by stable binding identity in the Shader's authoring graph. | — | — | - -### `Inno.Editor.Shaders.ShaderPreviewContext` - + + + + + + + + + + @@ -262,96 +81,71 @@ File Browser 的 Create 菜单支持创建 Material 和从选中 Shader 创建 M -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderPreviewContext.ShaderPreviewContext(Inno.Rendering.Runtime.RenderPersistentResourceId resourceId, Inno.Rendering.Assets.MaterialAsset material, Inno.Rendering.Assets.RenderShaderArtifact artifact, Inno.Rendering.Assets.ShaderDefinition definition, Inno.Core.Diagnostics.IDiagnosticReporter diagnostics, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L72) | Creates a preview invocation from host-owned detached inputs.
`resourceId`:Isolated material publication and viewport resource identity.
`material`:Detached values with a current Shader reference; do not modify the reference.
`artifact`:Compiled candidate, never published as a canonical Shader.
`definition`:Detached contract captured by the candidate.
`diagnostics`:Preview-local reporter; errors must not contaminate project diagnostics.
`pixelWidth`:Positive target width.
`pixelHeight`:Positive target height. | — | — | +| [`public sealed class Inno.Editor.Shaders.MaterialDocuments : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L23) | Owns Material authoring sessions; native source editing uses the shared asset draft lifecycle. | — | — | +| [`public Inno.Assets.Pipeline.AssetPipeline Inno.Editor.Shaders.MaterialDocuments.assets { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L33) | Gets the authoritative asset catalog used by source-aware inspectors. | — | — | +| [`public Inno.Editor.Rendering.IEditorPreviewService Inno.Editor.Shaders.MaterialDocuments.previews { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L40) | Gets the generation-scoped preview service shared by material presentations. | — | — | +| [`public System.Guid Inno.Editor.Shaders.MaterialDocuments.Open(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L64) | Opens native source, including a source with failed import, without publishing its draft.
`path`: Mounted Material source path. | The persistent identity used for subsequent draft operations. | — | +| [`public Inno.Rendering.Assets.MaterialAsset Inno.Editor.Shaders.MaterialDocuments.Read(System.Guid assetId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L74) | Reads a detached current-generation Material; referenced assets remain canonical read-only inputs.
`assetId`: Open Material source identity. | The current unsaved Material value. | — | +| [`public void Inno.Editor.Shaders.MaterialDocuments.Replace(System.Guid assetId, Inno.Rendering.Assets.MaterialAsset candidate, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L87) | Changes a draft through shared History without modifying its source or Scene/Game.
`assetId`: Open Material source identity.
`candidate`: Detached edited Material.
`mergeKey`: The current gesture key, or null for a discrete edit. | — | — | +| [`public void Inno.Editor.Shaders.MaterialDocuments.ReplaceMany(System.Collections.Generic.IReadOnlyDictionary candidates, string? mergeKey = null)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L101) | Changes compatible selected drafts within one shared gesture.
`candidates`: Detached Material values keyed by persistent source identity.
`mergeKey`: The current gesture key, or null for a discrete edit. | — | — | +| [`public Inno.Editor.Assets.AssetDraftDocuments.Draft Inno.Editor.Shaders.MaterialDocuments.OpenDraft(Inno.Assets.AssetPath path)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L115) | Opens the current source draft without publishing it.
`path`: The isolated Material source path. | The generation-scoped draft; callers must not retain it across a reload. | — | +| [`public Inno.Editor.Assets.AssetDraftDocuments.Draft Inno.Editor.Shaders.MaterialDocuments.Open(Inno.Rendering.Assets.MaterialAsset material)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L126) | Resolves the source draft for a canonical Material identity.
`material`: The current canonical Material whose source is edited. | The generation-scoped editable source draft. | — | +| [`public Inno.Editor.Shaders.ShaderParameterPresentation Inno.Editor.Shaders.MaterialDocuments.Presentation(Inno.Rendering.Assets.ShaderAsset shader, Inno.Rendering.ShaderPropertyId propertyId, out string error)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L149) | Reads parameter presentation from the current authoring Shader graph.
`shader`: The canonical Shader used by the Material.
`propertyId`: The stable binding identity.
`error`: Receives the source diagnostic, or an empty string when presentation is available. | Detached presentation values; an unavailable graph uses the ordinary presentation defaults. | — | +| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnStart(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L195) | Initializes this feature when its owning runtime becomes active.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L202) | Advances this feature using the current runtime state.
`context`: The context that supplies state and services for this operation. | — | — | +| [`protected override void Inno.Editor.Shaders.MaterialDocuments.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs#L209) | Stops this feature before its owning runtime releases the active generation.
`context`: The context that supplies state and services for this operation. | — | — | +| [`public sealed class Inno.Editor.Shaders.ShaderParameterPresentation : Inno.Core.Serialization.ISerializable`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L12) | Stores Editor-only parameter presentation by stable binding identity in the Shader's authoring graph. | — | — | +| [`public string Inno.Editor.Shaders.ShaderParameterPresentation.group { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L19) | Gets or sets the Material Inspector section; empty uses the standard Parameters section. | — | — | +| [`public string Inno.Editor.Shaders.ShaderParameterPresentation.description { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L23) | Gets or sets the hover description without adding a permanent paragraph. | — | — | +| [`public bool Inno.Editor.Shaders.ShaderParameterPresentation.visible { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L27) | Gets or sets whether this Material-owned parameter appears in ordinary Material Inspectors. | — | — | +| [`public bool Inno.Editor.Shaders.ShaderParameterPresentation.hasRange { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L31) | Gets or sets whether a scalar Float control uses the declared editing bounds. | — | — | +| [`public double Inno.Editor.Shaders.ShaderParameterPresentation.minimum { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L35) | Gets or sets the inclusive editing minimum; existing values are not changed by presentation metadata. | — | — | +| [`public double Inno.Editor.Shaders.ShaderParameterPresentation.maximum { get; set; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L39) | Gets or sets the inclusive editing maximum. | — | — | +| [`public static Inno.Editor.Shaders.ShaderParameterPresentation Inno.Editor.Shaders.ShaderParameterPresentation.Read(Inno.Core.Graphs.GraphDocument graph, Inno.Rendering.ShaderPropertyId propertyId, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L59) | Reads detached presentation for one binding, defaulting only when no presentation was authored.
`graph`: Authoring graph, not a runtime Shader program.
`propertyId`: Stable parameter identity shared by all its nodes.
`serialization`: Current owner converters.
`context`: Complete owner serialization context. | The authored presentation, or ordinary visible, unbounded defaults. | — | +| [`public static void Inno.Editor.Shaders.ShaderParameterPresentation.Write(Inno.Core.Graphs.GraphDocument graph, Inno.Rendering.ShaderPropertyId propertyId, Inno.Editor.Shaders.ShaderParameterPresentation presentation, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Core.Serialization.SerializationContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L92) | Writes presentation to a detached graph without changing any default, Material, or compiled program.
`graph`: Detached graph owned by the caller's History transaction.
`propertyId`: Stable parameter identity.
`presentation`: Detached Inspector settings.
`serialization`: Current owner converters.
`context`: Complete owner serialization context. | — | `System.ArgumentException`: The identity or range is invalid, or a bound cannot be represented by a scalar Shader Float. | +| [`public Inno.Editor.Shaders.ShaderParameterPresentation.ShaderParameterPresentation()`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderParameterPresentation.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Shaders.ShaderInspectionSelection`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L11) | Identifies a shader document and selected nodes without retaining a graph, extension object or delegate. | — | — | +| [`public Inno.Editor.Shaders.ShaderInspectionSelection.ShaderInspectionSelection(System.Guid assetId, System.Collections.Generic.IEnumerable nodes)`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L23) | Creates a neutral Inspector selection separate from the Asset Browser's navigation.
`assetId`: Persistent shader asset identity.
`nodes`: Selected stable node identities; empty selects document settings. | — | — | +| [`public System.Guid Inno.Editor.Shaders.ShaderInspectionSelection.assetId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L36) | Gets the persistent shader identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Editor.Shaders.ShaderInspectionSelection.nodes { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderInspectionSelection.cs#L40) | Gets selected stable node identities. | — | — | +| [`public sealed class Inno.Editor.Shaders.ShaderPreviewProviderAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L17) | Associates a reloadable preview implementation with one open Shader contract.
`contractId`: Non-empty domain-owned contract identity. | — | — | +| [`public Inno.Editor.Shaders.ShaderPreviewProviderAttribute.ShaderPreviewProviderAttribute(string contractId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L17) | Associates a reloadable preview implementation with one open Shader contract.
`contractId`: Non-empty domain-owned contract identity. | — | — | +| [`public string Inno.Editor.Shaders.ShaderPreviewProviderAttribute.contractId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L22) | Gets the non-empty Shader contract consumed by this preview. | — | — | +| [`public abstract class Inno.Editor.Shaders.ShaderPreviewProvider`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L29) | Builds domain-owned preview geometry and pass inputs without changing a scene or canonical asset. | — | — | +| [`public abstract Inno.Editor.Rendering.EditorViewportLayer Inno.Editor.Shaders.ShaderPreviewProvider.CreateLayer(Inno.Editor.Shaders.ShaderPreviewContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L40) | Creates one frame-local rendering layer through the ordinary Render Graph.
`context`: Detached Material, immutable candidate and isolated diagnostics for this invocation. | A domain-selected pipeline and frame payload; no instance may be retained beyond the submitted frame. | — | +| [`protected Inno.Editor.Shaders.ShaderPreviewProvider.ShaderPreviewProvider()`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L29) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Editor.Shaders.ShaderPreviewContext`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L46) | Carries frame-only preview inputs; persistent provider state must retain stable values rather than this object. | — | — | +| [`public Inno.Editor.Shaders.ShaderPreviewContext.ShaderPreviewContext(Inno.Rendering.Runtime.RenderPersistentResourceId resourceId, Inno.Rendering.Assets.MaterialAsset material, Inno.Rendering.Assets.RenderShaderArtifact artifact, Inno.Rendering.Assets.ShaderDefinition definition, Inno.Core.Diagnostics.IDiagnosticReporter diagnostics, int pixelWidth, int pixelHeight)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L72) | Creates a preview invocation from host-owned detached inputs.
`resourceId`: Isolated material publication and viewport resource identity.
`material`: Detached values with a current Shader reference; do not modify the reference.
`artifact`: Compiled candidate, never published as a canonical Shader.
`definition`: Detached contract captured by the candidate.
`diagnostics`: Preview-local reporter; errors must not contaminate project diagnostics.
`pixelWidth`: Positive target width.
`pixelHeight`: Positive target height. | — | — | +| [`public Inno.Rendering.Runtime.RenderPersistentResourceId Inno.Editor.Shaders.ShaderPreviewContext.resourceId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L92) | Gets the publication scope released by the host when this preview closes. | — | — | +| [`public Inno.Rendering.Assets.MaterialAsset Inno.Editor.Shaders.ShaderPreviewContext.material { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L96) | Gets the invocation-local Material values. | — | — | | [`public Inno.Rendering.Assets.RenderShaderArtifact Inno.Editor.Shaders.ShaderPreviewContext.artifact { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L100) | Gets the complete immutable GPU candidate. | — | — | | [`public Inno.Rendering.Assets.ShaderDefinition Inno.Editor.Shaders.ShaderPreviewContext.definition { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L104) | Gets the candidate's exact contract, not the latest uncompiled source interface. | — | — | | [`public Inno.Core.Diagnostics.IDiagnosticReporter Inno.Editor.Shaders.ShaderPreviewContext.diagnostics { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L108) | Gets the host-owned, isolated diagnostic producer. | — | — | -| [`public Inno.Rendering.Assets.MaterialAsset Inno.Editor.Shaders.ShaderPreviewContext.material { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L96) | Gets the invocation-local Material values. | — | — | -| [`public int Inno.Editor.Shaders.ShaderPreviewContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L116) | Gets target height in physical pixels. | — | — | | [`public int Inno.Editor.Shaders.ShaderPreviewContext.pixelWidth { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L112) | Gets target width in physical pixels. | — | — | -| [`public Inno.Rendering.Runtime.RenderPersistentResourceId Inno.Editor.Shaders.ShaderPreviewContext.resourceId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L92) | Gets the publication scope released by the host when this preview closes. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderPreviewContext`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L46) | Carries frame-only preview inputs; persistent provider state must retain stable values rather than this object. | — | — | - -### `Inno.Editor.Shaders.ShaderPreviewProvider` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Editor.Shaders.ShaderPreviewProvider.ShaderPreviewProvider()`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L29) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public abstract Inno.Editor.Rendering.EditorViewportLayer Inno.Editor.Shaders.ShaderPreviewProvider.CreateLayer(Inno.Editor.Shaders.ShaderPreviewContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L40) | Creates one frame-local rendering layer through the ordinary Render Graph.
返回:A domain-selected pipeline and frame payload; no instance may be retained beyond the submitted frame.
`context`:Detached Material, immutable candidate and isolated diagnostics for this invocation. | — | — | -| [`public abstract class Inno.Editor.Shaders.ShaderPreviewProvider`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L29) | Builds domain-owned preview geometry and pass inputs without changing a scene or canonical asset. | — | — | - -### `Inno.Editor.Shaders.ShaderPreviewProviderAttribute` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Editor.Shaders.ShaderPreviewProviderAttribute.ShaderPreviewProviderAttribute(string contractId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L17) | Associates a reloadable preview implementation with one open Shader contract.
`contractId`:Non-empty domain-owned contract identity. | — | — | -| [`public string Inno.Editor.Shaders.ShaderPreviewProviderAttribute.contractId { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L22) | Gets the non-empty Shader contract consumed by this preview. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderPreviewProviderAttribute : System.Attribute`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L17) | Associates a reloadable preview implementation with one open Shader contract.
`contractId`:Non-empty domain-owned contract identity. | — | — | - -### `Inno.Editor.Shaders.ShaderPreviews` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public void Inno.Editor.Shaders.ShaderPreviews.Draw(System.Guid ownerId, Inno.Rendering.Assets.MaterialAsset material, Inno.Editor.Rendering.EditorShaderDraftCompilationSnapshot compilation, float logicalSize)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L88) | Draws a compiled draft without modifying the source Shader or its canonical runtime publication.
`ownerId`:Persistent document identity, independent of the canonical Shader publication.
`material`:Detached defaults or overrides with the current Shader reference.
`compilation`:Isolated draft compiler result, including explicit last-good state.
`logicalSize`:Positive square image size in logical pixels. | — | — | -| [`public void Inno.Editor.Shaders.ShaderPreviews.DrawMaterial(System.Guid ownerId, Inno.Rendering.Assets.MaterialAsset material, float logicalSize)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L53) | Draws detached Material overrides using its Shader's compiled contract.
`ownerId`:Persistent document identity; no Material is retained under this identity.
`material`:Invocation-local Material values.
`logicalSize`:Positive square image size in logical pixels. | — | — | -| [`protected override void Inno.Editor.Shaders.ShaderPreviews.OnStop(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L170) | Stops this feature before its owning runtime releases the active generation.
`context`:The context that supplies state and services for this operation. | — | — | -| [`protected override void Inno.Editor.Shaders.ShaderPreviews.OnUpdate(Inno.Editor.Core.EditorContext context)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L158) | Advances this feature using the current runtime state.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public void Inno.Editor.Shaders.ShaderPreviews.Release(System.Guid ownerId)`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L146) | Releases this document's viewport and scoped GPU publication without touching other consumers.
`ownerId`:Closing or disabled preview owner. | — | — | -| [`public sealed class Inno.Editor.Shaders.ShaderPreviews : Inno.Editor.Core.EditorModule`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs#L21) | Hosts isolated Shader and Material images using domain-contributed preview pipelines. | — | — | - -### `Inno.Editor.Shaders.ShaderPropertyInspector` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Rendering.Assets.MaterialValue Inno.Editor.Shaders.ShaderPropertyInspector.ApplyEdit(Inno.Rendering.ShaderPropertyType type, Inno.Rendering.Assets.MaterialValue before, Inno.Rendering.Assets.MaterialValue edited, Inno.Rendering.Assets.MaterialValue target)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs#L111) | Applies the edited components of a representative value without replacing unrelated mixed components.
返回:A detached value preserving the target's untouched channels, texture or sampler.
`type`:Common exact Shader property type.
`before`:Representative value before the control gesture sample.
`edited`:Representative value after this sample.
`target`:Another selected Material's current effective value. | — | — | -| [`public static bool Inno.Editor.Shaders.ShaderPropertyInspector.Compatible(Inno.Rendering.ShaderPropertyType type, Inno.Rendering.Assets.MaterialValueKind kind)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs#L80) | Tests whether a stored override has the exact representation expected by a declaration.
返回:True only for supported matching kinds; no lossy conversion is performed.
`type`:Declared material type.
`kind`:Stored representation. | — | — | -| [`public static void Inno.Editor.Shaders.ShaderPropertyInspector.Draw(Inno.Editor.Inspection.InspectionDrawContext context, object owner, string path, Inno.Rendering.Assets.ShaderPropertyDefinition property, Inno.Rendering.Assets.MaterialValue value, System.Action setter, Inno.Editor.Inspection.IInspectionPropertyEditService edits, bool readOnly, Inno.Editor.Shaders.ShaderParameterPresentation? presentation = null)`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs#L44) | Draws a typed value without choosing its persistence or history policy.
`context`:Current Inspector invocation.
`owner`:Detached draft owner used for widget identity.
`path`:Stable draft property path.
`property`:Current Shader declaration.
`value`:Current default or override, never mutated.
`setter`:Writes only the host's detached draft.
`edits`:Host-owned gesture and history service.
`readOnly`:Whether this source is editable.
`presentation`:Optional Editor-only description and scalar bounds; it never changes inherited or stored values. | — | — | -| [`public static class Inno.Editor.Shaders.ShaderPropertyInspector`](../../src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs#L12) | Uses the shared Inspector drawers for both Shader defaults and Material overrides. | — | — | +| [`public int Inno.Editor.Shaders.ShaderPreviewContext.pixelHeight { get; }`](../../src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviewProvider.cs#L116) | Gets target height in physical pixels. | — | — | ## 项目依赖 -- [Inno.Editor.Assets](Inno.Editor.Assets.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.IO](../core/Inno.Core.IO.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Shaders](../rendering/Inno.Rendering.Shaders.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets.Authoring](../rendering/Inno.Rendering.Assets.Authoring.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Rendering.Assets](../rendering/Inno.Rendering.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Inspection](Inno.Editor.Inspection.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Core](Inno.Editor.Core.md):公开引用边界由实际签名核对。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Core.Graphs](../core/Inno.Core.Graphs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Editor.Rendering](Inno.Editor.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Rendering.Runtime](../rendering/Inno.Rendering.Runtime.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Types` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Assets.Authoring` | 实现依赖;不传递 compile surface | +| `Inno.Rendering.Shaders` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Core.Graphs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Rendering.Assets` | 公开签名依赖 | +| `Inno.Rendering.Runtime` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Editor.Core` | 公开签名依赖 | +| `Inno.Editor.Assets` | 公开签名依赖 | +| `Inno.Editor.Rendering` | 公开签名依赖 | diff --git a/docs/editor/Inno.Editor.Panel.Global.md b/docs/editor/Inno.Editor.Workbench.md similarity index 75% rename from docs/editor/Inno.Editor.Panel.Global.md rename to docs/editor/Inno.Editor.Workbench.md index fb662da00..8b9ecccac 100644 --- a/docs/editor/Inno.Editor.Panel.Global.md +++ b/docs/editor/Inno.Editor.Workbench.md @@ -1,4 +1,4 @@ -# Inno.Editor.Panel.Global +# Inno.Editor.Workbench ## Editor / Rendering @@ -6,12 +6,12 @@ Settings 新增 `Editor/Rendering` 页面。`Vertical Sync` 默认关闭;`Maxi [Editor 索引](README.md) · [Settings API](Inno.Editor.Settings.md) · [Interactions](Inno.Editor.Interactions.md) · [Wiki 首页](../README.md) -`Inno.Editor.Panel.Global` 是 Editor Application 的全局 feature composition 项目。它不提供一个可停靠 Panel,也没有公开 API;它只放置需要由宿主统一发现、但不应属于 Settings 或 Interactions 基础程序集的内建定义。 +`Inno.Editor.Workbench` 是 Editor Application 的全局 feature composition 项目。它不提供一个可停靠 Panel,也没有公开 API;它只放置需要由宿主统一发现、但不应属于 Settings 或 Interactions 基础程序集的内建定义。 ## 职责与边界 ```text -Inno.Editor.Panel.Global/ +Inno.Editor.Workbench/ ├─ Actions/ │ ├─ HistoryActions.cs │ ├─ SelectionActions.cs @@ -19,6 +19,8 @@ Inno.Editor.Panel.Global/ ├─ Settings/ │ ├─ GlobalSettingsPages.cs │ └─ IconSettings.cs +├─ Exporting/ # Forms and progress presentation +├─ Scripting/ # Blocking compilation presentation └─ Zoom/ ├─ EditorZoomModule.cs ├─ ZoomActions.cs @@ -82,20 +84,32 @@ Application 在创建 TypeCache 之前加载本程序集。之后 Settings Catal [上一页:Inno.Editor.Panel.FileBrowser](Inno.Editor.Panel.FileBrowser.md) · [下一页:Inno.Editor.Panel.Hierarchy](Inno.Editor.Panel.Hierarchy.md) + + +## 宿主工作流呈现 + +Exporting 的 modal 只捕获表单与显示状态,异步资源属于 EditorExportService。Scripting modal 从 IEditorScriptCompilation 读取独占进度和取消能力。两者均通过 runtime 注入,不在无界面 feature 内创建窗口。 + +Command、menu、shortcut 与 toolbar 仍复用同一 Action Query/Execute。上下文搜索在每次打开时清空,popup/owner 退休时释放字符串缓存;隐藏或禁用来自当前模型,不由 Workbench 维护另一份业务判断。 + ## 当前源码公开 API 清单 -只列当前源码的 public/protected 表面;内部实现不作为稳定 API。参数、返回、失败和 owner 以英文 XML 为准。 +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 -本项目没有公开入口,通过组合或扩展发现使用内部实现。 +本程序集没有对外公开类型;扩展由当前产品的 Registry 在内部发现。 ## 项目依赖 -- [Inno.Platform](../platform/Inno.Platform.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Input](../core/Inno.Core.Input.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Adapter.Presentation.ImGui.Sdl3](../backends/ImGui/Inno.Adapter.Presentation.ImGui.Sdl3.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Native.ImGui](../backends/ImGui/Inno.Native.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.ImGui](Inno.Editor.ImGui.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Interactions](Inno.Editor.Interactions.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Editor.Settings](Inno.Editor.Settings.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Editor.Exporting` | 实现依赖;不传递 compile surface | +| `Inno.Build` | 实现依赖;不传递 compile surface | +| `Inno.Build.Managed` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Scripting` | 实现依赖;不传递 compile surface | +| `Inno.Platform` | 实现依赖;不传递 compile surface | +| `Inno.Core.Input` | 实现依赖;不传递 compile surface | +| `Inno.Editor.ImGui` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Interactions` | 实现依赖;不传递 compile surface | +| `Inno.Editor.Settings` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | diff --git a/docs/editor/README.md b/docs/editor/README.md index 00f5fed69..eb01e10ef 100644 --- a/docs/editor/README.md +++ b/docs/editor/README.md @@ -21,8 +21,8 @@ Editor 采用“被动核心 → 后端无关交互 → ImGui 表现 → 独立 | [Inno.Editor.Inspection](Inno.Editor.Inspection.md) | InspectionDrawer、PropertyDrawer、Registry 与 serialized property renderer。 | | [Inno.Editor.Scripting](Inno.Editor.Scripting.md) | Asset-backed Roslyn 编译、facade、IDE 工程与热重载。 | | [Inno.Editor.Exporting](Inno.Editor.Exporting.md) | File 菜单 Plugin/Game 导出、编译门禁、平台 Player 发布与原子输出。 | -| [Inno.Editor.Panel.FileBrowser](Inno.Editor.Panel.FileBrowser.md) | AssetEditor、文件浏览、Asset 操作与 Asset-side drag/drop。 | -| [Inno.Editor.Panel.Global](Inno.Editor.Panel.Global.md) | internal 全局 Action、Editor/Appearance 页面、Icon 与 Zoom setting definitions。 | +| [Inno.Editor.Panel.FileBrowser](Inno.Editor.Panel.FileBrowser.md) | 文件浏览、导航与 Asset-side drag/drop;编辑由 Assets feature 负责。 | +| [Inno.Editor.Workbench](Inno.Editor.Workbench.md) | internal 全局 Action、Editor/Appearance 页面、Icon 与 Zoom setting definitions。 | | [Inno.Editor.Panel.Hierarchy](Inno.Editor.Panel.Hierarchy.md) | Scene workspace、Hierarchy、Scene/GameObject 操作与排序。 | | [Inno.Editor.Panel.Inspector](Inno.Editor.Panel.Inspector.md) | Inspector Panel、Scene Drawer 与 Component/System 操作。 | | [Inno.Editor.Panel.Logging](Inno.Editor.Panel.Logging.md) | Editor 日志/诊断缓冲与 Console Panel。 | @@ -30,11 +30,14 @@ Editor 采用“被动核心 → 后端无关交互 → ImGui 表现 → 独立 | [Inno.Editor.Panel.Stats](Inno.Editor.Panel.Stats.md) | 平滑后的帧统计与 Stats Panel。 | | [Inno.Editor.Panel.SceneView](Inno.Editor.Panel.SceneView.md) | 不含 Camera/Picking 假设的 Plugin 驱动 Scene viewport host。 | | [Inno.Editor.Panel.GameView](Inno.Editor.Panel.GameView.md) | 不含运行时世界观的 Plugin 驱动 Game viewport host。 | -| [Inno.Editor.Shaders](Inno.Editor.Shaders.md) | 可复用 Shader 节点呈现协议与 generation-scoped Drawer 注册。 | -| [Inno.Editor.Assets](Inno.Editor.Assets.md) | Material/Pipeline 共用的原生资产草稿、显式保存与中立 History 生命周期。 | +| [Inno.Editor.Shaders](Inno.Editor.Shaders.md) | 无界面的 Material 草稿、Shader 创作服务与缓存。 | +| [Inno.Editor.Assets](Inno.Editor.Assets.md) | 可复用 Asset 编辑、源操作、导入设置、草稿、显式保存与中立 History。 | | [Inno.Editor.Panel.ShaderEditor](Inno.Editor.Panel.ShaderEditor.md) | Shader 图画布、模板创建菜单、手势历史与显式 Save/Revert;完整验收进行中。 | | [Inno.Editor.Hosting](Inno.Editor.Hosting.md) | 共享启动、EngineHost/Edit Session 与全部功能生命周期;平台产品注入后端、系统规则和构建组合。 | | [Inno.Build.Cli](../build/Inno.Build.Cli.md) | 与 Editor 共用作者端脚本 API 和构建服务的无界面入口。 | +| [Inno.Editor.Assets.ImGui](Inno.Editor.Assets.ImGui.md) | Asset Browser 状态、图标发现和基础资源检查显示。 | +| [Inno.Editor.Shaders.ImGui](Inno.Editor.Shaders.ImGui.md) | Shader 节点控件、Material Inspector 和预览呈现。 | +| [Inno.Editor.Graph.ImGui](Inno.Editor.Graph.ImGui.md) | 不含 Shader 语义的坐标、网格、连线与命中测试。 | ## 依赖方向 @@ -61,10 +64,11 @@ flowchart TD Interactions --> Panels ImGui --> Panels Core --> Scripting["Inno.Editor.Scripting"] - ImGui --> Scripting Scripting --> PlayMode Scripting --> Exporting["Inno.Editor.Exporting"] - Settings --> Exporting + Exporting --> Workbench["Inno.Editor.Workbench"] + ImGui --> Workbench + Scripting --> Workbench Exporting --> Application PlayMode --> Application Audio --> Application diff --git a/docs/platform/Inno.Adapter.Presentation.md b/docs/platform/Inno.Adapter.Presentation.md index 1862fa162..f419c3337 100644 --- a/docs/platform/Inno.Adapter.Presentation.md +++ b/docs/platform/Inno.Adapter.Presentation.md @@ -38,72 +38,27 @@ Presentation 是 Application 生命周期资源,必须先于 shared render dev ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Adapter.Presentation.IAuthoringAdapterCatalog` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Adapter.Presentation.IPresentationBackendFactory Inno.Adapter.Presentation.IAuthoringAdapterCatalog.presentation { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L18) | Gets the graphical host-presentation factory used by authoring products. | — | — | -| [`Inno.Adapter.Rendering.IRenderingAuthoringBackendFactory Inno.Adapter.Presentation.IAuthoringAdapterCatalog.renderingAuthoring { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L13) | Gets the rendering toolchain factory used to compile authoring artifacts. | — | — | -| [`public interface Inno.Adapter.Presentation.IAuthoringAdapterCatalog : Inno.Adapter.IAdapterCatalog`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L8) | Extends the runtime adapter catalog with tooling and presentation factories required by authoring products. | — | — | - -### `Inno.Adapter.Presentation.IPresentationBackendFactory` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.IPresentationBackendFactory.CreateContext(Inno.Adapter.Presentation.PresentationBackendId backend, Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L30) | Creates a presentation context over compatible platform and rendering adapters.
返回:A caller-owned backend-neutral presentation context.
`backend`:Stable presentation implementation selected by the composition root.
`options`:Host-owned resources and presentation policy. | — | `System.NotSupportedException`:Thrown when the selected platform, rendering, and presentation backends are incompatible. | -| [`System.Collections.Generic.IReadOnlyList Inno.Adapter.Presentation.IPresentationBackendFactory.supportedBackends { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L13) | Gets the implementation identities available in this immutable factory snapshot. | — | — | -| [`public interface Inno.Adapter.Presentation.IPresentationBackendFactory`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L8) | Creates host presentation contexts from explicit backend selections. | — | — | - -### `Inno.Adapter.Presentation.IPresentationContext` - - + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Adapter.Presentation.IPresentationContext.DrawImage(Inno.Adapter.Presentation.PresentationTextureHandle texture, System.Numerics.Vector2 size)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L101) | Draws a registered texture in the active presentation surface.
`texture`:Opaque presentation texture token.
`size`:Destination size in logical presentation units. | — | — | -| [`void Inno.Adapter.Presentation.IPresentationContext.LoadLayout(string? settings)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L43) | Loads complete layout text captured by the host.
`settings`:Layout text, or an empty value when no layout exists. | — | — | -| [`Inno.Adapter.Presentation.PresentationTextureHandle Inno.Adapter.Presentation.IPresentationContext.RegisterTexture(Inno.Rendering.PersistentTextureHandle texture)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L79) | Registers a persistent render texture for presentation drawing.
返回:An opaque presentation token.
`texture`:Texture owned by the active rendering-device generation. | — | — | -| [`void Inno.Adapter.Presentation.IPresentationContext.RenderFrame(System.Action drawFrame)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L68) | Builds one presentation frame by invoking the host draw callback.
`drawFrame`:Callback that emits presentation commands for the current frame. | — | — | -| [`void Inno.Adapter.Presentation.IPresentationContext.SetLayoutFile(string? filePath)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L35) | Disables or redirects backend-owned layout-file persistence.
`filePath`:Layout path, or null when the host persists layout text itself. | — | — | -| [`bool Inno.Adapter.Presentation.IPresentationContext.TryCaptureLayout(out string settings, bool force = false)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L57) | Captures complete layout text when persistence is required.
返回:true when layout text was captured.
`settings`:Complete layout text when capture succeeds.
`force`:Whether to capture even when the backend did not mark the layout dirty. | — | — | -| [`bool Inno.Adapter.Presentation.IPresentationContext.TryGetWindowId(uint viewportId, out uint windowId)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L24) | Resolves the platform window that owns a presentation viewport.
返回:true when the viewport has a live platform window.
`viewportId`:The presentation viewport identity.
`windowId`:The owning platform window identity when available. | — | — | -| [`bool Inno.Adapter.Presentation.IPresentationContext.UnregisterTexture(Inno.Adapter.Presentation.PresentationTextureHandle texture)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L90) | Releases a presentation token without taking ownership of the source texture.
返回:true when the token was registered and released.
`texture`:Previously allocated presentation token. | — | — | -| [`public interface Inno.Adapter.Presentation.IPresentationContext : Inno.Rendering.IRenderFrameGraphContributor, System.IDisposable`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L10) | Defines the backend-neutral lifecycle used by a graphical composition host. | — | — | - -### `Inno.Adapter.Presentation.PresentationBackendCatalog` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.PresentationBackendCatalog.PresentationBackendCatalog(System.Collections.Generic.IEnumerable providers)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L26) | Validates and captures a complete provider set without creating any service.
`providers`:Providers whose lifetime remains with the composition owner. | — | `System.ArgumentNullException`:The provider sequence is null.
`System.ArgumentException`:A provider is null, unassigned, or duplicates a registration identity. | -| [`public Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.PresentationBackendCatalog.CreateContext(Inno.Adapter.Presentation.PresentationBackendId backend, Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L44) | Creates a presentation context over compatible platform and rendering adapters.
返回:A caller-owned backend-neutral presentation context.
`backend`:Stable presentation implementation selected by the composition root.
`options`:Host-owned resources and presentation policy. | — | `System.NotSupportedException`:Thrown when the selected platform, rendering, and presentation backends are incompatible.
`System.InvalidOperationException`:The selected provider returned no service. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Adapter.Presentation.PresentationBackendCatalog.supportedBackends { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L38) | Gets the implementation identities available in this immutable factory snapshot. | — | — | -| [`public sealed class Inno.Adapter.Presentation.PresentationBackendCatalog : Inno.Adapter.Presentation.IPresentationBackendFactory`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L10) | Resolves presentation providers from one immutable, composition-owned registration snapshot. | — | — | - -### `Inno.Adapter.Presentation.PresentationBackendId` - @@ -115,71 +70,21 @@ Presentation 是 Application 生命周期资源,必须先于 shared render dev - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.PresentationBackendId.PresentationBackendId(string value)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L19) | Creates an ordinal, case-sensitive implementation identifier.
`value`:A stable nonempty identifier containing no whitespace. | — | `System.ArgumentException`:The identifier is empty or contains whitespace. | -| [`public bool Inno.Adapter.Presentation.PresentationBackendId.Equals(Inno.Adapter.Presentation.PresentationBackendId other)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Adapter.Presentation.PresentationBackendId.Equals(object obj)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Adapter.Presentation.PresentationBackendId.GetHashCode()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Adapter.Presentation.PresentationBackendId.operator ==(Inno.Adapter.Presentation.PresentationBackendId left, Inno.Adapter.Presentation.PresentationBackendId right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Adapter.Presentation.PresentationBackendId.operator !=(Inno.Adapter.Presentation.PresentationBackendId left, Inno.Adapter.Presentation.PresentationBackendId right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Adapter.Presentation.PresentationBackendId.ToString()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L49) | Returns the identifier without resolving a provider.
返回:The stable identifier, or an empty string when unassigned. | — | — | -| [`public static Inno.Adapter.Presentation.PresentationBackendId Inno.Adapter.Presentation.PresentationBackendId.imGui { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L31) | Gets the identifier of the bundled ImGui implementation. | — | — | -| [`public bool Inno.Adapter.Presentation.PresentationBackendId.isValid { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L41) | Gets whether this value identifies an implementation. | — | — | -| [`public string Inno.Adapter.Presentation.PresentationBackendId.value { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L36) | Gets the stable identifier; a default value is unassigned. | — | — | -| [`public readonly record struct Inno.Adapter.Presentation.PresentationBackendId : System.IEquatable`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Identifies a presentation implementation without closing the set of supported backends. | — | — | - -### `Inno.Adapter.Presentation.PresentationBackendOptions` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.PresentationBackendOptions.PresentationBackendOptions()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public Inno.Adapter.Presentation.PresentationFeatures Inno.Adapter.Presentation.PresentationBackendOptions.features { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L29) | Gets or sets optional presentation features requested by the host. | — | — | -| [`public required Inno.Platform.IPlatformApplication Inno.Adapter.Presentation.PresentationBackendOptions.platformApplication { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L14) | Gets or sets the platform application that owns presentation windows and events. | — | — | -| [`public required Inno.Rendering.IRenderDevice Inno.Adapter.Presentation.PresentationBackendOptions.renderDevice { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L24) | Gets or sets the rendering device used to present host draw data. | — | — | -| [`public required Inno.Platform.IPlatformWindow Inno.Adapter.Presentation.PresentationBackendOptions.window { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L19) | Gets or sets the primary presentation window. | — | — | -| [`public sealed class Inno.Adapter.Presentation.PresentationBackendOptions`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L9) | Collects the validated presentation backend options values that configure one owned operation. | — | — | - -### `Inno.Adapter.Presentation.PresentationBackendProvider` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Adapter.Presentation.PresentationBackendProvider.PresentationBackendProvider(Inno.Adapter.Presentation.PresentationBackendId id)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L19) | Captures the implementation identity assigned by the composition owner.
`id`:The assigned, immutable implementation identifier. | — | `System.ArgumentException`:The identifier is unassigned. | -| [`public abstract Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.PresentationBackendProvider.CreateContext(Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L44) | Creates a new caller-owned presentation context over the supplied host resources.
返回:A new context whose caller must dispose it before releasing the supplied resources. Providers must not return null.
`options`:Externally owned platform and rendering resources, together with the requested presentation policy. | — | `System.NotSupportedException`:The host resources or requested features are incompatible with this implementation. | -| [`public Inno.Adapter.Presentation.PresentationBackendId Inno.Adapter.Presentation.PresentationBackendProvider.id { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L29) | Gets this registration's immutable implementation identity. | — | — | -| [`public abstract class Inno.Adapter.Presentation.PresentationBackendProvider`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L8) | Describes an explicitly composed host presentation implementation and its creation boundary. | — | — | - -### `Inno.Adapter.Presentation.PresentationFeatures` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Adapter.Presentation.PresentationFeatures.Docking = 2`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L24) | Enables dockable host surfaces when supported. | — | — | -| [`Inno.Adapter.Presentation.PresentationFeatures.MultipleWindows = 1`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L19) | Allows the presentation to create detached native windows. | — | — | -| [`Inno.Adapter.Presentation.PresentationFeatures.None = 0`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L14) | Requests no optional presentation features. | — | — | -| [`Inno.Adapter.Presentation.PresentationFeatures.SmoothResize = 4`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L29) | Enables smooth rendering while native windows are being resized. | — | — | -| [`public enum Inno.Adapter.Presentation.PresentationFeatures : int`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L9) | Describes optional capabilities requested from a host presentation backend. | — | — | - -### `Inno.Adapter.Presentation.PresentationTextureHandle` - @@ -191,23 +96,70 @@ Presentation 是 Application 生命周期资源,必须先于 shared render dev -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Adapter.Presentation.PresentationTextureHandle.PresentationTextureHandle(ulong value)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L16) | Creates an opaque presentation texture token.
`value`:Non-zero token allocated by the active presentation backend. | — | — | -| [`public bool Inno.Adapter.Presentation.PresentationTextureHandle.Equals(Inno.Adapter.Presentation.PresentationTextureHandle other)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Adapter.Presentation.PresentationTextureHandle.Equals(object obj)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Adapter.Presentation.PresentationTextureHandle.GetHashCode()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Adapter.Presentation.PresentationTextureHandle.operator ==(Inno.Adapter.Presentation.PresentationTextureHandle left, Inno.Adapter.Presentation.PresentationTextureHandle right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Adapter.Presentation.PresentationTextureHandle.operator !=(Inno.Adapter.Presentation.PresentationTextureHandle left, Inno.Adapter.Presentation.PresentationTextureHandle right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Adapter.Presentation.PresentationTextureHandle.ToString()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public bool Inno.Adapter.Presentation.PresentationTextureHandle.isValid { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L31) | Gets whether this token identifies a registered presentation texture. | — | — | -| [`public ulong Inno.Adapter.Presentation.PresentationTextureHandle.value { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L26) | Gets the opaque backend token. | — | — | +| [`public interface Inno.Adapter.Presentation.IAuthoringAdapterCatalog : Inno.Adapter.IAdapterCatalog`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L8) | Extends the runtime adapter catalog with tooling and presentation factories required by authoring products. | — | — | +| [`Inno.Adapter.Rendering.IRenderingAuthoringBackendFactory Inno.Adapter.Presentation.IAuthoringAdapterCatalog.renderingAuthoring { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L13) | Gets the rendering toolchain factory used to compile authoring artifacts. | — | — | +| [`Inno.Adapter.Presentation.IPresentationBackendFactory Inno.Adapter.Presentation.IAuthoringAdapterCatalog.presentation { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IAuthoringAdapterCatalog.cs#L18) | Gets the graphical host-presentation factory used by authoring products. | — | — | +| [`public interface Inno.Adapter.Presentation.IPresentationBackendFactory`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L8) | Creates host presentation contexts from explicit backend selections. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Adapter.Presentation.IPresentationBackendFactory.supportedBackends { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L13) | Gets the implementation identities available in this immutable factory snapshot. | — | — | +| [`Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.IPresentationBackendFactory.CreateContext(Inno.Adapter.Presentation.PresentationBackendId backend, Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationBackendFactory.cs#L30) | Creates a presentation context over compatible platform and rendering adapters.
`backend`: Stable presentation implementation selected by the composition root.
`options`: Host-owned resources and presentation policy. | A caller-owned backend-neutral presentation context. | `System.NotSupportedException`: Thrown when the selected platform, rendering, and presentation backends are incompatible. | +| [`public interface Inno.Adapter.Presentation.IPresentationContext : Inno.Rendering.IRenderFrameGraphContributor, System.IDisposable`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L10) | Defines the backend-neutral lifecycle used by a graphical composition host. | — | — | +| [`bool Inno.Adapter.Presentation.IPresentationContext.TryGetWindowId(uint viewportId, out uint windowId)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L24) | Resolves the platform window that owns a presentation viewport.
`viewportId`: The presentation viewport identity.
`windowId`: The owning platform window identity when available. | when the viewport has a live platform window. | — | +| [`void Inno.Adapter.Presentation.IPresentationContext.SetLayoutFile(string? filePath)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L35) | Disables or redirects backend-owned layout-file persistence.
`filePath`: Layout path, or when the host persists layout text itself. | — | — | +| [`void Inno.Adapter.Presentation.IPresentationContext.LoadLayout(string? settings)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L43) | Loads complete layout text captured by the host.
`settings`: Layout text, or an empty value when no layout exists. | — | — | +| [`bool Inno.Adapter.Presentation.IPresentationContext.TryCaptureLayout(out string settings, bool force = false)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L57) | Captures complete layout text when persistence is required.
`settings`: Complete layout text when capture succeeds.
`force`: Whether to capture even when the backend did not mark the layout dirty. | when layout text was captured. | — | +| [`void Inno.Adapter.Presentation.IPresentationContext.RenderFrame(System.Action drawFrame, System.Action? prepareFrame = null)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L78) | Builds one presentation frame by invoking the host draw callback.
`drawFrame`: Callback that emits presentation commands for the current frame.
`prepareFrame`: Optional synchronous preparation after the owned context and display metrics are selected, before frame creation. Failure prevents drawing and no partial frame is published. | — | `System.ArgumentNullException`: The draw callback is null.
`System.InvalidOperationException`: Another frame operation is active on this context. | +| [`Inno.Adapter.Presentation.PresentationTextureHandle Inno.Adapter.Presentation.IPresentationContext.RegisterTexture(Inno.Rendering.PersistentTextureHandle texture)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L92) | Registers a persistent render texture for presentation drawing.
`texture`: Texture owned by the active rendering-device generation. | An opaque presentation token. | — | +| [`bool Inno.Adapter.Presentation.IPresentationContext.UnregisterTexture(Inno.Adapter.Presentation.PresentationTextureHandle texture)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L103) | Releases a presentation token without taking ownership of the source texture.
`texture`: Previously allocated presentation token. | when the token was registered and released. | — | +| [`void Inno.Adapter.Presentation.IPresentationContext.DrawImage(Inno.Adapter.Presentation.PresentationTextureHandle texture, System.Numerics.Vector2 size)`](../../src/adapters/presentation/Inno.Adapter.Presentation/IPresentationContext.cs#L114) | Draws a registered texture in the active presentation surface.
`texture`: Opaque presentation texture token.
`size`: Destination size in logical presentation units. | — | — | +| [`public sealed class Inno.Adapter.Presentation.PresentationBackendCatalog : Inno.Adapter.Presentation.IPresentationBackendFactory`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L10) | Resolves presentation providers from one immutable, composition-owned registration snapshot. | — | — | +| [`public Inno.Adapter.Presentation.PresentationBackendCatalog.PresentationBackendCatalog(System.Collections.Generic.IEnumerable providers)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L26) | Validates and captures a complete provider set without creating any service.
`providers`: Providers whose lifetime remains with the composition owner. | — | `System.ArgumentNullException`: The provider sequence is null.
`System.ArgumentException`: A provider is null, unassigned, or duplicates a registration identity. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Adapter.Presentation.PresentationBackendCatalog.supportedBackends { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L38) | Gets the implementation identities available in this immutable factory snapshot. | — | — | +| [`public Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.PresentationBackendCatalog.CreateContext(Inno.Adapter.Presentation.PresentationBackendId backend, Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendCatalog.cs#L44) | Creates a presentation context over compatible platform and rendering adapters.
`backend`: Stable presentation implementation selected by the composition root.
`options`: Host-owned resources and presentation policy. | A caller-owned backend-neutral presentation context. | `System.NotSupportedException`: Thrown when the selected platform, rendering, and presentation backends are incompatible.
`System.InvalidOperationException`: The selected provider returned no service. | +| [`public readonly record struct Inno.Adapter.Presentation.PresentationBackendId : System.IEquatable`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Identifies a presentation implementation without closing the set of supported backends. | — | — | +| [`public Inno.Adapter.Presentation.PresentationBackendId.PresentationBackendId(string value)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L19) | Creates an ordinal, case-sensitive implementation identifier.
`value`: A stable nonempty identifier containing no whitespace. | — | `System.ArgumentException`: The identifier is empty or contains whitespace. | +| [`public static Inno.Adapter.Presentation.PresentationBackendId Inno.Adapter.Presentation.PresentationBackendId.imGui { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L31) | Gets the identifier of the bundled ImGui implementation. | — | — | +| [`public string Inno.Adapter.Presentation.PresentationBackendId.value { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L36) | Gets the stable identifier; a default value is unassigned. | — | — | +| [`public bool Inno.Adapter.Presentation.PresentationBackendId.isValid { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L41) | Gets whether this value identifies an implementation. | — | — | +| [`public override string Inno.Adapter.Presentation.PresentationBackendId.ToString()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L49) | Returns the identifier without resolving a provider. | The stable identifier, or an empty string when unassigned. | — | +| [`public static bool Inno.Adapter.Presentation.PresentationBackendId.operator !=(Inno.Adapter.Presentation.PresentationBackendId left, Inno.Adapter.Presentation.PresentationBackendId right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Adapter.Presentation.PresentationBackendId.operator ==(Inno.Adapter.Presentation.PresentationBackendId left, Inno.Adapter.Presentation.PresentationBackendId right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Adapter.Presentation.PresentationBackendId.GetHashCode()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Adapter.Presentation.PresentationBackendId.Equals(object obj)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Adapter.Presentation.PresentationBackendId.Equals(Inno.Adapter.Presentation.PresentationBackendId other)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendId.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed class Inno.Adapter.Presentation.PresentationBackendOptions`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L9) | Collects the validated presentation backend options values that configure one owned operation. | — | — | +| [`public required Inno.Platform.IPlatformApplication Inno.Adapter.Presentation.PresentationBackendOptions.platformApplication { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L14) | Gets or sets the platform application that owns presentation windows and events. | — | — | +| [`public required Inno.Platform.IPlatformWindow Inno.Adapter.Presentation.PresentationBackendOptions.window { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L19) | Gets or sets the primary presentation window. | — | — | +| [`public required Inno.Rendering.IRenderDevice Inno.Adapter.Presentation.PresentationBackendOptions.renderDevice { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L24) | Gets or sets the rendering device used to present host draw data. | — | — | +| [`public Inno.Adapter.Presentation.PresentationFeatures Inno.Adapter.Presentation.PresentationBackendOptions.features { get; set; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L29) | Gets or sets optional presentation features requested by the host. | — | — | +| [`public Inno.Adapter.Presentation.PresentationBackendOptions.PresentationBackendOptions()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendOptions.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Adapter.Presentation.PresentationBackendProvider`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L8) | Describes an explicitly composed host presentation implementation and its creation boundary. | — | — | +| [`protected Inno.Adapter.Presentation.PresentationBackendProvider.PresentationBackendProvider(Inno.Adapter.Presentation.PresentationBackendId id)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L19) | Captures the implementation identity assigned by the composition owner.
`id`: The assigned, immutable implementation identifier. | — | `System.ArgumentException`: The identifier is unassigned. | +| [`public Inno.Adapter.Presentation.PresentationBackendId Inno.Adapter.Presentation.PresentationBackendProvider.id { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L29) | Gets this registration's immutable implementation identity. | — | — | +| [`public abstract Inno.Adapter.Presentation.IPresentationContext Inno.Adapter.Presentation.PresentationBackendProvider.CreateContext(Inno.Adapter.Presentation.PresentationBackendOptions options)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationBackendProvider.cs#L44) | Creates a new caller-owned presentation context over the supplied host resources.
`options`: Externally owned platform and rendering resources, together with the requested presentation policy. | A new context whose caller must dispose it before releasing the supplied resources. Providers must not return null. | `System.NotSupportedException`: The host resources or requested features are incompatible with this implementation. | +| [`public enum Inno.Adapter.Presentation.PresentationFeatures : int`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L9) | Describes optional capabilities requested from a host presentation backend. | — | — | +| [`Inno.Adapter.Presentation.PresentationFeatures.None = 0`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L14) | Requests no optional presentation features. | — | — | +| [`Inno.Adapter.Presentation.PresentationFeatures.MultipleWindows = 1`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L19) | Allows the presentation to create detached native windows. | — | — | +| [`Inno.Adapter.Presentation.PresentationFeatures.Docking = 2`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L24) | Enables dockable host surfaces when supported. | — | — | +| [`Inno.Adapter.Presentation.PresentationFeatures.SmoothResize = 4`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationFeatures.cs#L29) | Enables smooth rendering while native windows are being resized. | — | — | | [`public readonly record struct Inno.Adapter.Presentation.PresentationTextureHandle : System.IEquatable`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Identifies a renderer-owned texture through an opaque host-presentation token. | — | — | +| [`public Inno.Adapter.Presentation.PresentationTextureHandle.PresentationTextureHandle(ulong value)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L16) | Creates an opaque presentation texture token.
`value`: Non-zero token allocated by the active presentation backend. | — | — | +| [`public ulong Inno.Adapter.Presentation.PresentationTextureHandle.value { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L26) | Gets the opaque backend token. | — | — | +| [`public bool Inno.Adapter.Presentation.PresentationTextureHandle.isValid { get; }`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L31) | Gets whether this token identifies a registered presentation texture. | — | — | +| [`public override string Inno.Adapter.Presentation.PresentationTextureHandle.ToString()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Adapter.Presentation.PresentationTextureHandle.operator !=(Inno.Adapter.Presentation.PresentationTextureHandle left, Inno.Adapter.Presentation.PresentationTextureHandle right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Adapter.Presentation.PresentationTextureHandle.operator ==(Inno.Adapter.Presentation.PresentationTextureHandle left, Inno.Adapter.Presentation.PresentationTextureHandle right)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Adapter.Presentation.PresentationTextureHandle.GetHashCode()`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Adapter.Presentation.PresentationTextureHandle.Equals(object obj)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Adapter.Presentation.PresentationTextureHandle.Equals(Inno.Adapter.Presentation.PresentationTextureHandle other)`](../../src/adapters/presentation/Inno.Adapter.Presentation/PresentationTextureHandle.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | ## 项目依赖 -- [Inno.Adapter](../runtime/Inno.Adapter.md):公开引用边界由实际签名核对。 -- [Inno.Adapter.Rendering.Authoring](../rendering/Inno.Adapter.Rendering.Authoring.md):公开引用边界由实际签名核对。 -- [Inno.Platform](Inno.Platform.md):公开引用边界由实际签名核对。 -- [Inno.Rendering](../rendering/Inno.Rendering.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Adapter` | 公开签名依赖 | +| `Inno.Adapter.Rendering.Authoring` | 公开签名依赖 | +| `Inno.Platform` | 公开签名依赖 | +| `Inno.Rendering` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/scene/Inno.Scene.md b/docs/scene/Inno.Scene.md index 937c5cd08..302367e41 100644 --- a/docs/scene/Inno.Scene.md +++ b/docs/scene/Inno.Scene.md @@ -1,13 +1,21 @@ # Inno.Scene +完整 Scene 捕获通过 Core Serialization 的显式中立 key 复用未变化的对象条目。Scene 自己拥有结构、层级、对象元数据与 Transform 写入的失效责任;每个条目同时覆盖精确 source/parent ID、sibling 位置与组件属性。本地/世界变换、层级保持变换和属性恢复均更新计数。计数不序列化、不导出为领域 API,耗尽后继续完整捕获。任意插件属性、自定义 Converter、Missing 和 Prefab 等无法完整证明的状态保留现有 writer;不按 UI 通知猜测场景是否变化。结构、根属性、别名和 System 仍执行既有写入流程。详细借用与失败语义见 [Serialization](../core/Inno.Core.Serialization.md)。 + [Engine 索引](README.md) · [Scene Assets](Inno.Scene.Assets.md) · [Wiki 首页](../README.md) +内部完整图写入复用 `SceneStore` 已有结构快照中的对象/组件 source-ID 投影。每次捕获仍逐项检查当前 Core persistent ID;不一致时创建新的只读投影,已经借用旧投影的同步写入不受替换影响。结构变化由原 owner 使快照失效。System 归独立生命周期 owner,其身份只在本次写入冻结,不进入结构快照缓存。该投影只表达序列化成员关系,不提供 live-object 解析;Subtree/Prefab 保持各自精确边界,外部引用仍通过 Core Identity 与完整序列化上下文处理。以上均为内部实现,不增加公开配置或持久化字段。 + `Inno.Scene` 提供 Scene、GameObject、Component、Transform hierarchy、GameBehavior 和 GameSystem 的运行时模型。`GameBehavior` 是唯一具有独立启停和帧生命周期的 Component 基类,统一负责 `enabled`、`isActiveAndEnabled`、`Awake`、`Start`、`OnEnable`、`OnDisable`、`Update`、`FixedUpdate`、`LateUpdate` 与 `OnDestroy`。Project Script、Renderer、Camera、Light 等场景功能都直接继承 `GameBehavior`,不存在第二层 `Behavior` 类型或兼容 façade。 `Transform` 除了便捷的 local/world TRS 外,还公开精确的 `localToWorldMatrix`、`worldToLocalMatrix`、`TransformPoint`、`InverseTransformPoint` 与原子 `SetWorldTransform`。`worldPosition` 与点变换统一由完整层级矩阵确定。旋转且非均匀缩放的多级层级可能包含不能由单一 world TRS 无损表示的 shear;渲染、包围盒和空间查询应优先使用矩阵/点变换 API,Inspector 与 Gizmo 的 TRS 编辑则使用原子 setter 避免三次中间重算。零缩放层级不可逆,世界到本地转换和保持世界值的重设父级会明确失败,不会静默使用 identity inverse。 ## 多 Scene +内部存活校验读取对象对既有 `SceneObjectRecord` 的反向借用;记录仍由同一 SceneStore 和 Core IndexedObjectStore 持有。注册成功后才建立借用,移除和 Clear 解除精确 owner,跨 Scene 转移及失败回滚重新连接对应记录。它不构成另一份对象目录,不跨 generation 保存 live object;外部身份解析继续经过 Core Identity。此机制不属于公开 API,也不改变序列化数据。 + +`GameScene.hierarchyRevision` 是当前 live Scene 的只读变更计数。对象注册、移除、重设父级和 sibling 排序使其失效;它只用于刷新派生视图,不是持久 ID 或文件格式。内部层级 owner 同时维护有序 roots 和派生位置索引,Editor 不保存第二份 live 层级。名字、属性及 active 状态不由这个计数描述,呈现仍在提交行时解析当前对象。 + Scene reload 的旧 element 普通退休失败会聚合抛出,且仍尝试其余旧 element;统一 generation 的不可逆 Complete 阶段据此 Fault。RetirementPendingException 则保持 owner、立即阻止后续清理并交给共享 gate Fault。不能只发布 Warning 后继续报告成功,也不能撤销已经提交的新 generation 假装回滚。 以下脚本例在有效 Runtime Session 与 SceneWorld 执行作用域内调用;first/second 为同一 world 准备的不同 Scene,player 属于 first。LoadScene 会卸载当前集合,不用于保留尚未保存的编辑场景。 @@ -277,36 +285,50 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 缺少 owner/Handler、身份冲突、错误 payload 或补偿失败仍明确阻断且保持栈位置。普通元素恢复失败必须移除所有半创建状态; 不可逆退休失败属于 Fault。已保存 Scene 的自动 Missing/Recovery 不产生 dirty 或伪 History entry。 +## 局部属性的引用作用域 + +`ScenePropertySerialization` 和元素状态恢复使用所属 Scene 的 Core Identity domain。对象、Component、System 引用在转换时解析当前注册实例,并检查所属 Scene、代际及预期类型;已销毁、脱离、跨 Scene 或跨 domain 的引用明确失败。纯值属性不会枚举无关对象,也不会为每次前后快照建立整场景双向引用表。调用方仍提供完整的 Asset resolver,资产引用保持 Catalog 的规范实例。 + +完整 Scene、Prefab、子树和 reload 候选仍需要自己的冻结图边界与局部 ID 映射。它们和当前属性定位是不同的操作:前者描述正在捕获或构造的完整图,后者通过 Core 解析当前存活对象。内部共用同一同步引用作用域,不建立第二个 live object authority;这些内部类型不是公开扩展 API。 + +恢复别名仅为本次元素状态的中立 `Guid → Guid` 映射;目标必须能在同一 owner 中解析,身份冲突、缺失目标或损坏的别名数据都会拒绝。嵌套序列化和 getter/converter 异常退出时恢复先前作用域,不把它保存在快照、History 或插件状态中。公开签名及 reference token 的持久形状不变。 + ## Editor Scene 名称与资产路径 已保存 Scene 的 Inspector 名称可以编辑。名称变化立即使文档进入 dirty 状态;保存时同目录 SceneAsset 被事务式重命名,`.imeta`、persistent ID、canonical instance 和 artifact identity 保持不变。目标文件已存在时保存会给出冲突错误,不覆盖另一个资产。 ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Scene.AllowMultipleComponentAttribute` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + + + + + + + + + + + + + + + - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.AllowMultipleComponentAttribute.AllowMultipleComponentAttribute()`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleComponentAttribute.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public sealed class Inno.Scene.AllowMultipleComponentAttribute : System.Attribute`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleComponentAttribute.cs#L9) | Allows multiple instances of a concrete component type on one game object. | — | — | - -### `Inno.Scene.AllowMultipleSystemAttribute` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.AllowMultipleSystemAttribute.AllowMultipleSystemAttribute()`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleSystemAttribute.cs#L9) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public sealed class Inno.Scene.AllowMultipleSystemAttribute : System.Attribute`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleSystemAttribute.cs#L9) | Allows multiple instances of a concrete Inno.Scene.GameSystem type in one scene. | — | — | - -### `Inno.Scene.Components.Transform` - + + + + + + @@ -326,45 +348,9 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.Components.Transform.Transform()`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L31) | Creates a transform with identity local values. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.InverseTransformPoint(Inno.Core.Mathematics.Vector3 point)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L201) | Transforms a world-space point into this transform's local space.
返回:The corresponding local-space point.
`point`:Point expressed in world space. | — | — | -| [`protected override void Inno.Scene.Components.Transform.Reset()`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L206) | Restores this instance to its initial reusable state. | — | — | -| [`public void Inno.Scene.Components.Transform.SetParent(Inno.Scene.Components.Transform? parent)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L153) | Sets the parent while preserving this transform's world-space values.
`parent`:New parent, or null to move to scene level. | — | `System.InvalidOperationException`:Thrown for hierarchy cycles, cross-scene parents, or a non-invertible parent hierarchy. | -| [`public void Inno.Scene.Components.Transform.SetSiblingIndex(int siblingIndex)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L161) | Moves this transform within its current sibling collection.
`siblingIndex`:Requested zero-based sibling index. | — | — | -| [`public void Inno.Scene.Components.Transform.SetWorldTransform(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, Inno.Core.Mathematics.Vector3 scale)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L175) | Atomically applies world-space translation, rotation, and scale.
`position`:Requested world-space translation.
`rotation`:Requested world-space rotation.
`scale`:Requested world-space scale. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.TransformPoint(Inno.Core.Mathematics.Vector3 point)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L190) | Transforms a local-space point through the complete parent hierarchy.
返回:The corresponding world-space point.
`point`:Point expressed in this transform's local space. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.Components.Transform.children { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L133) | Gets child transforms in sibling order. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.localPosition { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L40) | Gets or sets the local position relative to the parent. | — | — | -| [`public Inno.Core.Mathematics.Quaternion Inno.Scene.Components.Transform.localRotation { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L54) | Gets or sets the local rotation relative to the parent. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.localScale { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L68) | Gets or sets the local scale relative to the parent. | — | — | -| [`public Inno.Core.Mathematics.Matrix Inno.Scene.Components.Transform.localToWorldMatrix { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L108) | Gets the exact local-to-world matrix, including the complete parent hierarchy. | — | — | -| [`public Inno.Scene.Components.Transform? Inno.Scene.Components.Transform.parent { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L128) | Gets the parent transform, or null for a scene-level object. | — | — | -| [`public int Inno.Scene.Components.Transform.siblingIndex { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L138) | Gets or sets this transform's index among its siblings. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.worldPosition { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L81) | Gets or sets the world-space position. | — | — | -| [`public Inno.Core.Mathematics.Quaternion Inno.Scene.Components.Transform.worldRotation { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L90) | Gets or sets the world-space rotation. | — | — | -| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.worldScale { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L99) | Gets or sets the world-space scale. | — | — | -| [`public Inno.Core.Mathematics.Matrix Inno.Scene.Components.Transform.worldToLocalMatrix { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L116) | Gets the inverse of the exact local-to-world matrix. | — | `System.InvalidOperationException`:Thrown when a zero-scale hierarchy is not invertible. | -| [`public sealed class Inno.Scene.Components.Transform : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L15) | Stores local transform data and exposes the scene hierarchy relationship. | — | — | - -### `Inno.Scene.EngineAssetContent` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.EngineAssetContent.EngineAssetContent(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L28) | Creates a detached engine asset content snapshot.
`payload`:The complete serialized runtime graph.
`dependencies`:The direct persistent asset dependencies of the graph. | — | `System.ArgumentException`:Thrown when payload is empty. | -| [`public Inno.Assets.AssetDependency[] Inno.Scene.EngineAssetContent.GetDependencies()`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L53) | Gets a detached copy of direct persistent asset dependencies.
返回:A dependency array owned by the caller. | — | — | -| [`public byte[] Inno.Scene.EngineAssetContent.GetPayload()`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L45) | Gets a detached copy of the serialized runtime graph.
返回:A payload copy owned by the caller. | — | — | -| [`public sealed class Inno.Scene.EngineAssetContent`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L11) | Carries immutable scene or prefab runtime content across the runtime-to-authoring asset boundary. | — | — | - -### `Inno.Scene.EngineObject` - + + + @@ -372,19 +358,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Scene.EngineObject.EngineObject()`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L11) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public override bool Inno.Scene.EngineObject.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L71) | Compares this instance to another object using reference identity.
返回:true when obj is this instance.
`obj`:The object to compare with this instance. | — | — | -| [`public override int Inno.Scene.EngineObject.GetHashCode()`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L79) | Returns a stable runtime reference hash code.
返回:The reference hash code. | — | — | -| [`public static bool Inno.Scene.EngineObject.operator ==(Inno.Scene.EngineObject? left, Inno.Scene.EngineObject? right)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L32) | Compares engine objects using reference identity and destroyed-object null semantics.
返回:true when both operands refer to the same live object or both behave as null.
`left`:Left operand.
`right`:Right operand. | — | — | -| [`public static bool Inno.Scene.EngineObject.operator !=(Inno.Scene.EngineObject? left, Inno.Scene.EngineObject? right)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L57) | Compares engine objects using reference identity and destroyed-object null semantics.
返回:true when the operands do not represent the same object.
`left`:Left operand.
`right`:Right operand. | — | — | -| [`public bool Inno.Scene.EngineObject.isDestroyed { get; }`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L18) | Gets whether the engine has destroyed this object. | — | — | -| [`public abstract class Inno.Scene.EngineObject : Inno.Core.Identity.IdentityObject`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L11) | Provides identity and destruction state shared by all managed scene objects. | — | — | - -### `Inno.Scene.GameBehavior` - @@ -397,40 +370,11 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Scene.GameBehavior.GameBehavior()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L10) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.Awake()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L44) | Called once before this behavior first becomes active. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.FixedUpdate()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L79) | Called during the fixed-rate update stage. Use Time.fixedDeltaTime for step timing. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.LateUpdate()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L86) | Called during the late update stage. Use Time.deltaTime for frame timing. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.OnDestroy()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L93) | Called immediately before this behavior is detached and destroyed. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.OnDisable()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L65) | Called when this component stops being active and enabled. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.OnEnable()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L58) | Called when this component becomes active and enabled. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.Start()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L51) | Called once immediately before the first update of this behavior. | — | — | -| [`protected virtual void Inno.Scene.GameBehavior.Update()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L72) | Called during the variable-rate update stage. Use Time.deltaTime for frame timing. | — | — | -| [`public bool Inno.Scene.GameBehavior.enabled { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L22) | Gets or sets whether this component participates in scene lifecycle and frame updates. | Changing this value while the component belongs to a loaded scene immediately reconciles Inno.Scene.GameBehavior.OnEnable and Inno.Scene.GameBehavior.OnDisable against hierarchy activation. | — | -| [`public bool Inno.Scene.GameBehavior.isActiveAndEnabled { get; }`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L39) | Gets whether this component is enabled and active in its owning hierarchy. | — | — | -| [`public abstract class Inno.Scene.GameBehavior : Inno.Scene.GameComponent, Inno.Scene.ISceneLifecycleObject`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L10) | Base component for independently enabled scene functionality with frame lifecycle callbacks. | — | — | - -### `Inno.Scene.GameComponent` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Scene.GameComponent.GameComponent()`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L11) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected virtual void Inno.Scene.GameComponent.Reset()`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L37) | Restores this component to its default state when it is added or explicitly reset. | — | — | -| [`public Inno.Scene.GameObject Inno.Scene.GameComponent.gameObject { get; }`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L21) | Gets the owning game object. | — | `System.InvalidOperationException`:Thrown when the component is detached or destroyed. | -| [`public Inno.Scene.Components.Transform Inno.Scene.GameComponent.transform { get; }`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L32) | Gets the owning game object's transform. | — | `System.InvalidOperationException`:Thrown when the component is detached or destroyed. | -| [`public abstract class Inno.Scene.GameComponent : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L11) | Base type for data and behavior objects attached to a Inno.Scene.GameObject. | — | — | - -### `Inno.Scene.GameObject` - @@ -457,42 +401,11 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const string Inno.Scene.GameObject.defaultTag = "Untagged"`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L20) | Defines the tag assigned to newly created game objects. | — | — | -| [`public Inno.Scene.GameComponent Inno.Scene.GameObject.AddComponent(System.Type componentType)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L201) | Creates and attaches a component of the requested runtime type.
返回:The attached component.
`componentType`:Concrete component type. | — | — | -| [`public TComponent Inno.Scene.GameObject.AddComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L190) | Creates and attaches a component of the requested type.
返回:The attached component. | — | — | -| [`public Inno.Scene.GameComponent Inno.Scene.GameObject.GetComponent(System.Type componentType)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L235) | Gets the first attached component assignable to a runtime type.
返回:The first matching component.
`componentType`:Requested component type. | — | `System.InvalidOperationException`:Thrown when no matching component exists. | -| [`public TComponent Inno.Scene.GameObject.GetComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L216) | Gets the first attached component assignable to the requested type.
返回:The first matching component. | — | `System.InvalidOperationException`:Thrown when no matching component exists. | -| [`public int Inno.Scene.GameObject.GetComponentIndex(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L306) | Gets the attachment index of a component on this object.
返回:The zero-based attachment index.
`component`:Attached component to locate. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameObject.GetComponents()`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L295) | Gets all attached components in attachment order.
返回:A stable component snapshot. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameObject.GetComponents() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L286) | Gets all attached components assignable to the requested type in attachment order.
返回:A stable component snapshot. | — | — | -| [`public bool Inno.Scene.GameObject.HasComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L275) | Gets whether a matching component is attached.
返回:true when a matching component exists. | — | — | -| [`public bool Inno.Scene.GameObject.RemoveComponent(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L360) | Removes and destroys a specific attached component.
返回:true when the component was attached and removed.
`component`:GameComponent instance to remove. | — | `System.InvalidOperationException`:Thrown when attempting to remove the mandatory transform. | -| [`public void Inno.Scene.GameObject.ResetComponent(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L342) | Restores an attached component to the defaults defined by its optional Reset message.
`component`:GameComponent instance to reset. | — | `System.ArgumentNullException`:Thrown when component is null.
`System.InvalidOperationException`:Thrown when the object is invalid or the component is not attached to it. | -| [`public void Inno.Scene.GameObject.SetActive(bool value)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L179) | Changes this object's explicit active state and updates its hierarchy subtree.
`value`:The concrete value read or transformed by this operation. | — | — | -| [`public void Inno.Scene.GameObject.SetComponentIndex(Inno.Scene.GameComponent component, int componentIndex)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L322) | Moves an attached component to a requested attachment index. The mandatory Transform participates in ordering but remains non-removable.
`component`:Attached component to move.
`componentIndex`:Requested zero-based attachment index. | — | — | -| [`public bool Inno.Scene.GameObject.TryGetComponent(out TComponent? component) where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L255) | Tries to get the first attached component assignable to the requested type.
返回:true when a matching component exists.
`component`:Matching component when found. | — | — | -| [`public bool Inno.Scene.GameObject.activeInHierarchy { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L156) | Gets whether this object is active after parent hierarchy state is applied. | — | — | -| [`public bool Inno.Scene.GameObject.activeSelf { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L144) | Gets whether this object is explicitly active. | — | — | -| [`public bool Inno.Scene.GameObject.isPartOfPrefabInstance { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L161) | Gets whether this object retains a prefab source connection. | — | — | -| [`public bool Inno.Scene.GameObject.isRuntimeValid { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L43) | Gets whether this object is live in its owning scene. | — | — | -| [`public Inno.Scene.Layers.GameLayer Inno.Scene.GameObject.layer { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L109) | Gets or sets the single runtime layer used to filter this game object. | GameLayer names are project configuration stored separately from scene data. Scene and prefab serialization persist only the stable numeric layer slot. | — | -| [`public string Inno.Scene.GameObject.name { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L56) | Gets or sets the display name stored in the owning scene. | — | — | -| [`public Inno.Scene.PrefabInstanceInfo? Inno.Scene.GameObject.prefabInstance { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L171) | Gets read-only information about this object's prefab source connection. | — | — | -| [`public Inno.Scene.GameObject? Inno.Scene.GameObject.prefabInstanceRoot { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L166) | Gets the root of this object's prefab instance connection. | — | — | -| [`public Inno.Scene.GameScene Inno.Scene.GameObject.scene { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L51) | Gets the owning scene. | — | `System.InvalidOperationException`:Thrown when this object is destroyed or detached. | -| [`public string Inno.Scene.GameObject.tag { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L84) | Gets or sets the ordinal tag used to categorize and query this game object. | Tag definitions are project configuration stored separately from scene data. Scene and prefab serialization persist the assigned ordinal value even when its current project definition is absent. | `System.ArgumentException`:Thrown when the assigned tag is empty or contains only white-space characters. | -| [`public Inno.Scene.Components.Transform Inno.Scene.GameObject.transform { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L132) | Gets the mandatory transform component. | — | `System.InvalidOperationException`:Thrown when this object is destroyed or incompletely initialized. | -| [`public sealed class Inno.Scene.GameObject : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L15) | Represents a scene-owned object and exposes its component-oriented API. | — | — | - -### `Inno.Scene.GameScene` - + @@ -511,34 +424,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.GameScene.GameScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L36) | Creates an empty scene.
`name`:Initial scene name. | — | — | -| [`public void Inno.Scene.GameScene.AddSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L321) | Registers a game system instance.
`system`:System to register. | — | — | -| [`public Inno.Scene.GameSystem Inno.Scene.GameScene.AddSystem(System.Type systemType)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L303) | Creates and registers a concrete game system by runtime type.
返回:The validated game system that represents the completed operation.
`systemType`:The system type consumed by add system; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public TSystem Inno.Scene.GameScene.AddSystem() where TSystem : Inno.Scene.GameSystem, new()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L288) | Creates and registers a parameterless game system.
返回:The registered system. | — | — | -| [`public Inno.Scene.GameObject Inno.Scene.GameScene.CreateObject(string name = "GameObject")`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L84) | Creates a game object with a mandatory transform component.
返回:The created game object.
`name`:Initial game object name. | — | — | -| [`public bool Inno.Scene.GameScene.DestroyObject(Inno.Scene.GameObject gameObject)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L121) | Recursively destroys a game object and its complete child subtree.
返回:true when the object belonged to this scene and was destroyed.
`gameObject`:Root object to destroy. | — | — | -| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObject(string name)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L191) | Finds the first game object with an ordinally matching name.
返回:The first match, or null when no object matches.
`name`:Name to find. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithLayer(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L258) | Finds every game object assigned to one layer in scene storage order.
返回:A stable snapshot containing every matching game object.
`layer`:The layer slot to find. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithLayers(Inno.Scene.Layers.GameLayerMask layers)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L273) | Finds every game object assigned to any layer contained in a mask.
返回:A stable snapshot in scene storage order.
`layers`:The set of accepted layer slots. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithTag(string tag)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L228) | Finds every game object with an ordinally matching tag in scene storage order.
返回:A stable snapshot containing every matching game object.
`tag`:Tag to find. | — | `System.ArgumentException`:Thrown when tag is empty or contains only white-space characters. | -| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObjectWithLayer(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L243) | Finds the first game object assigned to a layer in scene storage order.
返回:The first matching game object, or null when no object matches.
`layer`:The layer slot to find. | — | — | -| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObjectWithTag(string tag)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L210) | Finds the first game object with an ordinally matching tag in scene storage order.
返回:The first match, or null when no object matches.
`tag`:Tag to find. | — | `System.ArgumentException`:Thrown when tag is empty or contains only white-space characters. | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.GetObjects()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L176) | Gets all committed live game objects in scene storage order.
返回:A stable object snapshot. | — | — | -| [`public int Inno.Scene.GameScene.GetSystemIndex(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L378) | Gets the display index of a registered system.
返回:The zero-based display index.
`system`:Registered system to locate. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.GetSystems()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L363) | Gets registered systems in display and serialization order. Explicit Inno.Scene.GameSystem.order values independently control lifecycle execution priority.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`public Inno.Scene.GameObject Inno.Scene.GameScene.InstantiatePrefab(Inno.Scene.PrefabAsset prefab, Inno.Scene.Components.Transform? parent = null)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L105) | Creates a connected instance of an imported prefab in this loaded scene.
返回:The newly instantiated prefab root.
`prefab`:The prefab asset that supplies the object subtree.
`parent`:Optional parent for the new instance. | — | `System.ArgumentNullException`:The prefab is null.
`System.InvalidOperationException`:The scene is not loaded in its owning world or prefab services are unavailable. | -| [`public bool Inno.Scene.GameScene.RemoveSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L336) | Removes a registered game system.
返回:true when the system was registered.
`system`:System to remove. | — | — | -| [`public void Inno.Scene.GameScene.ResetSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L349) | Explicitly restores a registered system to its default state.
`system`:The system consumed by reset system; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public void Inno.Scene.GameScene.SetSystemIndex(Inno.Scene.GameSystem system, int systemIndex)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L395) | Moves a registered system to a requested display and serialization index. This operation does not change its explicit Inno.Scene.GameSystem.order.
`system`:Registered system to move.
`systemIndex`:Requested zero-based display index. | — | — | -| [`public bool Inno.Scene.GameScene.isLoaded { get; }`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L70) | Gets whether this scene is currently owned by Inno.Scene.SceneManager. | — | — | -| [`public string Inno.Scene.GameScene.name { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L57) | Gets or sets the scene display name. | — | — | -| [`public sealed class Inno.Scene.GameScene : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L17) | Owns a runtime scene, including its objects, components, hierarchy, and systems. | — | — | - -### `Inno.Scene.GameSystem` - @@ -559,32 +444,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`protected Inno.Scene.GameSystem.GameSystem()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L13) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`protected virtual void Inno.Scene.GameSystem.Awake()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L63) | Called once before the system first becomes active. | — | — | -| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.GetComponents() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L125) | Gets all scene components assignable to a requested type.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.GetObjects()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L133) | Gets all committed live game objects in deterministic scene storage order.
返回:The immutable structure snapshot retained by the scene until its object or component structure changes. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnDestroy()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L91) | Called before a system that entered runtime lifecycle is destroyed. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnDisable()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L84) | Called when the system stops being active and enabled. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnEnable()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L77) | Called when the system becomes active and enabled. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnFixedUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L98) | Called during the fixed-rate scene stage. Use Time.fixedDeltaTime for step timing. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnLateUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L112) | Called during the late scene stage. Use Time.deltaTime for frame timing. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.OnUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L105) | Called during the variable-rate scene stage. Use Time.deltaTime for frame timing. | — | — | -| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L144) | Queries game objects containing one required component type.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent where T2 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L158) | Queries game objects containing two required component types.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent where T2 : Inno.Scene.GameComponent where T3 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L178) | Queries game objects containing three required component types.
返回:An immutable snapshot of the values selected by the operation. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.Reset()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L56) | Restores this system to defaults when added or explicitly reset. | — | — | -| [`protected virtual void Inno.Scene.GameSystem.Start()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L70) | Called once immediately before the first update. | — | — | -| [`public bool Inno.Scene.GameSystem.enabled { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L22) | Gets or sets whether this system participates in scene lifecycle updates. | — | — | -| [`public bool Inno.Scene.GameSystem.isActiveAndEnabled { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L39) | Gets whether this system is registered, enabled, and dispatchable. | — | — | -| [`public virtual int Inno.Scene.GameSystem.order { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L44) | Gets the ascending execution order used by the owning scene. | — | — | -| [`protected Inno.Scene.GameScene Inno.Scene.GameSystem.scene { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L49) | Gets the owning scene after this system has been registered. | — | — | -| [`public abstract class Inno.Scene.GameSystem : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable, Inno.Scene.ISceneLifecycleObject`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L13) | Base type for serializable, ordered scene-level behaviors. | — | — | - -### `Inno.Scene.GameTagCatalog` - @@ -596,47 +455,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const string Inno.Scene.GameTagCatalog.settingProtocolId = "inno.scene.tags"`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L21) | Gets the immutable project-setting protocol value used by discovery metadata. | — | — | -| [`public Inno.Scene.GameTagCatalog.GameTagCatalog()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L16) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public bool Inno.Scene.GameTagCatalog.Add(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L108) | Adds one normalized project tag definition.
返回:true when a definition was added.
`tag`:The unique tag to define. | — | `System.ArgumentException`:Thrown when tag is empty or invalid. | -| [`public Inno.Scene.GameTagCatalog Inno.Scene.GameTagCatalog.Clone()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L155) | Creates a detached mutable copy of this catalog.
返回:An independently owned catalog with the same definitions. | — | — | -| [`public Inno.Core.Settings.ProjectScopedId Inno.Scene.GameTagCatalog.GetId(Inno.Core.Settings.ProjectId projectId, string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L72) | Gets the complete project-scoped identity of a defined tag.
返回:The canonical projectId.name identity.
`projectId`:The current project namespace.
`tag`:The configured tag name. | — | — | -| [`public Inno.Core.Settings.ProjectLocalId Inno.Scene.GameTagCatalog.GetLocalId(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L52) | Gets the stable project-independent identity of a defined tag.
返回:The deterministic local identity.
`tag`:The configured tag name. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameTagCatalog.GetTags()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L37) | Gets the defined tags with the immutable default tag first.
返回:An independently owned deterministic tag snapshot. | — | — | -| [`public bool Inno.Scene.GameTagCatalog.IsDefined(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L89) | Determines whether an ordinal tag is defined by the project.
返回:true when the normalized tag is defined.
`tag`:The tag to resolve. | — | `System.ArgumentException`:Thrown when tag is empty or invalid. | -| [`public bool Inno.Scene.GameTagCatalog.Remove(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L134) | Removes one custom project tag definition without rewriting scene assignments.
返回:true when an existing definition was removed.
`tag`:The custom tag to remove. | — | `System.ArgumentException`:Thrown when tag is empty or invalid. | -| [`public static Inno.Core.Settings.ProjectSettingId Inno.Scene.GameTagCatalog.settingId { get; }`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L29) | Gets the stable project setting protocol for the project-wide tag catalog. | — | — | -| [`public sealed class Inno.Scene.GameTagCatalog : Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L16) | Stores the project-wide tag definitions used to author and validate scene object assignments. | — | — | - -### `Inno.Scene.ISceneReloadStateTransfer` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`void Inno.Scene.ISceneReloadStateTransfer.Apply()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L34) | Creates replacement instances and restores their serialized state. | — | — | -| [`void Inno.Scene.ISceneReloadStateTransfer.Complete()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L49) | Finalizes replacement instances after the assembly reload is committed. | — | — | -| [`void Inno.Scene.ISceneReloadStateTransfer.PrepareForActivation()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L29) | Disables active retiring lifecycle objects before the new assembly generation becomes active. | — | — | -| [`void Inno.Scene.ISceneReloadStateTransfer.RestorePreviousState()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L44) | Restores lifecycle state on the previous scene instances after rollback. | — | — | -| [`void Inno.Scene.ISceneReloadStateTransfer.RollbackStructure()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L39) | Restores the previous scene structure after a failed state transfer. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.diagnostics { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L19) | Gets non-fatal state transfer decisions produced while replacing reloadable scene objects. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.recoveryChanges { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L24) | Gets the shared candidate resolutions for actual scene element and asset dependency slots after application. | — | — | -| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.retiredObjects { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L14) | Gets old scene objects whose runtime types belong to the retiring assembly generation. | — | — | -| [`public interface Inno.Scene.ISceneReloadStateTransfer`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L9) | Represents a staged scene object state transfer associated with one assembly reload transaction. | — | — | - -### `Inno.Scene.Layers.GameLayer` - @@ -649,24 +467,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const int Inno.Scene.Layers.GameLayer.C_MAX_COUNT = 32`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L13) | Defines the number of layer slots supported by the runtime bit-mask representation. | — | — | -| [`public Inno.Scene.Layers.GameLayer.GameLayer(int index)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L29) | Creates a layer identifier from a zero-based layer index.
`index`:The layer index in the inclusive range from zero through thirty-one. | — | `System.ArgumentOutOfRangeException`:Thrown when index is outside the supported range. | -| [`public int Inno.Scene.Layers.GameLayer.CompareTo(Inno.Scene.Layers.GameLayer other)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L50) | Compares this layer with another layer by index.
返回:A signed value describing the relative index ordering.
`other`:The value to compare with this instance. | — | — | -| [`public bool Inno.Scene.Layers.GameLayer.Equals(Inno.Scene.Layers.GameLayer other)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L61) | Determines whether another layer identifies the same index.
返回:true when both layers identify the same index.
`other`:The layer to compare with this value. | — | — | -| [`public override bool Inno.Scene.Layers.GameLayer.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L72) | Determines whether this instance and the supplied value represent the same logical state.
返回:true when both values represent the same logical state; otherwise, false.
`obj`:The object to compare with this instance. | — | — | -| [`public override int Inno.Scene.Layers.GameLayer.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L80) | Computes a hash code from the fields that participate in logical equality.
返回:A hash code consistent with the implemented equality contract. | — | — | -| [`public static bool Inno.Scene.Layers.GameLayer.operator ==(Inno.Scene.Layers.GameLayer left, Inno.Scene.Layers.GameLayer right)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L102) | Determines whether two layer identifiers contain the same index.
返回:true when the identifiers are equal.
`left`:The first layer identifier.
`right`:The second layer identifier. | — | — | -| [`public static bool Inno.Scene.Layers.GameLayer.operator !=(Inno.Scene.Layers.GameLayer left, Inno.Scene.Layers.GameLayer right)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L119) | Determines whether two layer identifiers contain different indices.
返回:true when the identifiers are different.
`left`:The first layer identifier.
`right`:The second layer identifier. | — | — | -| [`public override string Inno.Scene.Layers.GameLayer.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L88) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | — | — | -| [`public static Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayer.defaultLayer { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L18) | Gets the built-in default layer stored in slot zero. | — | — | -| [`public int Inno.Scene.Layers.GameLayer.index { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L39) | Gets the zero-based layer index. | — | — | -| [`public readonly struct Inno.Scene.Layers.GameLayer : System.IEquatable, System.IComparable`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L8) | Identifies one of the thirty-two runtime layers available to scene objects. | — | — | - -### `Inno.Scene.Layers.GameLayerCatalog` - @@ -688,51 +488,12 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const string Inno.Scene.Layers.GameLayerCatalog.settingProtocolId = "inno.scene.layers"`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L23) | Gets the immutable project-setting protocol value used by discovery metadata. | — | — | -| [`public Inno.Scene.Layers.GameLayerCatalog.GameLayerCatalog()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L44) | Creates a layer stack containing the immutable default layer. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.CanInteract(Inno.Scene.Layers.GameLayer first, Inno.Scene.Layers.GameLayer second)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L386) | Determines whether two layer slots may interact.
返回:true when interaction is enabled.
`first`:The first layer.
`second`:The second layer. | — | — | -| [`public Inno.Scene.Layers.GameLayerCatalog Inno.Scene.Layers.GameLayerCatalog.Clone()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L432) | Creates a detached copy of this stack.
返回:An independent layer stack. | — | — | -| [`public void Inno.Scene.Layers.GameLayerCatalog.Define(Inno.Scene.Layers.GameLayer layer, string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L293) | Defines or renames a project layer without accepting an authored ID.
`layer`:The compact runtime slot.
`name`:The unique display name. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.Layers.GameLayerCatalog.GetDefinitions()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L71) | Gets an immutable snapshot of every named layer ordered by slot index.
返回:The ordered layer-definition snapshot. | — | — | -| [`public Inno.Scene.Layers.GameLayerId? Inno.Scene.Layers.GameLayerCatalog.GetId(Inno.Core.Settings.ProjectId projectId, Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L146) | Gets the complete identity assigned to a layer slot.
返回:The qualified identity, or null.
`projectId`:The current project namespace.
`layer`:The layer slot to resolve. | — | — | -| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerCatalog.GetInteractionMask(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L368) | Gets the interaction mask assigned to one layer.
返回:The configured interaction mask.
`layer`:The source layer. | — | — | -| [`public Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayerCatalog.GetLayer(string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L259) | Resolves an ordinal layer name to its compact slot.
返回:The matching runtime layer.
`name`:The configured layer name. | — | — | -| [`public Inno.Core.Settings.ProjectLocalId? Inno.Scene.Layers.GameLayerCatalog.GetLocalId(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L127) | Gets the stable project-independent identity assigned to a layer slot.
返回:The local identity, or null.
`layer`:The layer slot to resolve. | — | — | -| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerCatalog.GetMask(System.Collections.Generic.IEnumerable names)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L275) | Creates a mask from configured ordinal layer names.
返回:A mask containing every resolved layer.
`names`:The configured names to include. | — | — | -| [`public string? Inno.Scene.Layers.GameLayerCatalog.GetName(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L112) | Gets the display name assigned to a layer slot.
返回:The configured name, or null.
`layer`:The layer slot to resolve. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.IsDefined(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L97) | Determines whether a layer slot is defined.
返回:true when the slot is defined.
`layer`:The layer slot to test. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.Remove(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L347) | Removes a custom layer definition while retaining its numeric slot and interactions.
返回:true when a definition was removed.
`layer`:The custom layer slot to undefine. | — | — | -| [`public void Inno.Scene.Layers.GameLayerCatalog.SetInteraction(Inno.Scene.Layers.GameLayer first, Inno.Scene.Layers.GameLayer second, bool canInteract)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L406) | Sets a symmetric interaction pair.
`first`:The first layer.
`second`:The second layer.
`canInteract`:Whether interaction is enabled. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(Inno.Core.Settings.ProjectId projectId, Inno.Scene.Layers.GameLayerId id, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L199) | Tries to resolve a qualified identity to its compact runtime slot.
返回:true when the identity belongs to this project and is defined.
`projectId`:The current project namespace.
`id`:The complete layer identity.
`layer`:The resolved runtime slot. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(Inno.Core.Settings.ProjectLocalId localId, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L166) | Tries to resolve a local identity to its compact runtime slot.
返回:true when the identity is defined.
`localId`:The stable project-local identity.
`layer`:The resolved runtime slot. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(string name, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L233) | Tries to resolve an ordinal layer name to its compact slot.
返回:true when a matching layer exists.
`name`:The layer name to find.
`layer`:The resolved layer. | — | — | -| [`public int Inno.Scene.Layers.GameLayerCatalog.count { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L56) | Gets the number of currently named layer slots. | — | — | -| [`public static Inno.Core.Settings.ProjectSettingId Inno.Scene.Layers.GameLayerCatalog.settingId { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L39) | Gets the stable project setting protocol for the project-wide layer catalog. | — | — | -| [`public sealed class Inno.Scene.Layers.GameLayerCatalog : Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L18) | Stores the named layer catalog and symmetric layer-interaction matrix used by a project. | — | — | - -### `Inno.Scene.Layers.GameLayerDefinition` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.Layers.GameLayerDefinition.GameLayerDefinition(Inno.Core.Settings.ProjectLocalId localId, Inno.Scene.Layers.GameLayer layer, string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L24) | Creates an immutable layer definition.
`localId`:The stable project-independent identity.
`layer`:The layer slot represented by the definition.
`name`:The non-empty display and lookup name. | — | — | -| [`public Inno.Scene.Layers.GameLayerId Inno.Scene.Layers.GameLayerDefinition.GetId(Inno.Core.Settings.ProjectId projectId)`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L60) | Resolves the complete identity under a project namespace.
返回:The canonical project-scoped layer identity.
`projectId`:The current project namespace. | — | — | -| [`public Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayerDefinition.layer { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L44) | Gets the layer slot represented by this definition. | — | — | -| [`public Inno.Core.Settings.ProjectLocalId Inno.Scene.Layers.GameLayerDefinition.localId { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L39) | Gets the stable project-independent identity. | — | — | -| [`public string Inno.Scene.Layers.GameLayerDefinition.name { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L49) | Gets the unique ordinal layer name. | — | — | -| [`public sealed class Inno.Scene.Layers.GameLayerDefinition`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L10) | Describes one named layer slot in a Inno.Scene.Layers.GameLayerCatalog snapshot. | — | — | - -### `Inno.Scene.Layers.GameLayerId` - @@ -744,23 +505,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.Layers.GameLayerId.GameLayerId(Inno.Core.Settings.ProjectId projectId, Inno.Core.Settings.ProjectLocalId name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L34) | Creates a project-scoped layer identity from its project and local parts.
`projectId`:The current project identity.
`name`:The stable project-local layer identity. | — | — | -| [`public Inno.Scene.Layers.GameLayerId.GameLayerId(Inno.Core.Settings.ProjectScopedId value)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L18) | Creates a project-scoped layer identity.
`value`:The canonical qualified identity. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerId.Equals(Inno.Scene.Layers.GameLayerId other)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Scene.Layers.GameLayerId.Equals(object obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Scene.Layers.GameLayerId.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Scene.Layers.GameLayerId.operator ==(Inno.Scene.Layers.GameLayerId left, Inno.Scene.Layers.GameLayerId right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Scene.Layers.GameLayerId.operator !=(Inno.Scene.Layers.GameLayerId left, Inno.Scene.Layers.GameLayerId right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Scene.Layers.GameLayerId.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L76) | Formats this value as a human-readable representation.
返回:The qualified identity. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerId.isValid { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L68) | Gets whether this value contains a usable identity. | — | — | -| [`public string Inno.Scene.Layers.GameLayerId.value { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L63) | Gets the canonical projectId.name identity string. | — | — | -| [`public readonly record struct Inno.Scene.Layers.GameLayerId : System.IEquatable`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Identifies one project-scoped game layer independently from its compact runtime slot. | — | — | - -### `Inno.Scene.Layers.GameLayerMask` - @@ -779,72 +523,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.Layers.GameLayerMask.GameLayerMask(uint value)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L27) | Creates a mask from its raw thirty-two-bit representation.
`value`:The raw mask bits. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerMask.Contains(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L67) | Determines whether the supplied layer is contained in this mask.
返回:true when the layer bit is enabled.
`layer`:The layer consumed by contains; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public bool Inno.Scene.Layers.GameLayerMask.Equals(Inno.Scene.Layers.GameLayerMask other)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L100) | Determines whether another mask contains the same layer bits.
返回:true when both masks contain the same bits.
`other`:The mask to compare with this value. | — | — | -| [`public override bool Inno.Scene.Layers.GameLayerMask.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L111) | Determines whether this instance and the supplied value represent the same logical state.
返回:true when both values represent the same logical state; otherwise, false.
`obj`:The object to compare with this instance. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.FromLayers(System.Collections.Generic.IEnumerable layers)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L49) | Creates a mask containing the supplied layers.
返回:A mask containing every supplied layer.
`layers`:The layers whose bits should be enabled. | — | `System.ArgumentNullException`:Thrown when layers is null. | -| [`public override int Inno.Scene.Layers.GameLayerMask.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L119) | Computes a hash code from the fields that participate in logical equality.
返回:A hash code consistent with the implemented equality contract. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator &(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L158) | Retains only layer bits enabled in both masks.
返回:The bitwise intersection of both masks.
`left`:The first mask.
`right`:The second mask. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator |(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L141) | Combines the enabled bits from two masks.
返回:The bitwise union of both masks.
`left`:The first mask.
`right`:The second mask. | — | — | -| [`public static bool Inno.Scene.Layers.GameLayerMask.operator ==(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L186) | Determines whether two masks contain the same bits.
返回:true when both masks are equal.
`left`:The first mask.
`right`:The second mask. | — | — | -| [`public static bool Inno.Scene.Layers.GameLayerMask.operator !=(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L203) | Determines whether two masks contain different bits.
返回:true when both masks differ.
`left`:The first mask.
`right`:The second mask. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator ~(Inno.Scene.Layers.GameLayerMask mask)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L172) | Inverts every layer bit in a mask.
返回:The complement of the supplied mask.
`mask`:The mask to invert. | — | — | -| [`public override string Inno.Scene.Layers.GameLayerMask.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L127) | Formats this value as a human-readable representation.
返回:The human-readable representation of this value. | — | — | -| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.With(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L78) | Returns a mask with the supplied layer enabled.
返回:The updated immutable mask.
`layer`:The layer consumed by with; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.Without(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L89) | Returns a mask with the supplied layer disabled.
返回:The updated immutable mask.
`layer`:The layer consumed by without; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.everything { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L19) | Gets a mask containing every supported layer. | — | — | -| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.none { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L14) | Gets a mask containing no layers. | — | — | -| [`public uint Inno.Scene.Layers.GameLayerMask.value { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L35) | Gets the raw thirty-two-bit mask value. | — | — | -| [`public readonly struct Inno.Scene.Layers.GameLayerMask : System.IEquatable`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L9) | Stores a compact set of scene layers for rendering, physics, and query filtering. | — | — | - -### `Inno.Scene.MissingGameComponent` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Extensibility.Types.TypeRef Inno.Scene.MissingGameComponent.missingType { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L45) | Gets the logical identity of the unavailable component type. | — | — | -| [`public string Inno.Scene.MissingGameComponent.missingTypeName { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L50) | Gets the last known managed type name for diagnostics and editor presentation. | — | — | -| [`public sealed class Inno.Scene.MissingGameComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L19) | Preserves the identity, order, and serialized state of a component whose managed type is unavailable. | The scene serializer and script reload pipeline create this placeholder automatically. It does not retain the unavailable managed System.Type or any object from its collectible assembly load context. | — | - -### `Inno.Scene.MissingGameSystem` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Extensibility.Types.TypeRef Inno.Scene.MissingGameSystem.missingType { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L46) | Gets the logical identity of the unavailable system type. | — | — | -| [`public string Inno.Scene.MissingGameSystem.missingTypeName { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L51) | Gets the last known managed type name for diagnostics and editor presentation. | — | — | -| [`public sealed class Inno.Scene.MissingGameSystem : Inno.Scene.GameSystem`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L19) | Preserves the identity, order, and serialized state of a scene system whose managed type is unavailable. | The scene serializer and script reload pipeline create this placeholder automatically. It remains disabled and does not retain the unavailable managed System.Type or its collectible assembly load context. | — | - -### `Inno.Scene.PrefabAsset` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.PrefabAsset.PrefabAsset()`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L18) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public static Inno.Scene.PrefabAsset Inno.Scene.PrefabAsset.Capture(Inno.Scene.GameObject root, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L40) | Captures a game object subtree into a new unsaved prefab asset.
返回:A prefab asset ready to save.
`root`:Prefab source root.
`serialization`:The serialization registry that owns the active prefab converter generation.
`assets`:The runtime asset generation used to resolve persistent references in the prefab graph. | — | — | -| [`public Inno.Scene.EngineAssetContent Inno.Scene.PrefabAsset.CaptureContent()`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L133) | Captures the immutable runtime payload and dependency descriptors required by the prefab asset pipeline.
返回:A detached authoring-content snapshot owned by the caller. | — | — | -| [`public static Inno.Scene.PrefabAsset Inno.Scene.PrefabAsset.CreateImported(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L112) | Creates an imported prefab asset from validated runtime content produced by the prefab asset pipeline.
返回:A prefab asset ready for an asset import writer to commit.
`payload`:The complete serialized runtime object subtree.
`dependencies`:The direct persistent asset dependencies captured with the subtree. | — | — | -| [`public Inno.Scene.GameObject Inno.Scene.PrefabAsset.Instantiate(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Scene.Components.Transform? parent = null)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L77) | Instantiates this prefab into a scene using newly generated object identities.
返回:The instantiated root game object.
`scene`:Target scene.
`serialization`:The serialization registry that owns the active prefab converter generation.
`assets`:The runtime asset generation used to resolve persistent references in the prefab graph.
`parent`:Optional target parent. | — | — | -| [`public sealed class Inno.Scene.PrefabAsset : Inno.Assets.AssetObject`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L18) | Stores a persistent game object subtree that can be instantiated repeatedly. | — | — | - -### `Inno.Scene.PrefabInstanceInfo` - @@ -854,71 +532,27 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.GameObject Inno.Scene.PrefabInstanceInfo.instanceRoot { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L47) | Gets the root object of this prefab instance connection. | — | — | -| [`public bool Inno.Scene.PrefabInstanceInfo.isMissing { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L62) | Gets whether the source prefab was unavailable during restoration. | — | — | -| [`public bool Inno.Scene.PrefabInstanceInfo.isRoot { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L52) | Gets whether this object is the instance connection root. | — | — | -| [`public bool Inno.Scene.PrefabInstanceInfo.isVariant { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L57) | Gets whether the connection originates from a prefab variant. | — | — | -| [`public int Inno.Scene.PrefabInstanceInfo.orphanedOverrideCount { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L72) | Gets the number of retained overrides that no longer match the current source. | — | — | -| [`public int Inno.Scene.PrefabInstanceInfo.overrideCount { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L67) | Gets the number of retained overrides for this instance connection. | — | — | -| [`public System.Guid Inno.Scene.PrefabInstanceInfo.sourceAssetId { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L37) | Gets the persistent identity of the source prefab asset. | — | — | -| [`public System.Guid Inno.Scene.PrefabInstanceInfo.sourceObjectId { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L42) | Gets the source-local identity represented by this object. | — | — | -| [`public sealed class Inno.Scene.PrefabInstanceInfo`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L10) | Describes the read-only source connection retained by one prefab instance object. | — | — | - -### `Inno.Scene.SceneAsset` - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.SceneAsset.SceneAsset()`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L16) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public static Inno.Scene.SceneAsset Inno.Scene.SceneAsset.Capture(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L38) | Captures a runtime scene into a new unsaved scene asset.
返回:A detached scene asset that an authoring asset database can save.
`scene`:Scene to capture.
`serialization`:The serialization registry that owns the active scene converter generation.
`assets`:The asset database generation used to resolve references while collecting dependencies. | — | — | -| [`public Inno.Scene.EngineAssetContent Inno.Scene.SceneAsset.CaptureContent()`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L136) | Captures the immutable runtime payload and dependency descriptors required by the scene asset pipeline.
返回:A detached authoring-content snapshot owned by the caller. | — | — | -| [`public void Inno.Scene.SceneAsset.CaptureFrom(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L61) | Replaces the pending source content with a fresh capture while preserving this asset identity.
`scene`:Scene to capture.
`serialization`:The serialization registry that owns the active scene converter generation.
`assets`:The asset database generation used to resolve references while collecting dependencies. | — | — | -| [`public static Inno.Scene.SceneAsset Inno.Scene.SceneAsset.CreateImported(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L115) | Creates an imported scene asset from validated runtime content produced by the scene asset pipeline.
返回:A scene asset ready for an asset import writer to commit.
`payload`:The complete serialized runtime scene graph.
`dependencies`:The direct persistent asset dependencies captured with the scene graph. | — | — | -| [`public Inno.Scene.GameScene Inno.Scene.SceneAsset.Instantiate(Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L88) | Creates an unloaded runtime scene and acquires its hard asset dependencies.
返回:A newly allocated scene.
`serialization`:The serialization registry that owns the active scene converter generation.
`assets`:The runtime asset generation used to resolve persistent references in the scene graph. | — | — | -| [`protected override void Inno.Scene.SceneAsset.OnRuntimePayloadChanged(System.ReadOnlyMemory previousPayload, System.ReadOnlyMemory currentPayload)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L156) | Rebuilds runtime-derived state after the serialized asset payload changes.
`previousPayload`:The previous payload consumed by on runtime payload changed; ownership remains with the caller unless explicitly stated otherwise.
`currentPayload`:The current payload consumed by on runtime payload changed; ownership remains with the caller unless explicitly stated otherwise. | — | — | -| [`public sealed class Inno.Scene.SceneAsset : Inno.Assets.AssetObject`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L16) | Stores imported scene source that can create independent runtime scenes. | — | — | - -### `Inno.Scene.SceneContentSource` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.References.ContentReadScope Inno.Scene.SceneContentSource.CreateScope(Inno.Scene.SceneWorld world)`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L22) | Captures the ordered roots and primary identity of one scene world.
返回:A caller-owned, operation-scoped identity view.
`world`:The owner whose current scene set should be visible. | — | — | -| [`public static Inno.References.ContentReadScope Inno.Scene.SceneContentSource.CreateScope(System.Collections.Generic.IEnumerable scenes, Inno.Scene.GameScene? activeScene)`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L40) | Captures an explicit coherent scene presentation selected by a host.
返回:A caller-owned scope that retains no scene instances.
`scenes`:The ordered live scene roots.
`activeScene`:The optional primary root, which must be in the collection. | — | — | -| [`public static class Inno.Scene.SceneContentSource`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L11) | Projects loaded scenes into the shared content protocol without depending on a rendering or audio model. | — | — | - -### `Inno.Scene.SceneElementSerialization` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static byte[] Inno.Scene.SceneElementSerialization.CaptureState(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L39) | Captures one element's logical type, neutral properties, asset dependencies and scene-reference aliases.
返回:Neutral element bytes suitable for History and restoration without retaining the current managed type.
`target`:The live component or system, including a missing placeholder.
`serialization`:The owner's current serializer generation.
`assets`:The owner's complete asset resolver used during capture. | — | `System.ArgumentException`:The target is not a component or system.
`System.InvalidOperationException`:The element is not live in a loaded scene. | -| [`public static Inno.Scene.GameComponent Inno.Scene.SceneElementSerialization.RestoreComponent(Inno.Scene.GameObject owner, Inno.Extensibility.Types.TypeRef type, System.Guid persistentId, int componentIndex, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L166) | Recreates one component without invoking Reset and restores its persistent properties.
返回:The recreated component, or a preserved missing placeholder when its logical type is unavailable.
`owner`:The live GameObject that will own the component.
`type`:The reload-safe identity of the current component implementation.
`persistentId`:The component instance identity to preserve.
`componentIndex`:The requested attachment index.
`stateData`:Complete neutral element bytes produced by CaptureState, including missing-state metadata.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The resolver used to materialize canonical asset references in restored properties. | — | `System.ArgumentNullException`:Thrown when owner is null.
`System.IO.InvalidDataException`:Thrown when the payload is invalid or the stable type resolves to an incompatible component.
`System.InvalidOperationException`:Thrown when the identity or component multiplicity conflicts, property restoration is incomplete, or a failed restoration cannot remove its partially created component. | -| [`public static void Inno.Scene.SceneElementSerialization.RestoreState(Inno.Scene.EngineObject target, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L101) | Restores a captured element state into the same logical type, including neutral state on a missing placeholder.
`target`:The attached element receiving the state.
`stateData`:Bytes produced by Inno.Scene.SceneElementSerialization.CaptureState(Inno.Scene.EngineObject,Inno.Core.Serialization.SerializationRegistry,Inno.Assets.IAssetReferenceResolver).
`serialization`:The owner's current serializer generation.
`assets`:The owner's complete asset resolver. | — | `System.IO.InvalidDataException`:The payload is invalid or represents a different logical type.
`System.InvalidOperationException`:Property or reference restoration is incomplete. | -| [`public static Inno.Scene.GameSystem Inno.Scene.SceneElementSerialization.RestoreSystem(Inno.Scene.GameScene scene, Inno.Extensibility.Types.TypeRef type, System.Guid persistentId, int systemIndex, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L239) | Recreates one scene system without invoking Reset and restores its persistent properties.
返回:The recreated system, or a preserved missing placeholder when its logical type is unavailable.
`scene`:The live loaded scene that will own the system.
`type`:The reload-safe identity of the current system implementation.
`persistentId`:The system instance identity to preserve.
`systemIndex`:The requested display and serialization index.
`stateData`:Complete neutral element bytes produced by CaptureState, including missing-state metadata.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The resolver used to materialize canonical asset references in restored properties. | — | `System.ArgumentNullException`:Thrown when scene is null.
`System.IO.InvalidDataException`:Thrown when the payload is invalid or the stable type resolves to an incompatible system.
`System.InvalidOperationException`:Thrown when the identity or system multiplicity conflicts, property restoration is incomplete, or a failed restoration cannot remove its partially created system. | -| [`public static class Inno.Scene.SceneElementSerialization`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L16) | Recreates individual scene components and systems from stable type and object identities. | — | — | - -### `Inno.Scene.SceneManager` - + + + + + + + + + + + + + + + + + + + + + @@ -937,108 +571,6 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static void Inno.Scene.SceneManager.FixedUpdate(float fixedDeltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L157) | Advances fixed-step scene lifecycle callbacks in the current runtime session.
`fixedDeltaTime`:The fixed simulation interval in seconds. | — | — | -| [`public static int Inno.Scene.SceneManager.GetSceneIndex(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L39) | Gets the hierarchy index of a scene loaded by the current runtime session.
返回:The zero-based hierarchy index.
`scene`:The loaded scene to locate. | — | — | -| [`public static void Inno.Scene.SceneManager.LateUpdate(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L173) | Advances late scene lifecycle callbacks in the current runtime session.
`deltaTime`:The elapsed frame time in seconds. | — | — | -| [`public static Inno.Scene.GameScene Inno.Scene.SceneManager.LoadNewScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/SceneManager.cs#L87) | Creates and loads a new active scene in the current runtime session.
返回:The newly created and loaded scene.
`name`:The initial scene display name. | — | — | -| [`public static Inno.Scene.GameScene Inno.Scene.SceneManager.LoadNewSceneAdditive(string name = "Untitled Scene", bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L101) | Creates and additively loads a new scene in the current runtime session.
返回:The newly created and loaded scene.
`name`:The initial scene display name.
`makeActive`:Whether the new scene becomes active. | — | — | -| [`public static void Inno.Scene.SceneManager.LoadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L61) | Replaces the current session scene set with one active scene.
`scene`:The scene to load. | — | — | -| [`public static void Inno.Scene.SceneManager.LoadSceneAdditive(Inno.Scene.GameScene scene, bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L72) | Loads a scene alongside the current session scene set.
`scene`:The scene to load additively.
`makeActive`:Whether the loaded scene becomes active. | — | — | -| [`public static void Inno.Scene.SceneManager.MoveGameObjectToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L124) | Moves a live object subtree into another scene owned by the current runtime session.
`gameObject`:The live root object to move.
`destination`:The loaded destination scene. | — | — | -| [`public static void Inno.Scene.SceneManager.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L113) | Makes a loaded scene active in the current runtime session.
`scene`:The loaded scene to activate. | — | — | -| [`public static void Inno.Scene.SceneManager.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L50) | Moves a loaded scene to a hierarchy index without changing the active scene.
`scene`:The loaded scene to move.
`sceneIndex`:The requested zero-based hierarchy index. | — | — | -| [`public static void Inno.Scene.SceneManager.UnloadActiveScene()`](../../src/content/scene/Inno.Scene/SceneManager.cs#L133) | Unloads the active scene in the current runtime session when one exists. | — | — | -| [`public static void Inno.Scene.SceneManager.UnloadAllScenes()`](../../src/content/scene/Inno.Scene/SceneManager.cs#L149) | Unloads every scene from the current runtime session. | — | — | -| [`public static bool Inno.Scene.SceneManager.UnloadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L144) | Unloads one scene from the current runtime session.
返回:true when the scene was loaded; otherwise, false.
`scene`:The scene to unload. | — | — | -| [`public static void Inno.Scene.SceneManager.Update(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L165) | Advances variable-step scene lifecycle callbacks in the current runtime session.
`deltaTime`:The elapsed frame time in seconds. | — | — | -| [`public static Inno.Scene.GameScene? Inno.Scene.SceneManager.activeScene { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L18) | Gets the active scene in the current runtime session. | — | — | -| [`public static bool Inno.Scene.SceneManager.hasActiveScene { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L23) | Gets whether the current runtime session has an active scene. | — | — | -| [`public static System.Collections.Generic.IReadOnlyList Inno.Scene.SceneManager.loadedScenes { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L28) | Gets an immutable snapshot of scenes loaded by the current runtime session. | — | — | -| [`public static class Inno.Scene.SceneManager`](../../src/content/scene/Inno.Scene/SceneManager.cs#L13) | Provides Unity-style scene operations by resolving the world in the current runtime execution context. | This façade owns no mutable scene state. Engine internals should depend on a concrete Inno.Scene.SceneWorld and only script-facing code should use this type. | — | - -### `Inno.Scene.ScenePropertySerialization` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static byte[] Inno.Scene.ScenePropertySerialization.CaptureProperties(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L82) | Captures all persistent properties without serializing the complete scene.
返回:Neutral bytes containing independently encoded properties.
`target`:The live scene object whose state should be captured.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The resolver used to preserve canonical asset references in captured values. | — | `System.ArgumentNullException`:Thrown when target is null.
`System.InvalidOperationException`:Thrown when the target is destroyed or not owned by a loaded scene. | -| [`public static byte[] Inno.Scene.ScenePropertySerialization.CaptureProperty(Inno.Scene.EngineObject target, string propertyName, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L43) | Captures one persistent property without serializing the complete scene.
返回:Neutral bytes that can be restored into the same logical object identity.
`target`:The live scene object containing the property.
`propertyName`:The exact serialized member key.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The resolver used to preserve canonical asset references in captured values. | — | `System.ArgumentNullException`:Thrown when target is null.
`System.ArgumentException`:Thrown when propertyName is empty or unknown.
`System.InvalidOperationException`:Thrown when the target is destroyed or not owned by a loaded scene. | -| [`public static System.Collections.Generic.IReadOnlyList Inno.Scene.ScenePropertySerialization.CapturePropertySnapshots(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L113) | Captures each persistent property independently while preserving scene-reference identities.
返回:Stable ordered property snapshots suitable for compact delta comparison.
`target`:Live scene object whose properties should be captured.
`serialization`:Registry owning the active converter generation.
`assets`:Resolver used to preserve canonical asset references. | — | — | -| [`public static Inno.Core.Serialization.SerializationPropertyRestoreResult Inno.Scene.ScenePropertySerialization.RestoreProperties(Inno.Scene.EngineObject target, System.ReadOnlySpan data, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Core.Serialization.SerializationPropertyRestoreMode mode = Inno.Core.Serialization.SerializationPropertyRestoreMode.Strict)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L156) | Restores independently captured properties into a live scene object.
返回:A summary of restored, ignored, and incompatible properties.
`target`:The current object generation receiving the values.
`data`:Bytes produced by Inno.Scene.ScenePropertySerialization.CaptureProperty(Inno.Scene.EngineObject,System.String,Inno.Core.Serialization.SerializationRegistry,Inno.Assets.IAssetReferenceResolver) or Inno.Scene.ScenePropertySerialization.CaptureProperties(Inno.Scene.EngineObject,Inno.Core.Serialization.SerializationRegistry,Inno.Assets.IAssetReferenceResolver).
`mode`:The compatibility policy applied to matching current properties.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The resolver used to materialize canonical asset references in restored values. | — | `System.ArgumentNullException`:Thrown when target is null.
`System.InvalidOperationException`:Thrown when strict restoration fails or the target is not in a loaded scene. | -| [`public static class Inno.Scene.ScenePropertySerialization`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L14) | Captures and restores individual scene-object properties while preserving scene reference identities. | — | — | - -### `Inno.Scene.SceneReloadDiagnostic` - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public string Inno.Scene.SceneReloadDiagnostic.code { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L34) | Gets the stable diagnostic code. | — | — | -| [`public string Inno.Scene.SceneReloadDiagnostic.currentPropertyType { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L69) | Gets the current declared property type name. | — | — | -| [`public string Inno.Scene.SceneReloadDiagnostic.message { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L44) | Gets the human-readable diagnostic message. | — | — | -| [`public System.Guid Inno.Scene.SceneReloadDiagnostic.objectPersistentId { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L54) | Gets the persistent identity of the affected component or system. | — | — | -| [`public string Inno.Scene.SceneReloadDiagnostic.previousPropertyType { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L64) | Gets the previous declared property type name. | — | — | -| [`public string Inno.Scene.SceneReloadDiagnostic.propertyName { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L59) | Gets the incompatible serialized property name. | — | — | -| [`public System.Guid Inno.Scene.SceneReloadDiagnostic.scenePersistentId { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L49) | Gets the persistent identity of the affected scene. | — | — | -| [`public Inno.Core.Diagnostics.DiagnosticSeverity Inno.Scene.SceneReloadDiagnostic.severity { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L39) | Gets the diagnostic severity. | — | — | -| [`public sealed class Inno.Scene.SceneReloadDiagnostic`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L9) | Describes a non-fatal issue encountered while migrating scene state to a new assembly generation. | — | — | - -### `Inno.Scene.SceneReloadService` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.SceneReloadService.SceneReloadService(Inno.Scene.SceneWorld world, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L34) | Creates a scene reload service bound to one serialization generation owner.
`serialization`:The serialization registry used to capture and restore scene state.
`world`:The isolated scene world whose active generation is captured.
`assets`:The asset reference generation used throughout capture, activation, and rollback. | — | `System.ArgumentNullException`:Thrown when world, serialization, or assets is null. | -| [`public Inno.Scene.ISceneReloadStateTransfer Inno.Scene.SceneReloadService.Capture(Inno.Extensibility.Types.TypeCacheReloadContext context)`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L53) | Captures all loaded scene objects affected by a prepared type-cache reload.
返回:A staged state transfer that has not modified live objects.
`context`:The prepared type-cache reload context. | — | — | -| [`public sealed class Inno.Scene.SceneReloadService`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L12) | Creates scene state-transfer transactions for assembly generation changes. | — | — | - -### `Inno.Scene.SceneSubtreeSerialization` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static byte[] Inno.Scene.SceneSubtreeSerialization.Capture(Inno.Scene.GameObject root, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L37) | Captures one live GameObject and all descendants without serializing unrelated scene objects.
返回:Neutral bytes containing the subtree structure, components, properties, and identity references.
`root`:The live subtree root.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The asset-reference resolver used to encode referenced asset identities. | — | `System.ArgumentNullException`:Thrown when root is null.
`System.InvalidOperationException`:Thrown when the root is destroyed or detached. | -| [`public static Inno.Scene.GameObject Inno.Scene.SceneSubtreeSerialization.Restore(Inno.Scene.GameScene scene, System.ReadOnlySpan data, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Scene.Components.Transform? parent, int siblingIndex)`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L81) | Restores a previously captured subtree into a loaded scene with its original persistent identities.
返回:The restored subtree root.
`scene`:The loaded destination scene.
`data`:Bytes created by Inno.Scene.SceneSubtreeSerialization.Capture(Inno.Scene.GameObject,Inno.Core.Serialization.SerializationRegistry,Inno.Assets.IAssetReferenceResolver).
`parent`:The optional external parent restored after the subtree is created.
`siblingIndex`:The requested sibling index under parent or at the scene root.
`serialization`:The serialization registry that owns the active converter generation.
`assets`:The asset-reference resolver used to restore referenced asset identities. | — | `System.ArgumentNullException`:Thrown when scene is null.
`System.InvalidOperationException`:Thrown when the scene is unavailable or an identity conflicts. | -| [`public static class Inno.Scene.SceneSubtreeSerialization`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L14) | Captures and restores one GameObject subtree with its persistent object and component identities. | — | — | - -### `Inno.Scene.SceneTypeResolutionException` - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scene.SceneTypeResolutionException.SceneTypeResolutionException(System.Guid stableTypeId, string elementKind)`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L23) | Creates an exception for one unresolved serialized scene type.
`stableTypeId`:The stable identity stored by the serialized scene element.
`elementKind`:The human-readable scene element kind, such as component or system. | — | `System.ArgumentException`:Thrown when stableTypeId is empty or elementKind is empty. | -| [`public string Inno.Scene.SceneTypeResolutionException.elementKind { get; }`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L44) | Gets the scene element kind that expected the missing type. | — | — | -| [`public System.Guid Inno.Scene.SceneTypeResolutionException.stableTypeId { get; }`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L39) | Gets the unresolved stable type identity. | — | — | -| [`public sealed class Inno.Scene.SceneTypeResolutionException : System.Exception`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L9) | Reports that a serialized scene element cannot be created because its stable type is not present in the active type catalog. | — | — | - -### `Inno.Scene.SceneWorld` - @@ -1062,45 +594,351 @@ History 通过 SceneEdits 捕获这些 bytes,不直接使用上面的 Host 工 + + + + + + + + + + + + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public Inno.Scene.SceneWorld.SceneWorld(Inno.Core.Identity.IdentityAllocator identities, Inno.Extensibility.Types.TypeCatalog types)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L41) | Creates an empty scene world bound to one session identity allocator.
`identities`:The allocator that owns identities for scenes and their elements.
`types`:The host type catalog used to derive scene component and system generations. | — | `System.ArgumentNullException`:Thrown when identities is null. | -| [`public void Inno.Scene.SceneWorld.ConfigurePrefabInstantiation(Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L73) | Binds the session-owned serialization and asset generations used by scene prefab instances.
`serialization`:The active serialization registry for this session.
`assets`:The asset resolver that owns prefab dependencies. | — | `System.InvalidOperationException`:The binding was already configured. | -| [`public void Inno.Scene.SceneWorld.Dispose()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L439) | Unloads every scene and permanently releases this world. | — | — | -| [`public System.IDisposable Inno.Scene.SceneWorld.EnterScope()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L144) | Binds this world and its identity allocator to the current asynchronous execution context.
返回:A strict last-in-first-out execution scope owned by the caller. | — | `System.ObjectDisposedException`:Thrown when this world has been disposed. | -| [`public TObject? Inno.Scene.SceneWorld.Find(System.Guid persistentId) where TObject : Inno.Core.Identity.IdentityObject`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L131) | Resolves a live scene object by its persistent identity within this world.
返回:The live compatible object, or null when this world does not own one.
`persistentId`:The persistent identity to resolve. | — | — | -| [`public void Inno.Scene.SceneWorld.FixedUpdate(float fixedDeltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L400) | Advances fixed-step lifecycle callbacks for every loaded scene.
`fixedDeltaTime`:The fixed simulation interval in seconds. | — | — | -| [`public int Inno.Scene.SceneWorld.GetSceneIndex(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L163) | Gets the hierarchy index of a loaded scene.
返回:The zero-based hierarchy index.
`scene`:The loaded scene to locate. | — | `System.InvalidOperationException`:Thrown when the scene is not loaded by this world. | -| [`public void Inno.Scene.SceneWorld.LateUpdate(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L428) | Advances late lifecycle callbacks for every loaded scene.
`deltaTime`:The elapsed frame time in seconds. | — | — | -| [`public Inno.Scene.GameScene Inno.Scene.SceneWorld.LoadNewScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L255) | Creates and loads a new active scene.
返回:The newly created and loaded scene.
`name`:The initial scene display name. | — | — | -| [`public Inno.Scene.GameScene Inno.Scene.SceneWorld.LoadNewSceneAdditive(string name = "Untitled Scene", bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L275) | Creates and additively loads a new scene.
返回:The newly created and loaded scene.
`name`:The initial scene display name.
`makeActive`:Whether the new scene becomes active. | — | — | -| [`public void Inno.Scene.SceneWorld.LoadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L208) | Replaces the loaded set with one scene and makes it active.
`scene`:The scene to load. | — | — | -| [`public void Inno.Scene.SceneWorld.LoadSceneAdditive(Inno.Scene.GameScene scene, bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L230) | Loads a scene alongside the existing scene set.
`scene`:The scene to load additively.
`makeActive`:Whether the loaded scene becomes active. | — | — | -| [`public void Inno.Scene.SceneWorld.MoveGameObjectToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L315) | Moves a live object subtree between two scenes loaded by this world.
`gameObject`:The live root object to move.
`destination`:The loaded destination scene. | — | `System.InvalidOperationException`:Thrown when either scene is not loaded or the object is not live. | -| [`public void Inno.Scene.SceneWorld.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L294) | Makes one loaded scene active without changing the loaded set.
`scene`:The loaded scene to activate. | — | `System.InvalidOperationException`:Thrown when the scene is not loaded by this world. | -| [`public void Inno.Scene.SceneWorld.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L185) | Moves a loaded scene to a hierarchy index without changing the active scene.
`scene`:The loaded scene to move.
`sceneIndex`:The requested zero-based hierarchy index. | — | `System.InvalidOperationException`:Thrown when the scene is not loaded by this world. | -| [`public void Inno.Scene.SceneWorld.UnloadActiveScene()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L334) | Unloads the active scene when one exists. | — | — | -| [`public void Inno.Scene.SceneWorld.UnloadAllScenes()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L372) | Unloads every scene while preserving the first lifecycle failure. | — | — | -| [`public bool Inno.Scene.SceneWorld.UnloadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L350) | Unloads one scene and selects another loaded scene when necessary.
返回:true when the scene was loaded by this world; otherwise, false.
`scene`:The scene to unload. | — | — | -| [`public void Inno.Scene.SceneWorld.Update(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L414) | Advances variable-step lifecycle callbacks for every loaded scene.
`deltaTime`:The elapsed frame time in seconds. | — | — | +| [`public sealed class Inno.Scene.EngineAssetContent`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L11) | Carries immutable scene or prefab runtime content across the runtime-to-authoring asset boundary. | — | — | +| [`public Inno.Scene.EngineAssetContent.EngineAssetContent(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L28) | Creates a detached engine asset content snapshot.
`payload`: The complete serialized runtime graph.
`dependencies`: The direct persistent asset dependencies of the graph. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public byte[] Inno.Scene.EngineAssetContent.GetPayload()`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L45) | Gets a detached copy of the serialized runtime graph. | A payload copy owned by the caller. | — | +| [`public Inno.Assets.AssetDependency[] Inno.Scene.EngineAssetContent.GetDependencies()`](../../src/content/scene/Inno.Scene/Assets/EngineAssetContent.cs#L53) | Gets a detached copy of direct persistent asset dependencies. | A dependency array owned by the caller. | — | +| [`public sealed class Inno.Scene.PrefabAsset : Inno.Assets.AssetObject`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L18) | Stores a persistent game object subtree that can be instantiated repeatedly. | — | — | +| [`public static Inno.Scene.PrefabAsset Inno.Scene.PrefabAsset.Capture(Inno.Scene.GameObject root, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L40) | Captures a game object subtree into a new unsaved prefab asset.
`root`: Prefab source root.
`serialization`: The serialization registry that owns the active prefab converter generation.
`assets`: The runtime asset generation used to resolve persistent references in the prefab graph. | A prefab asset ready to save. | — | +| [`public Inno.Scene.GameObject Inno.Scene.PrefabAsset.Instantiate(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Scene.Components.Transform? parent = null)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L77) | Instantiates this prefab into a scene using newly generated object identities.
`scene`: Target scene.
`serialization`: The serialization registry that owns the active prefab converter generation.
`assets`: The runtime asset generation used to resolve persistent references in the prefab graph.
`parent`: Optional target parent. | The instantiated root game object. | — | +| [`public static Inno.Scene.PrefabAsset Inno.Scene.PrefabAsset.CreateImported(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L112) | Creates an imported prefab asset from validated runtime content produced by the prefab asset pipeline.
`payload`: The complete serialized runtime object subtree.
`dependencies`: The direct persistent asset dependencies captured with the subtree. | A prefab asset ready for an asset import writer to commit. | — | +| [`public Inno.Scene.EngineAssetContent Inno.Scene.PrefabAsset.CaptureContent()`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L133) | Captures the immutable runtime payload and dependency descriptors required by the prefab asset pipeline. | A detached authoring-content snapshot owned by the caller. | — | +| [`public Inno.Scene.PrefabAsset.PrefabAsset()`](../../src/content/scene/Inno.Scene/Assets/PrefabAsset.cs#L18) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.SceneAsset : Inno.Assets.AssetObject`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L16) | Stores imported scene source that can create independent runtime scenes. | — | — | +| [`public static Inno.Scene.SceneAsset Inno.Scene.SceneAsset.Capture(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L38) | Captures a runtime scene into a new unsaved scene asset.
`scene`: Scene to capture.
`serialization`: The serialization registry that owns the active scene converter generation.
`assets`: The asset database generation used to resolve references while collecting dependencies. | A detached scene asset that an authoring asset database can save. | — | +| [`public void Inno.Scene.SceneAsset.CaptureFrom(Inno.Scene.GameScene scene, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L61) | Replaces the pending source content with a fresh capture while preserving this asset identity.
`scene`: Scene to capture.
`serialization`: The serialization registry that owns the active scene converter generation.
`assets`: The asset database generation used to resolve references while collecting dependencies. | — | — | +| [`public Inno.Scene.GameScene Inno.Scene.SceneAsset.Instantiate(Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L88) | Creates an unloaded runtime scene and acquires its hard asset dependencies.
`serialization`: The serialization registry that owns the active scene converter generation.
`assets`: The runtime asset generation used to resolve persistent references in the scene graph. | A newly allocated scene. | — | +| [`public static Inno.Scene.SceneAsset Inno.Scene.SceneAsset.CreateImported(System.ReadOnlySpan payload, System.Collections.Generic.IReadOnlyList dependencies)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L115) | Creates an imported scene asset from validated runtime content produced by the scene asset pipeline.
`payload`: The complete serialized runtime scene graph.
`dependencies`: The direct persistent asset dependencies captured with the scene graph. | A scene asset ready for an asset import writer to commit. | — | +| [`public Inno.Scene.EngineAssetContent Inno.Scene.SceneAsset.CaptureContent()`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L136) | Captures the immutable runtime payload and dependency descriptors required by the scene asset pipeline. | A detached authoring-content snapshot owned by the caller. | — | +| [`protected override void Inno.Scene.SceneAsset.OnRuntimePayloadChanged(System.ReadOnlyMemory previousPayload, System.ReadOnlyMemory currentPayload)`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L156) | Rebuilds runtime-derived state after the serialized asset payload changes.
`previousPayload`: The previous payload consumed by on runtime payload changed; ownership remains with the caller unless explicitly stated otherwise.
`currentPayload`: The current payload consumed by on runtime payload changed; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public Inno.Scene.SceneAsset.SceneAsset()`](../../src/content/scene/Inno.Scene/Assets/SceneAsset.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.AllowMultipleComponentAttribute : System.Attribute`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleComponentAttribute.cs#L9) | Allows multiple instances of a concrete component type on one game object. | — | — | +| [`public Inno.Scene.AllowMultipleComponentAttribute.AllowMultipleComponentAttribute()`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleComponentAttribute.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.AllowMultipleSystemAttribute : System.Attribute`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleSystemAttribute.cs#L9) | Allows multiple instances of a concrete type in one scene. | — | — | +| [`public Inno.Scene.AllowMultipleSystemAttribute.AllowMultipleSystemAttribute()`](../../src/content/scene/Inno.Scene/Attributes/AllowMultipleSystemAttribute.cs#L9) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.MissingGameComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L19) | Preserves the identity, order, and serialized state of a component whose managed type is unavailable. | — | — | +| [`public Inno.Extensibility.Types.TypeRef Inno.Scene.MissingGameComponent.missingType { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L45) | Gets the logical identity of the unavailable component type. | — | — | +| [`public string Inno.Scene.MissingGameComponent.missingTypeName { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameComponent.cs#L50) | Gets the last known managed type name for diagnostics and editor presentation. | — | — | +| [`public sealed class Inno.Scene.MissingGameSystem : Inno.Scene.GameSystem`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L19) | Preserves the identity, order, and serialized state of a scene system whose managed type is unavailable. | — | — | +| [`public Inno.Extensibility.Types.TypeRef Inno.Scene.MissingGameSystem.missingType { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L46) | Gets the logical identity of the unavailable system type. | — | — | +| [`public string Inno.Scene.MissingGameSystem.missingTypeName { get; }`](../../src/content/scene/Inno.Scene/Concrete/MissingGameSystem.cs#L51) | Gets the last known managed type name for diagnostics and editor presentation. | — | — | +| [`public sealed class Inno.Scene.Components.Transform : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L15) | Stores local transform data and exposes the scene hierarchy relationship. | — | — | +| [`public Inno.Scene.Components.Transform.Transform()`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L31) | Creates a transform with identity local values. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.localPosition { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L40) | Gets or sets the local position relative to the parent. | — | — | +| [`public Inno.Core.Mathematics.Quaternion Inno.Scene.Components.Transform.localRotation { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L54) | Gets or sets the local rotation relative to the parent. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.localScale { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L68) | Gets or sets the local scale relative to the parent. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.worldPosition { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L81) | Gets or sets the world-space position. | — | — | +| [`public Inno.Core.Mathematics.Quaternion Inno.Scene.Components.Transform.worldRotation { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L90) | Gets or sets the world-space rotation. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.worldScale { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L99) | Gets or sets the world-space scale. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Scene.Components.Transform.localToWorldMatrix { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L108) | Gets the exact local-to-world matrix, including the complete parent hierarchy. | — | — | +| [`public Inno.Core.Mathematics.Matrix Inno.Scene.Components.Transform.worldToLocalMatrix { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L116) | Gets the inverse of the exact local-to-world matrix. | — | `System.InvalidOperationException`: Thrown when a zero-scale hierarchy is not invertible. | +| [`public Inno.Scene.Components.Transform? Inno.Scene.Components.Transform.parent { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L128) | Gets the parent transform, or for a scene-level object. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.Components.Transform.children { get; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L133) | Gets child transforms in sibling order. | — | — | +| [`public int Inno.Scene.Components.Transform.siblingIndex { get; set; }`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L138) | Gets or sets this transform's index among its siblings. | — | — | +| [`public void Inno.Scene.Components.Transform.SetParent(Inno.Scene.Components.Transform? parent)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L153) | Sets the parent while preserving this transform's world-space values.
`parent`: New parent, or to move to scene level. | — | `System.InvalidOperationException`: Thrown for hierarchy cycles, cross-scene parents, or a non-invertible parent hierarchy. | +| [`public void Inno.Scene.Components.Transform.SetSiblingIndex(int siblingIndex)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L161) | Moves this transform within its current sibling collection.
`siblingIndex`: Requested zero-based sibling index. | — | — | +| [`public void Inno.Scene.Components.Transform.SetWorldTransform(Inno.Core.Mathematics.Vector3 position, Inno.Core.Mathematics.Quaternion rotation, Inno.Core.Mathematics.Vector3 scale)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L175) | Atomically applies world-space translation, rotation, and scale.
`position`: Requested world-space translation.
`rotation`: Requested world-space rotation.
`scale`: Requested world-space scale. | — | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.TransformPoint(Inno.Core.Mathematics.Vector3 point)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L190) | Transforms a local-space point through the complete parent hierarchy.
`point`: Point expressed in this transform's local space. | The corresponding world-space point. | — | +| [`public Inno.Core.Mathematics.Vector3 Inno.Scene.Components.Transform.InverseTransformPoint(Inno.Core.Mathematics.Vector3 point)`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L201) | Transforms a world-space point into this transform's local space.
`point`: Point expressed in world space. | The corresponding local-space point. | — | +| [`protected override void Inno.Scene.Components.Transform.Reset()`](../../src/content/scene/Inno.Scene/Concrete/Transform.cs#L206) | Restores this instance to its initial reusable state. | — | — | +| [`public static class Inno.Scene.SceneContentSource`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L11) | Projects loaded scenes into the shared content protocol without depending on a rendering or audio model. | — | — | +| [`public static Inno.References.ContentReadScope Inno.Scene.SceneContentSource.CreateScope(Inno.Scene.SceneWorld world)`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L22) | Captures the ordered roots and primary identity of one scene world.
`world`: The owner whose current scene set should be visible. | A caller-owned, operation-scoped identity view. | — | +| [`public static Inno.References.ContentReadScope Inno.Scene.SceneContentSource.CreateScope(System.Collections.Generic.IEnumerable scenes, Inno.Scene.GameScene? activeScene)`](../../src/content/scene/Inno.Scene/Content/SceneContentSource.cs#L40) | Captures an explicit coherent scene presentation selected by a host.
`scenes`: The ordered live scene roots.
`activeScene`: The optional primary root, which must be in the collection. | A caller-owned scope that retains no scene instances. | — | +| [`public abstract class Inno.Scene.EngineObject : Inno.Core.Identity.IdentityObject`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L11) | Provides identity and destruction state shared by all managed scene objects. | — | — | +| [`public bool Inno.Scene.EngineObject.isDestroyed { get; }`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L18) | Gets whether the engine has destroyed this object. | — | — | +| [`public static bool Inno.Scene.EngineObject.operator ==(Inno.Scene.EngineObject? left, Inno.Scene.EngineObject? right)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L32) | Compares engine objects using reference identity and destroyed-object null semantics.
`left`: Left operand.
`right`: Right operand. | when both operands refer to the same live object or both behave as null. | — | +| [`public static bool Inno.Scene.EngineObject.operator !=(Inno.Scene.EngineObject? left, Inno.Scene.EngineObject? right)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L57) | Compares engine objects using reference identity and destroyed-object null semantics.
`left`: Left operand.
`right`: Right operand. | when the operands do not represent the same object. | — | +| [`public override bool Inno.Scene.EngineObject.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L71) | Compares this instance to another object using reference identity.
`obj`: The object to compare with this instance. | when is this instance. | — | +| [`public override int Inno.Scene.EngineObject.GetHashCode()`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L79) | Returns a stable runtime reference hash code. | The reference hash code. | — | +| [`protected Inno.Scene.EngineObject.EngineObject()`](../../src/content/scene/Inno.Scene/Core/EngineObject.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Scene.GameBehavior : Inno.Scene.GameComponent, Inno.Scene.ISceneLifecycleObject`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L10) | Base component for independently enabled scene functionality with frame lifecycle callbacks. | — | — | +| [`public bool Inno.Scene.GameBehavior.enabled { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L22) | Gets or sets whether this component participates in scene lifecycle and frame updates. | — | — | +| [`public bool Inno.Scene.GameBehavior.isActiveAndEnabled { get; }`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L39) | Gets whether this component is enabled and active in its owning hierarchy. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.Awake()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L44) | Called once before this behavior first becomes active. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.Start()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L51) | Called once immediately before the first update of this behavior. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.OnEnable()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L58) | Called when this component becomes active and enabled. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.OnDisable()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L65) | Called when this component stops being active and enabled. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.Update()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L72) | Called during the variable-rate update stage. Use Time.deltaTime for frame timing. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.FixedUpdate()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L79) | Called during the fixed-rate update stage. Use Time.fixedDeltaTime for step timing. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.LateUpdate()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L86) | Called during the late update stage. Use Time.deltaTime for frame timing. | — | — | +| [`protected virtual void Inno.Scene.GameBehavior.OnDestroy()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L93) | Called immediately before this behavior is detached and destroyed. | — | — | +| [`protected Inno.Scene.GameBehavior.GameBehavior()`](../../src/content/scene/Inno.Scene/Core/GameBehavior.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public abstract class Inno.Scene.GameComponent : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L11) | Base type for data and behavior objects attached to a . | — | — | +| [`public Inno.Scene.GameObject Inno.Scene.GameComponent.gameObject { get; }`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L21) | Gets the owning game object. | — | `System.InvalidOperationException`: Thrown when the component is detached or destroyed. | +| [`public Inno.Scene.Components.Transform Inno.Scene.GameComponent.transform { get; }`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L32) | Gets the owning game object's transform. | — | `System.InvalidOperationException`: Thrown when the component is detached or destroyed. | +| [`protected virtual void Inno.Scene.GameComponent.Reset()`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L37) | Restores this component to its default state when it is added or explicitly reset. | — | — | +| [`protected Inno.Scene.GameComponent.GameComponent()`](../../src/content/scene/Inno.Scene/Core/GameComponent.cs#L11) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.GameObject : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L15) | Represents a scene-owned object and exposes its component-oriented API. | — | — | +| [`public const string Inno.Scene.GameObject.defaultTag = "Untagged"`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L20) | Defines the tag assigned to newly created game objects. | — | — | +| [`public bool Inno.Scene.GameObject.isRuntimeValid { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L43) | Gets whether this object is live in its owning scene. | — | — | +| [`public Inno.Scene.GameScene Inno.Scene.GameObject.scene { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L51) | Gets the owning scene. | — | `System.InvalidOperationException`: Thrown when this object is destroyed or detached. | +| [`public string Inno.Scene.GameObject.name { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L56) | Gets or sets the display name stored in the owning scene. | — | — | +| [`public string Inno.Scene.GameObject.tag { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L84) | Gets or sets the ordinal tag used to categorize and query this game object. | — | `System.ArgumentException`: Thrown when the assigned tag is empty or contains only white-space characters. | +| [`public Inno.Scene.Layers.GameLayer Inno.Scene.GameObject.layer { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L109) | Gets or sets the single runtime layer used to filter this game object. | — | — | +| [`public Inno.Scene.Components.Transform Inno.Scene.GameObject.transform { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L132) | Gets the mandatory transform component. | — | `System.InvalidOperationException`: Thrown when this object is destroyed or incompletely initialized. | +| [`public bool Inno.Scene.GameObject.activeSelf { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L144) | Gets whether this object is explicitly active. | — | — | +| [`public bool Inno.Scene.GameObject.activeInHierarchy { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L156) | Gets whether this object is active after parent hierarchy state is applied. | — | — | +| [`public bool Inno.Scene.GameObject.isPartOfPrefabInstance { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L161) | Gets whether this object retains a prefab source connection. | — | — | +| [`public Inno.Scene.GameObject? Inno.Scene.GameObject.prefabInstanceRoot { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L166) | Gets the root of this object's prefab instance connection. | — | — | +| [`public Inno.Scene.PrefabInstanceInfo? Inno.Scene.GameObject.prefabInstance { get; }`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L171) | Gets read-only information about this object's prefab source connection. | — | — | +| [`public void Inno.Scene.GameObject.SetActive(bool value)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L179) | Changes this object's explicit active state and updates its hierarchy subtree.
`value`: The concrete value read or transformed by this operation. | — | — | +| [`public TComponent Inno.Scene.GameObject.AddComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L190) | Creates and attaches a component of the requested type.
`TComponent`: Concrete component type. | The attached component. | — | +| [`public Inno.Scene.GameComponent Inno.Scene.GameObject.AddComponent(System.Type componentType)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L201) | Creates and attaches a component of the requested runtime type.
`componentType`: Concrete component type. | The attached component. | — | +| [`public TComponent Inno.Scene.GameObject.GetComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L216) | Gets the first attached component assignable to the requested type.
`TComponent`: Requested component type. | The first matching component. | `System.InvalidOperationException`: Thrown when no matching component exists. | +| [`public Inno.Scene.GameComponent Inno.Scene.GameObject.GetComponent(System.Type componentType)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L235) | Gets the first attached component assignable to a runtime type.
`componentType`: Requested component type. | The first matching component. | `System.InvalidOperationException`: Thrown when no matching component exists. | +| [`public bool Inno.Scene.GameObject.TryGetComponent(out TComponent? component) where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L255) | Tries to get the first attached component assignable to the requested type.
`component`: Matching component when found.
`TComponent`: Requested component type. | when a matching component exists. | — | +| [`public bool Inno.Scene.GameObject.HasComponent() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L275) | Gets whether a matching component is attached.
`TComponent`: Requested component type. | when a matching component exists. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameObject.GetComponents() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L286) | Gets all attached components assignable to the requested type in attachment order.
`TComponent`: Requested component type. | A stable component snapshot. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameObject.GetComponents()`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L295) | Gets all attached components in attachment order. | A stable component snapshot. | — | +| [`public int Inno.Scene.GameObject.GetComponentIndex(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L306) | Gets the attachment index of a component on this object.
`component`: Attached component to locate. | The zero-based attachment index. | — | +| [`public void Inno.Scene.GameObject.SetComponentIndex(Inno.Scene.GameComponent component, int componentIndex)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L322) | Moves an attached component to a requested attachment index. The mandatory Transform participates in ordering but remains non-removable.
`component`: Attached component to move.
`componentIndex`: Requested zero-based attachment index. | — | — | +| [`public void Inno.Scene.GameObject.ResetComponent(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L342) | Restores an attached component to the defaults defined by its optional Reset message.
`component`: GameComponent instance to reset. | — | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the object is invalid or the component is not attached to it. | +| [`public bool Inno.Scene.GameObject.RemoveComponent(Inno.Scene.GameComponent component)`](../../src/content/scene/Inno.Scene/Core/GameObject.cs#L360) | Removes and destroys a specific attached component.
`component`: GameComponent instance to remove. | when the component was attached and removed. | `System.InvalidOperationException`: Thrown when attempting to remove the mandatory transform. | +| [`public sealed class Inno.Scene.GameScene : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L17) | Owns a runtime scene, including its objects, components, hierarchy, and systems. | — | — | +| [`public Inno.Scene.GameScene.GameScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L36) | Creates an empty scene.
`name`: Initial scene name. | — | — | +| [`public string Inno.Scene.GameScene.name { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L57) | Gets or sets the scene display name. | — | — | +| [`public bool Inno.Scene.GameScene.isLoaded { get; }`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L70) | Gets whether this scene is currently owned by . | — | — | +| [`public ulong Inno.Scene.GameScene.hierarchyRevision { get; }`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L79) | Gets the owner-local change counter for object membership, parentage and sibling ordering. | — | — | +| [`public Inno.Scene.GameObject Inno.Scene.GameScene.CreateObject(string name = "GameObject")`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L93) | Creates a game object with a mandatory transform component.
`name`: Initial game object name. | The created game object. | — | +| [`public Inno.Scene.GameObject Inno.Scene.GameScene.InstantiatePrefab(Inno.Scene.PrefabAsset prefab, Inno.Scene.Components.Transform? parent = null)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L114) | Creates a connected instance of an imported prefab in this loaded scene.
`prefab`: The prefab asset that supplies the object subtree.
`parent`: Optional parent for the new instance. | The newly instantiated prefab root. | `System.ArgumentNullException`: The prefab is null.
`System.InvalidOperationException`: The scene is not loaded in its owning world or prefab services are unavailable. | +| [`public bool Inno.Scene.GameScene.DestroyObject(Inno.Scene.GameObject gameObject)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L130) | Recursively destroys a game object and its complete child subtree.
`gameObject`: Root object to destroy. | when the object belonged to this scene and was destroyed. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.GetObjects()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L185) | Gets all committed live game objects in scene storage order. | A stable object snapshot. | — | +| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObject(string name)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L200) | Finds the first game object with an ordinally matching name.
`name`: Name to find. | The first match, or when no object matches. | — | +| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObjectWithTag(string tag)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L219) | Finds the first game object with an ordinally matching tag in scene storage order.
`tag`: Tag to find. | The first match, or when no object matches. | `System.ArgumentException`: Thrown when is empty or contains only white-space characters. | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithTag(string tag)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L237) | Finds every game object with an ordinally matching tag in scene storage order.
`tag`: Tag to find. | A stable snapshot containing every matching game object. | `System.ArgumentException`: Thrown when is empty or contains only white-space characters. | +| [`public Inno.Scene.GameObject? Inno.Scene.GameScene.FindObjectWithLayer(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L252) | Finds the first game object assigned to a layer in scene storage order.
`layer`: The layer slot to find. | The first matching game object, or when no object matches. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithLayer(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L267) | Finds every game object assigned to one layer in scene storage order.
`layer`: The layer slot to find. | A stable snapshot containing every matching game object. | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.FindObjectsWithLayers(Inno.Scene.Layers.GameLayerMask layers)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L282) | Finds every game object assigned to any layer contained in a mask.
`layers`: The set of accepted layer slots. | A stable snapshot in scene storage order. | — | +| [`public TSystem Inno.Scene.GameScene.AddSystem() where TSystem : Inno.Scene.GameSystem, new()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L297) | Creates and registers a parameterless game system.
`TSystem`: Concrete system type. | The registered system. | — | +| [`public Inno.Scene.GameSystem Inno.Scene.GameScene.AddSystem(System.Type systemType)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L312) | Creates and registers a concrete game system by runtime type.
`systemType`: The system type consumed by add system; ownership remains with the caller unless explicitly stated otherwise. | The validated game system that represents the completed operation. | — | +| [`public void Inno.Scene.GameScene.AddSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L330) | Registers a game system instance.
`system`: System to register. | — | — | +| [`public bool Inno.Scene.GameScene.RemoveSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L345) | Removes a registered game system.
`system`: System to remove. | when the system was registered. | — | +| [`public void Inno.Scene.GameScene.ResetSystem(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L358) | Explicitly restores a registered system to its default state.
`system`: The system consumed by reset system; ownership remains with the caller unless explicitly stated otherwise. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameScene.GetSystems()`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L372) | Gets registered systems in display and serialization order. Explicit values independently control lifecycle execution priority. | An immutable snapshot of the values selected by the operation. | — | +| [`public int Inno.Scene.GameScene.GetSystemIndex(Inno.Scene.GameSystem system)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L387) | Gets the display index of a registered system.
`system`: Registered system to locate. | The zero-based display index. | — | +| [`public void Inno.Scene.GameScene.SetSystemIndex(Inno.Scene.GameSystem system, int systemIndex)`](../../src/content/scene/Inno.Scene/Core/GameScene.cs#L404) | Moves a registered system to a requested display and serialization index. This operation does not change its explicit .
`system`: Registered system to move.
`systemIndex`: Requested zero-based display index. | — | — | +| [`public abstract class Inno.Scene.GameSystem : Inno.Scene.EngineObject, Inno.Core.Serialization.ISerializable, Inno.Scene.ISceneLifecycleObject`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L13) | Base type for serializable, ordered scene-level behaviors. | — | — | +| [`public bool Inno.Scene.GameSystem.enabled { get; set; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L22) | Gets or sets whether this system participates in scene lifecycle updates. | — | — | +| [`public bool Inno.Scene.GameSystem.isActiveAndEnabled { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L39) | Gets whether this system is registered, enabled, and dispatchable. | — | — | +| [`public virtual int Inno.Scene.GameSystem.order { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L44) | Gets the ascending execution order used by the owning scene. | — | — | +| [`protected Inno.Scene.GameScene Inno.Scene.GameSystem.scene { get; }`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L49) | Gets the owning scene after this system has been registered. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.Reset()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L56) | Restores this system to defaults when added or explicitly reset. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.Awake()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L63) | Called once before the system first becomes active. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.Start()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L70) | Called once immediately before the first update. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnEnable()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L77) | Called when the system becomes active and enabled. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnDisable()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L84) | Called when the system stops being active and enabled. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnDestroy()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L91) | Called before a system that entered runtime lifecycle is destroyed. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnFixedUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L98) | Called during the fixed-rate scene stage. Use Time.fixedDeltaTime for step timing. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L105) | Called during the variable-rate scene stage. Use Time.deltaTime for frame timing. | — | — | +| [`protected virtual void Inno.Scene.GameSystem.OnLateUpdate()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L112) | Called during the late scene stage. Use Time.deltaTime for frame timing. | — | — | +| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.GetComponents() where TComponent : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L125) | Gets all scene components assignable to a requested type.
`TComponent`: The component base or concrete type included in the result. | An immutable snapshot of the values selected by the operation. | — | +| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.GetObjects()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L133) | Gets all committed live game objects in deterministic scene storage order. | The immutable structure snapshot retained by the scene until its object or component structure changes. | — | +| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L144) | Queries game objects containing one required component type.
`T1`: The first component type required on every returned game object. | An immutable snapshot of the values selected by the operation. | — | +| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent where T2 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L158) | Queries game objects containing two required component types.
`T1`: The first component type required on every returned game object.
`T2`: The second component type required on every returned game object. | An immutable snapshot of the values selected by the operation. | — | +| [`protected System.Collections.Generic.IReadOnlyList Inno.Scene.GameSystem.Query() where T1 : Inno.Scene.GameComponent where T2 : Inno.Scene.GameComponent where T3 : Inno.Scene.GameComponent`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L178) | Queries game objects containing three required component types.
`T1`: The first component type required on every returned game object.
`T2`: The second component type required on every returned game object.
`T3`: The third component type required on every returned game object. | An immutable snapshot of the values selected by the operation. | — | +| [`protected Inno.Scene.GameSystem.GameSystem()`](../../src/content/scene/Inno.Scene/Core/GameSystem.cs#L13) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scene.GameTagCatalog : Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L16) | Stores the project-wide tag definitions used to author and validate scene object assignments. | — | — | +| [`public const string Inno.Scene.GameTagCatalog.settingProtocolId = "inno.scene.tags"`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L21) | Gets the immutable project-setting protocol value used by discovery metadata. | — | — | +| [`public static Inno.Core.Settings.ProjectSettingId Inno.Scene.GameTagCatalog.settingId { get; }`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L29) | Gets the stable project setting protocol for the project-wide tag catalog. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.GameTagCatalog.GetTags()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L37) | Gets the defined tags with the immutable default tag first. | An independently owned deterministic tag snapshot. | — | +| [`public Inno.Core.Settings.ProjectLocalId Inno.Scene.GameTagCatalog.GetLocalId(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L52) | Gets the stable project-independent identity of a defined tag.
`tag`: The configured tag name. | The deterministic local identity. | — | +| [`public Inno.Core.Settings.ProjectScopedId Inno.Scene.GameTagCatalog.GetId(Inno.Core.Settings.ProjectId projectId, string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L72) | Gets the complete project-scoped identity of a defined tag.
`projectId`: The current project namespace.
`tag`: The configured tag name. | The canonical projectId.name identity. | — | +| [`public bool Inno.Scene.GameTagCatalog.IsDefined(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L89) | Determines whether an ordinal tag is defined by the project.
`tag`: The tag to resolve. | when the normalized tag is defined. | `System.ArgumentException`: Thrown when is empty or invalid. | +| [`public bool Inno.Scene.GameTagCatalog.Add(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L108) | Adds one normalized project tag definition.
`tag`: The unique tag to define. | when a definition was added. | `System.ArgumentException`: Thrown when is empty or invalid. | +| [`public bool Inno.Scene.GameTagCatalog.Remove(string tag)`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L134) | Removes one custom project tag definition without rewriting scene assignments.
`tag`: The custom tag to remove. | when an existing definition was removed. | `System.ArgumentException`: Thrown when is empty or invalid. | +| [`public Inno.Scene.GameTagCatalog Inno.Scene.GameTagCatalog.Clone()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L155) | Creates a detached mutable copy of this catalog. | An independently owned catalog with the same definitions. | — | +| [`public Inno.Scene.GameTagCatalog.GameTagCatalog()`](../../src/content/scene/Inno.Scene/Core/GameTagCatalog.cs#L16) | Inherited or compiler-provided contract. | — | — | +| [`public readonly struct Inno.Scene.Layers.GameLayer : System.IEquatable, System.IComparable`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L8) | Identifies one of the thirty-two runtime layers available to scene objects. | — | — | +| [`public const int Inno.Scene.Layers.GameLayer.C_MAX_COUNT = 32`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L13) | Defines the number of layer slots supported by the runtime bit-mask representation. | — | — | +| [`public static Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayer.defaultLayer { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L18) | Gets the built-in default layer stored in slot zero. | — | — | +| [`public Inno.Scene.Layers.GameLayer.GameLayer(int index)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L29) | Creates a layer identifier from a zero-based layer index.
`index`: The layer index in the inclusive range from zero through thirty-one. | — | `System.ArgumentOutOfRangeException`: Thrown when is outside the supported range. | +| [`public int Inno.Scene.Layers.GameLayer.index { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L39) | Gets the zero-based layer index. | — | — | +| [`public int Inno.Scene.Layers.GameLayer.CompareTo(Inno.Scene.Layers.GameLayer other)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L50) | Compares this layer with another layer by index.
`other`: The value to compare with this instance. | A signed value describing the relative index ordering. | — | +| [`public bool Inno.Scene.Layers.GameLayer.Equals(Inno.Scene.Layers.GameLayer other)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L61) | Determines whether another layer identifies the same index.
`other`: The layer to compare with this value. | when both layers identify the same index. | — | +| [`public override bool Inno.Scene.Layers.GameLayer.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L72) | Determines whether this instance and the supplied value represent the same logical state.
`obj`: The object to compare with this instance. | when both values represent the same logical state; otherwise, . | — | +| [`public override int Inno.Scene.Layers.GameLayer.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L80) | Computes a hash code from the fields that participate in logical equality. | A hash code consistent with the implemented equality contract. | — | +| [`public override string Inno.Scene.Layers.GameLayer.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L88) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static bool Inno.Scene.Layers.GameLayer.operator ==(Inno.Scene.Layers.GameLayer left, Inno.Scene.Layers.GameLayer right)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L102) | Determines whether two layer identifiers contain the same index.
`left`: The first layer identifier.
`right`: The second layer identifier. | when the identifiers are equal. | — | +| [`public static bool Inno.Scene.Layers.GameLayer.operator !=(Inno.Scene.Layers.GameLayer left, Inno.Scene.Layers.GameLayer right)`](../../src/content/scene/Inno.Scene/Layers/GameLayer.cs#L119) | Determines whether two layer identifiers contain different indices.
`left`: The first layer identifier.
`right`: The second layer identifier. | when the identifiers are different. | — | +| [`public sealed class Inno.Scene.Layers.GameLayerCatalog : Inno.Core.Serialization.ISerializable`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L18) | Stores the named layer catalog and symmetric layer-interaction matrix used by a project. | — | — | +| [`public const string Inno.Scene.Layers.GameLayerCatalog.settingProtocolId = "inno.scene.layers"`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L23) | Gets the immutable project-setting protocol value used by discovery metadata. | — | — | +| [`public static Inno.Core.Settings.ProjectSettingId Inno.Scene.Layers.GameLayerCatalog.settingId { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L39) | Gets the stable project setting protocol for the project-wide layer catalog. | — | — | +| [`public Inno.Scene.Layers.GameLayerCatalog.GameLayerCatalog()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L44) | Creates a layer stack containing the immutable default layer. | — | — | +| [`public int Inno.Scene.Layers.GameLayerCatalog.count { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L56) | Gets the number of currently named layer slots. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scene.Layers.GameLayerCatalog.GetDefinitions()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L71) | Gets an immutable snapshot of every named layer ordered by slot index. | The ordered layer-definition snapshot. | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.IsDefined(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L97) | Determines whether a layer slot is defined.
`layer`: The layer slot to test. | when the slot is defined. | — | +| [`public string? Inno.Scene.Layers.GameLayerCatalog.GetName(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L112) | Gets the display name assigned to a layer slot.
`layer`: The layer slot to resolve. | The configured name, or . | — | +| [`public Inno.Core.Settings.ProjectLocalId? Inno.Scene.Layers.GameLayerCatalog.GetLocalId(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L127) | Gets the stable project-independent identity assigned to a layer slot.
`layer`: The layer slot to resolve. | The local identity, or . | — | +| [`public Inno.Scene.Layers.GameLayerId? Inno.Scene.Layers.GameLayerCatalog.GetId(Inno.Core.Settings.ProjectId projectId, Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L146) | Gets the complete identity assigned to a layer slot.
`projectId`: The current project namespace.
`layer`: The layer slot to resolve. | The qualified identity, or . | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(Inno.Core.Settings.ProjectLocalId localId, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L166) | Tries to resolve a local identity to its compact runtime slot.
`localId`: The stable project-local identity.
`layer`: The resolved runtime slot. | when the identity is defined. | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(Inno.Core.Settings.ProjectId projectId, Inno.Scene.Layers.GameLayerId id, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L199) | Tries to resolve a qualified identity to its compact runtime slot.
`projectId`: The current project namespace.
`id`: The complete layer identity.
`layer`: The resolved runtime slot. | when the identity belongs to this project and is defined. | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.TryGetLayer(string name, out Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L233) | Tries to resolve an ordinal layer name to its compact slot.
`name`: The layer name to find.
`layer`: The resolved layer. | when a matching layer exists. | — | +| [`public Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayerCatalog.GetLayer(string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L259) | Resolves an ordinal layer name to its compact slot.
`name`: The configured layer name. | The matching runtime layer. | — | +| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerCatalog.GetMask(System.Collections.Generic.IEnumerable names)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L275) | Creates a mask from configured ordinal layer names.
`names`: The configured names to include. | A mask containing every resolved layer. | — | +| [`public void Inno.Scene.Layers.GameLayerCatalog.Define(Inno.Scene.Layers.GameLayer layer, string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L293) | Defines or renames a project layer without accepting an authored ID.
`layer`: The compact runtime slot.
`name`: The unique display name. | — | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.Remove(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L347) | Removes a custom layer definition while retaining its numeric slot and interactions.
`layer`: The custom layer slot to undefine. | when a definition was removed. | — | +| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerCatalog.GetInteractionMask(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L368) | Gets the interaction mask assigned to one layer.
`layer`: The source layer. | The configured interaction mask. | — | +| [`public bool Inno.Scene.Layers.GameLayerCatalog.CanInteract(Inno.Scene.Layers.GameLayer first, Inno.Scene.Layers.GameLayer second)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L386) | Determines whether two layer slots may interact.
`first`: The first layer.
`second`: The second layer. | when interaction is enabled. | — | +| [`public void Inno.Scene.Layers.GameLayerCatalog.SetInteraction(Inno.Scene.Layers.GameLayer first, Inno.Scene.Layers.GameLayer second, bool canInteract)`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L406) | Sets a symmetric interaction pair.
`first`: The first layer.
`second`: The second layer.
`canInteract`: Whether interaction is enabled. | — | — | +| [`public Inno.Scene.Layers.GameLayerCatalog Inno.Scene.Layers.GameLayerCatalog.Clone()`](../../src/content/scene/Inno.Scene/Layers/GameLayerCatalog.cs#L432) | Creates a detached copy of this stack. | An independent layer stack. | — | +| [`public sealed class Inno.Scene.Layers.GameLayerDefinition`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L10) | Describes one named layer slot in a snapshot. | — | — | +| [`public Inno.Scene.Layers.GameLayerDefinition.GameLayerDefinition(Inno.Core.Settings.ProjectLocalId localId, Inno.Scene.Layers.GameLayer layer, string name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L24) | Creates an immutable layer definition.
`localId`: The stable project-independent identity.
`layer`: The layer slot represented by the definition.
`name`: The non-empty display and lookup name. | — | — | +| [`public Inno.Core.Settings.ProjectLocalId Inno.Scene.Layers.GameLayerDefinition.localId { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L39) | Gets the stable project-independent identity. | — | — | +| [`public Inno.Scene.Layers.GameLayer Inno.Scene.Layers.GameLayerDefinition.layer { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L44) | Gets the layer slot represented by this definition. | — | — | +| [`public string Inno.Scene.Layers.GameLayerDefinition.name { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L49) | Gets the unique ordinal layer name. | — | — | +| [`public Inno.Scene.Layers.GameLayerId Inno.Scene.Layers.GameLayerDefinition.GetId(Inno.Core.Settings.ProjectId projectId)`](../../src/content/scene/Inno.Scene/Layers/GameLayerDefinition.cs#L60) | Resolves the complete identity under a project namespace.
`projectId`: The current project namespace. | The canonical project-scoped layer identity. | — | +| [`public readonly record struct Inno.Scene.Layers.GameLayerId : System.IEquatable`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Identifies one project-scoped game layer independently from its compact runtime slot. | — | — | +| [`public Inno.Scene.Layers.GameLayerId.GameLayerId(Inno.Core.Settings.ProjectScopedId value)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L18) | Creates a project-scoped layer identity.
`value`: The canonical qualified identity. | — | — | +| [`public Inno.Scene.Layers.GameLayerId.GameLayerId(Inno.Core.Settings.ProjectId projectId, Inno.Core.Settings.ProjectLocalId name)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L34) | Creates a project-scoped layer identity from its project and local parts.
`projectId`: The current project identity.
`name`: The stable project-local layer identity. | — | — | +| [`public string Inno.Scene.Layers.GameLayerId.value { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L63) | Gets the canonical projectId.name identity string. | — | — | +| [`public bool Inno.Scene.Layers.GameLayerId.isValid { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L68) | Gets whether this value contains a usable identity. | — | — | +| [`public override string Inno.Scene.Layers.GameLayerId.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L76) | Formats this value as a human-readable representation. | The qualified identity. | — | +| [`public static bool Inno.Scene.Layers.GameLayerId.operator !=(Inno.Scene.Layers.GameLayerId left, Inno.Scene.Layers.GameLayerId right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Scene.Layers.GameLayerId.operator ==(Inno.Scene.Layers.GameLayerId left, Inno.Scene.Layers.GameLayerId right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Scene.Layers.GameLayerId.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Scene.Layers.GameLayerId.Equals(object obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Scene.Layers.GameLayerId.Equals(Inno.Scene.Layers.GameLayerId other)`](../../src/content/scene/Inno.Scene/Layers/GameLayerId.cs#L10) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public readonly struct Inno.Scene.Layers.GameLayerMask : System.IEquatable`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L9) | Stores a compact set of scene layers for rendering, physics, and query filtering. | — | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.none { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L14) | Gets a mask containing no layers. | — | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.everything { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L19) | Gets a mask containing every supported layer. | — | — | +| [`public Inno.Scene.Layers.GameLayerMask.GameLayerMask(uint value)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L27) | Creates a mask from its raw thirty-two-bit representation.
`value`: The raw mask bits. | — | — | +| [`public uint Inno.Scene.Layers.GameLayerMask.value { get; }`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L35) | Gets the raw thirty-two-bit mask value. | — | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.FromLayers(System.Collections.Generic.IEnumerable layers)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L49) | Creates a mask containing the supplied layers.
`layers`: The layers whose bits should be enabled. | A mask containing every supplied layer. | `System.ArgumentNullException`: Thrown when is . | +| [`public bool Inno.Scene.Layers.GameLayerMask.Contains(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L67) | Determines whether the supplied layer is contained in this mask.
`layer`: The layer consumed by contains; ownership remains with the caller unless explicitly stated otherwise. | when the layer bit is enabled. | — | +| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.With(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L78) | Returns a mask with the supplied layer enabled.
`layer`: The layer consumed by with; ownership remains with the caller unless explicitly stated otherwise. | The updated immutable mask. | — | +| [`public Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.Without(Inno.Scene.Layers.GameLayer layer)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L89) | Returns a mask with the supplied layer disabled.
`layer`: The layer consumed by without; ownership remains with the caller unless explicitly stated otherwise. | The updated immutable mask. | — | +| [`public bool Inno.Scene.Layers.GameLayerMask.Equals(Inno.Scene.Layers.GameLayerMask other)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L100) | Determines whether another mask contains the same layer bits.
`other`: The mask to compare with this value. | when both masks contain the same bits. | — | +| [`public override bool Inno.Scene.Layers.GameLayerMask.Equals(object? obj)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L111) | Determines whether this instance and the supplied value represent the same logical state.
`obj`: The object to compare with this instance. | when both values represent the same logical state; otherwise, . | — | +| [`public override int Inno.Scene.Layers.GameLayerMask.GetHashCode()`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L119) | Computes a hash code from the fields that participate in logical equality. | A hash code consistent with the implemented equality contract. | — | +| [`public override string Inno.Scene.Layers.GameLayerMask.ToString()`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L127) | Formats this value as a human-readable representation. | The human-readable representation of this value. | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator \|(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L141) | Combines the enabled bits from two masks.
`left`: The first mask.
`right`: The second mask. | The bitwise union of both masks. | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator &(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L158) | Retains only layer bits enabled in both masks.
`left`: The first mask.
`right`: The second mask. | The bitwise intersection of both masks. | — | +| [`public static Inno.Scene.Layers.GameLayerMask Inno.Scene.Layers.GameLayerMask.operator ~(Inno.Scene.Layers.GameLayerMask mask)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L172) | Inverts every layer bit in a mask.
`mask`: The mask to invert. | The complement of the supplied mask. | — | +| [`public static bool Inno.Scene.Layers.GameLayerMask.operator ==(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L186) | Determines whether two masks contain the same bits.
`left`: The first mask.
`right`: The second mask. | when both masks are equal. | — | +| [`public static bool Inno.Scene.Layers.GameLayerMask.operator !=(Inno.Scene.Layers.GameLayerMask left, Inno.Scene.Layers.GameLayerMask right)`](../../src/content/scene/Inno.Scene/Layers/GameLayerMask.cs#L203) | Determines whether two masks contain different bits.
`left`: The first mask.
`right`: The second mask. | when both masks differ. | — | +| [`public sealed class Inno.Scene.PrefabInstanceInfo`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L10) | Describes the read-only source connection retained by one prefab instance object. | — | — | +| [`public System.Guid Inno.Scene.PrefabInstanceInfo.sourceAssetId { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L37) | Gets the persistent identity of the source prefab asset. | — | — | +| [`public System.Guid Inno.Scene.PrefabInstanceInfo.sourceObjectId { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L42) | Gets the source-local identity represented by this object. | — | — | +| [`public Inno.Scene.GameObject Inno.Scene.PrefabInstanceInfo.instanceRoot { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L47) | Gets the root object of this prefab instance connection. | — | — | +| [`public bool Inno.Scene.PrefabInstanceInfo.isRoot { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L52) | Gets whether this object is the instance connection root. | — | — | +| [`public bool Inno.Scene.PrefabInstanceInfo.isVariant { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L57) | Gets whether the connection originates from a prefab variant. | — | — | +| [`public bool Inno.Scene.PrefabInstanceInfo.isMissing { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L62) | Gets whether the source prefab was unavailable during restoration. | — | — | +| [`public int Inno.Scene.PrefabInstanceInfo.overrideCount { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L67) | Gets the number of retained overrides for this instance connection. | — | — | +| [`public int Inno.Scene.PrefabInstanceInfo.orphanedOverrideCount { get; }`](../../src/content/scene/Inno.Scene/Prefabs/PrefabInstanceInfo.cs#L72) | Gets the number of retained overrides that no longer match the current source. | — | — | +| [`public interface Inno.Scene.ISceneReloadStateTransfer`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L9) | Represents a staged scene object state transfer associated with one assembly reload transaction. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.retiredObjects { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L14) | Gets old scene objects whose runtime types belong to the retiring assembly generation. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.diagnostics { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L19) | Gets non-fatal state transfer decisions produced while replacing reloadable scene objects. | — | — | +| [`System.Collections.Generic.IReadOnlyList Inno.Scene.ISceneReloadStateTransfer.recoveryChanges { get; }`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L24) | Gets the shared candidate resolutions for actual scene element and asset dependency slots after application. | — | — | +| [`void Inno.Scene.ISceneReloadStateTransfer.PrepareForActivation()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L29) | Disables active retiring lifecycle objects before the new assembly generation becomes active. | — | — | +| [`void Inno.Scene.ISceneReloadStateTransfer.Apply()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L34) | Creates replacement instances and restores their serialized state. | — | — | +| [`void Inno.Scene.ISceneReloadStateTransfer.RollbackStructure()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L39) | Restores the previous scene structure after a failed state transfer. | — | — | +| [`void Inno.Scene.ISceneReloadStateTransfer.RestorePreviousState()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L44) | Restores lifecycle state on the previous scene instances after rollback. | — | — | +| [`void Inno.Scene.ISceneReloadStateTransfer.Complete()`](../../src/content/scene/Inno.Scene/Reloading/ISceneReloadStateTransfer.cs#L49) | Finalizes replacement instances after the assembly reload is committed. | — | — | +| [`public sealed class Inno.Scene.SceneReloadDiagnostic`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L9) | Describes a non-fatal issue encountered while migrating scene state to a new assembly generation. | — | — | +| [`public string Inno.Scene.SceneReloadDiagnostic.code { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L34) | Gets the stable diagnostic code. | — | — | +| [`public Inno.Core.Diagnostics.DiagnosticSeverity Inno.Scene.SceneReloadDiagnostic.severity { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L39) | Gets the diagnostic severity. | — | — | +| [`public string Inno.Scene.SceneReloadDiagnostic.message { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L44) | Gets the human-readable diagnostic message. | — | — | +| [`public System.Guid Inno.Scene.SceneReloadDiagnostic.scenePersistentId { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L49) | Gets the persistent identity of the affected scene. | — | — | +| [`public System.Guid Inno.Scene.SceneReloadDiagnostic.objectPersistentId { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L54) | Gets the persistent identity of the affected component or system. | — | — | +| [`public string Inno.Scene.SceneReloadDiagnostic.propertyName { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L59) | Gets the incompatible serialized property name. | — | — | +| [`public string Inno.Scene.SceneReloadDiagnostic.previousPropertyType { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L64) | Gets the previous declared property type name. | — | — | +| [`public string Inno.Scene.SceneReloadDiagnostic.currentPropertyType { get; }`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadDiagnostic.cs#L69) | Gets the current declared property type name. | — | — | +| [`public sealed class Inno.Scene.SceneReloadService`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L12) | Creates scene state-transfer transactions for assembly generation changes. | — | — | +| [`public Inno.Scene.SceneReloadService.SceneReloadService(Inno.Scene.SceneWorld world, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L34) | Creates a scene reload service bound to one serialization generation owner.
`serialization`: The serialization registry used to capture and restore scene state.
`world`: The isolated scene world whose active generation is captured.
`assets`: The asset reference generation used throughout capture, activation, and rollback. | — | `System.ArgumentNullException`: Thrown when , , or is null. | +| [`public Inno.Scene.ISceneReloadStateTransfer Inno.Scene.SceneReloadService.Capture(Inno.Extensibility.Types.TypeCacheReloadContext context)`](../../src/content/scene/Inno.Scene/Reloading/SceneReloadService.cs#L53) | Captures all loaded scene objects affected by a prepared type-cache reload.
`context`: The prepared type-cache reload context. | A staged state transfer that has not modified live objects. | — | +| [`public static class Inno.Scene.SceneManager`](../../src/content/scene/Inno.Scene/SceneManager.cs#L13) | Provides Unity-style scene operations by resolving the world in the current runtime execution context. | — | — | +| [`public static Inno.Scene.GameScene? Inno.Scene.SceneManager.activeScene { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L18) | Gets the active scene in the current runtime session. | — | — | +| [`public static bool Inno.Scene.SceneManager.hasActiveScene { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L23) | Gets whether the current runtime session has an active scene. | — | — | +| [`public static System.Collections.Generic.IReadOnlyList Inno.Scene.SceneManager.loadedScenes { get; }`](../../src/content/scene/Inno.Scene/SceneManager.cs#L28) | Gets an immutable snapshot of scenes loaded by the current runtime session. | — | — | +| [`public static int Inno.Scene.SceneManager.GetSceneIndex(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L39) | Gets the hierarchy index of a scene loaded by the current runtime session.
`scene`: The loaded scene to locate. | The zero-based hierarchy index. | — | +| [`public static void Inno.Scene.SceneManager.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L50) | Moves a loaded scene to a hierarchy index without changing the active scene.
`scene`: The loaded scene to move.
`sceneIndex`: The requested zero-based hierarchy index. | — | — | +| [`public static void Inno.Scene.SceneManager.LoadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L61) | Replaces the current session scene set with one active scene.
`scene`: The scene to load. | — | — | +| [`public static void Inno.Scene.SceneManager.LoadSceneAdditive(Inno.Scene.GameScene scene, bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L72) | Loads a scene alongside the current session scene set.
`scene`: The scene to load additively.
`makeActive`: Whether the loaded scene becomes active. | — | — | +| [`public static Inno.Scene.GameScene Inno.Scene.SceneManager.LoadNewScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/SceneManager.cs#L87) | Creates and loads a new active scene in the current runtime session.
`name`: The initial scene display name. | The newly created and loaded scene. | — | +| [`public static Inno.Scene.GameScene Inno.Scene.SceneManager.LoadNewSceneAdditive(string name = "Untitled Scene", bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L101) | Creates and additively loads a new scene in the current runtime session.
`name`: The initial scene display name.
`makeActive`: Whether the new scene becomes active. | The newly created and loaded scene. | — | +| [`public static void Inno.Scene.SceneManager.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L113) | Makes a loaded scene active in the current runtime session.
`scene`: The loaded scene to activate. | — | — | +| [`public static void Inno.Scene.SceneManager.MoveGameObjectToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L124) | Moves a live object subtree into another scene owned by the current runtime session.
`gameObject`: The live root object to move.
`destination`: The loaded destination scene. | — | — | +| [`public static void Inno.Scene.SceneManager.UnloadActiveScene()`](../../src/content/scene/Inno.Scene/SceneManager.cs#L133) | Unloads the active scene in the current runtime session when one exists. | — | — | +| [`public static bool Inno.Scene.SceneManager.UnloadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L144) | Unloads one scene from the current runtime session.
`scene`: The scene to unload. | when the scene was loaded; otherwise, . | — | +| [`public static void Inno.Scene.SceneManager.UnloadAllScenes()`](../../src/content/scene/Inno.Scene/SceneManager.cs#L149) | Unloads every scene from the current runtime session. | — | — | +| [`public static void Inno.Scene.SceneManager.FixedUpdate(float fixedDeltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L157) | Advances fixed-step scene lifecycle callbacks in the current runtime session.
`fixedDeltaTime`: The fixed simulation interval in seconds. | — | — | +| [`public static void Inno.Scene.SceneManager.Update(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L165) | Advances variable-step scene lifecycle callbacks in the current runtime session.
`deltaTime`: The elapsed frame time in seconds. | — | — | +| [`public static void Inno.Scene.SceneManager.LateUpdate(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneManager.cs#L173) | Advances late scene lifecycle callbacks in the current runtime session.
`deltaTime`: The elapsed frame time in seconds. | — | — | +| [`public sealed class Inno.Scene.SceneWorld : System.IDisposable`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L16) | Owns the loaded scene set and lifecycle state for one isolated runtime session. | — | — | +| [`public Inno.Scene.SceneWorld.SceneWorld(Inno.Core.Identity.IdentityAllocator identities, Inno.Extensibility.Types.TypeCatalog types)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L41) | Creates an empty scene world bound to one session identity allocator.
`identities`: The allocator that owns identities for scenes and their elements.
`types`: The host type catalog used to derive scene component and system generations. | — | `System.ArgumentNullException`: Thrown when is null. | +| [`public void Inno.Scene.SceneWorld.ConfigurePrefabInstantiation(Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L73) | Binds the session-owned serialization and asset generations used by scene prefab instances.
`serialization`: The active serialization registry for this session.
`assets`: The asset resolver that owns prefab dependencies. | — | `System.InvalidOperationException`: The binding was already configured. | | [`public Inno.Scene.GameScene? Inno.Scene.SceneWorld.activeScene { get; }`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L107) | Gets the scene currently selected for unqualified scene operations. | — | — | | [`public bool Inno.Scene.SceneWorld.hasActiveScene { get; }`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L112) | Gets whether this world contains an active scene. | — | — | | [`public System.Collections.Generic.IReadOnlyList Inno.Scene.SceneWorld.loadedScenes { get; }`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L117) | Gets an immutable snapshot of loaded scenes in hierarchy order. | — | — | -| [`public sealed class Inno.Scene.SceneWorld : System.IDisposable`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L16) | Owns the loaded scene set and lifecycle state for one isolated runtime session. | — | — | +| [`public TObject? Inno.Scene.SceneWorld.Find(System.Guid persistentId) where TObject : Inno.Core.Identity.IdentityObject`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L131) | Resolves a live scene object by its persistent identity within this world.
`persistentId`: The persistent identity to resolve.
`TObject`: The required scene object contract. | The live compatible object, or when this world does not own one. | — | +| [`public System.IDisposable Inno.Scene.SceneWorld.EnterScope()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L144) | Binds this world and its identity allocator to the current asynchronous execution context. | A strict last-in-first-out execution scope owned by the caller. | `System.ObjectDisposedException`: Thrown when this world has been disposed. | +| [`public int Inno.Scene.SceneWorld.GetSceneIndex(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L163) | Gets the hierarchy index of a loaded scene.
`scene`: The loaded scene to locate. | The zero-based hierarchy index. | `System.InvalidOperationException`: Thrown when the scene is not loaded by this world. | +| [`public void Inno.Scene.SceneWorld.SetSceneIndex(Inno.Scene.GameScene scene, int sceneIndex)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L185) | Moves a loaded scene to a hierarchy index without changing the active scene.
`scene`: The loaded scene to move.
`sceneIndex`: The requested zero-based hierarchy index. | — | `System.InvalidOperationException`: Thrown when the scene is not loaded by this world. | +| [`public void Inno.Scene.SceneWorld.LoadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L208) | Replaces the loaded set with one scene and makes it active.
`scene`: The scene to load. | — | — | +| [`public void Inno.Scene.SceneWorld.LoadSceneAdditive(Inno.Scene.GameScene scene, bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L230) | Loads a scene alongside the existing scene set.
`scene`: The scene to load additively.
`makeActive`: Whether the loaded scene becomes active. | — | — | +| [`public Inno.Scene.GameScene Inno.Scene.SceneWorld.LoadNewScene(string name = "Untitled Scene")`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L255) | Creates and loads a new active scene.
`name`: The initial scene display name. | The newly created and loaded scene. | — | +| [`public Inno.Scene.GameScene Inno.Scene.SceneWorld.LoadNewSceneAdditive(string name = "Untitled Scene", bool makeActive = true)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L275) | Creates and additively loads a new scene.
`name`: The initial scene display name.
`makeActive`: Whether the new scene becomes active. | The newly created and loaded scene. | — | +| [`public void Inno.Scene.SceneWorld.SetActiveScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L294) | Makes one loaded scene active without changing the loaded set.
`scene`: The loaded scene to activate. | — | `System.InvalidOperationException`: Thrown when the scene is not loaded by this world. | +| [`public void Inno.Scene.SceneWorld.MoveGameObjectToScene(Inno.Scene.GameObject gameObject, Inno.Scene.GameScene destination)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L315) | Moves a live object subtree between two scenes loaded by this world.
`gameObject`: The live root object to move.
`destination`: The loaded destination scene. | — | `System.InvalidOperationException`: Thrown when either scene is not loaded or the object is not live. | +| [`public void Inno.Scene.SceneWorld.UnloadActiveScene()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L334) | Unloads the active scene when one exists. | — | — | +| [`public bool Inno.Scene.SceneWorld.UnloadScene(Inno.Scene.GameScene scene)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L350) | Unloads one scene and selects another loaded scene when necessary.
`scene`: The scene to unload. | when the scene was loaded by this world; otherwise, . | — | +| [`public void Inno.Scene.SceneWorld.UnloadAllScenes()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L372) | Unloads every scene while preserving the first lifecycle failure. | — | — | +| [`public void Inno.Scene.SceneWorld.FixedUpdate(float fixedDeltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L400) | Advances fixed-step lifecycle callbacks for every loaded scene.
`fixedDeltaTime`: The fixed simulation interval in seconds. | — | — | +| [`public void Inno.Scene.SceneWorld.Update(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L414) | Advances variable-step lifecycle callbacks for every loaded scene.
`deltaTime`: The elapsed frame time in seconds. | — | — | +| [`public void Inno.Scene.SceneWorld.LateUpdate(float deltaTime)`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L428) | Advances late lifecycle callbacks for every loaded scene.
`deltaTime`: The elapsed frame time in seconds. | — | — | +| [`public void Inno.Scene.SceneWorld.Dispose()`](../../src/content/scene/Inno.Scene/SceneWorld.cs#L439) | Unloads every scene and permanently releases this world. | — | — | +| [`public static class Inno.Scene.SceneElementSerialization`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L16) | Recreates individual scene components and systems from stable type and object identities. | — | — | +| [`public static byte[] Inno.Scene.SceneElementSerialization.CaptureState(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L39) | Captures one element's logical type, neutral properties, asset dependencies and scene-reference aliases.
`target`: The live component or system, including a missing placeholder.
`serialization`: The owner's current serializer generation.
`assets`: The owner's complete asset resolver used during capture. | Neutral element bytes suitable for History and restoration without retaining the current managed type. | `System.ArgumentException`: The target is not a component or system.
`System.InvalidOperationException`: The element is not live in a loaded scene. | +| [`public static void Inno.Scene.SceneElementSerialization.RestoreState(Inno.Scene.EngineObject target, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L101) | Restores a captured element state into the same logical type, including neutral state on a missing placeholder.
`target`: The attached element receiving the state.
`stateData`: Bytes produced by .
`serialization`: The owner's current serializer generation.
`assets`: The owner's complete asset resolver. | — | `System.IO.InvalidDataException`: The payload is invalid or represents a different logical type.
`System.InvalidOperationException`: Property or reference restoration is incomplete. | +| [`public static Inno.Scene.GameComponent Inno.Scene.SceneElementSerialization.RestoreComponent(Inno.Scene.GameObject owner, Inno.Extensibility.Types.TypeRef type, System.Guid persistentId, int componentIndex, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L166) | Recreates one component without invoking Reset and restores its persistent properties.
`owner`: The live GameObject that will own the component.
`type`: The reload-safe identity of the current component implementation.
`persistentId`: The component instance identity to preserve.
`componentIndex`: The requested attachment index.
`stateData`: Complete neutral element bytes produced by CaptureState, including missing-state metadata.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The resolver used to materialize canonical asset references in restored properties. | The recreated component, or a preserved missing placeholder when its logical type is unavailable. | `System.ArgumentNullException`: Thrown when is .
`System.IO.InvalidDataException`: Thrown when the payload is invalid or the stable type resolves to an incompatible component.
`System.InvalidOperationException`: Thrown when the identity or component multiplicity conflicts, property restoration is incomplete, or a failed restoration cannot remove its partially created component. | +| [`public static Inno.Scene.GameSystem Inno.Scene.SceneElementSerialization.RestoreSystem(Inno.Scene.GameScene scene, Inno.Extensibility.Types.TypeRef type, System.Guid persistentId, int systemIndex, System.ReadOnlySpan stateData, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneElementSerialization.cs#L239) | Recreates one scene system without invoking Reset and restores its persistent properties.
`scene`: The live loaded scene that will own the system.
`type`: The reload-safe identity of the current system implementation.
`persistentId`: The system instance identity to preserve.
`systemIndex`: The requested display and serialization index.
`stateData`: Complete neutral element bytes produced by CaptureState, including missing-state metadata.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The resolver used to materialize canonical asset references in restored properties. | The recreated system, or a preserved missing placeholder when its logical type is unavailable. | `System.ArgumentNullException`: Thrown when is .
`System.IO.InvalidDataException`: Thrown when the payload is invalid or the stable type resolves to an incompatible system.
`System.InvalidOperationException`: Thrown when the identity or system multiplicity conflicts, property restoration is incomplete, or a failed restoration cannot remove its partially created system. | +| [`public static class Inno.Scene.ScenePropertySerialization`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L14) | Captures and restores individual scene-object properties while preserving scene reference identities. | — | — | +| [`public static byte[] Inno.Scene.ScenePropertySerialization.CaptureProperty(Inno.Scene.EngineObject target, string propertyName, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L43) | Captures one persistent property without serializing the complete scene.
`target`: The live scene object containing the property.
`propertyName`: The exact serialized member key.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The resolver used to preserve canonical asset references in captured values. | Neutral bytes that can be restored into the same logical object identity. | `System.ArgumentNullException`: Thrown when is .
`System.ArgumentException`: Thrown when is empty or unknown.
`System.InvalidOperationException`: Thrown when the target is destroyed or not owned by a loaded scene. | +| [`public static byte[] Inno.Scene.ScenePropertySerialization.CaptureProperties(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L82) | Captures all persistent properties without serializing the complete scene.
`target`: The live scene object whose state should be captured.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The resolver used to preserve canonical asset references in captured values. | Neutral bytes containing independently encoded properties. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the target is destroyed or not owned by a loaded scene. | +| [`public static System.Collections.Generic.IReadOnlyList Inno.Scene.ScenePropertySerialization.CapturePropertySnapshots(Inno.Scene.EngineObject target, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L113) | Captures each persistent property independently while preserving scene-reference identities.
`target`: Live scene object whose properties should be captured.
`serialization`: Registry owning the active converter generation.
`assets`: Resolver used to preserve canonical asset references. | Stable ordered property snapshots suitable for compact delta comparison. | — | +| [`public static Inno.Core.Serialization.SerializationPropertyRestoreResult Inno.Scene.ScenePropertySerialization.RestoreProperties(Inno.Scene.EngineObject target, System.ReadOnlySpan data, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Core.Serialization.SerializationPropertyRestoreMode mode = Inno.Core.Serialization.SerializationPropertyRestoreMode.Strict)`](../../src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs#L156) | Restores independently captured properties into a live scene object.
`target`: The current object generation receiving the values.
`data`: Bytes produced by or .
`mode`: The compatibility policy applied to matching current properties.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The resolver used to materialize canonical asset references in restored values. | A summary of restored, ignored, and incompatible properties. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when strict restoration fails or the target is not in a loaded scene. | +| [`public static class Inno.Scene.SceneSubtreeSerialization`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L14) | Captures and restores one GameObject subtree with its persistent object and component identities. | — | — | +| [`public static byte[] Inno.Scene.SceneSubtreeSerialization.Capture(Inno.Scene.GameObject root, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets)`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L37) | Captures one live GameObject and all descendants without serializing unrelated scene objects.
`root`: The live subtree root.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The asset-reference resolver used to encode referenced asset identities. | Neutral bytes containing the subtree structure, components, properties, and identity references. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the root is destroyed or detached. | +| [`public static Inno.Scene.GameObject Inno.Scene.SceneSubtreeSerialization.Restore(Inno.Scene.GameScene scene, System.ReadOnlySpan data, Inno.Core.Serialization.SerializationRegistry serialization, Inno.Assets.IAssetReferenceResolver assets, Inno.Scene.Components.Transform? parent, int siblingIndex)`](../../src/content/scene/Inno.Scene/Serialization/SceneSubtreeSerialization.cs#L81) | Restores a previously captured subtree into a loaded scene with its original persistent identities.
`scene`: The loaded destination scene.
`data`: Bytes created by .
`parent`: The optional external parent restored after the subtree is created.
`siblingIndex`: The requested sibling index under or at the scene root.
`serialization`: The serialization registry that owns the active converter generation.
`assets`: The asset-reference resolver used to restore referenced asset identities. | The restored subtree root. | `System.ArgumentNullException`: Thrown when is .
`System.InvalidOperationException`: Thrown when the scene is unavailable or an identity conflicts. | +| [`public sealed class Inno.Scene.SceneTypeResolutionException : System.Exception`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L9) | Reports that a serialized scene element cannot be created because its stable type is not present in the active type catalog. | — | — | +| [`public Inno.Scene.SceneTypeResolutionException.SceneTypeResolutionException(System.Guid stableTypeId, string elementKind)`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L23) | Creates an exception for one unresolved serialized scene type.
`stableTypeId`: The stable identity stored by the serialized scene element.
`elementKind`: The human-readable scene element kind, such as component or system. | — | `System.ArgumentException`: Thrown when is empty or is empty. | +| [`public System.Guid Inno.Scene.SceneTypeResolutionException.stableTypeId { get; }`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L39) | Gets the unresolved stable type identity. | — | — | +| [`public string Inno.Scene.SceneTypeResolutionException.elementKind { get; }`](../../src/content/scene/Inno.Scene/Serialization/SceneTypeResolutionException.cs#L44) | Gets the scene element kind that expected the missing type. | — | — | ## 项目依赖 -- [Inno.Extensibility.Reload](../extensibility/Inno.Extensibility.Reload.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Execution](../core/Inno.Core.Execution.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Collections](../core/Inno.Core.Collections.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Scripting.Api](../scripting/Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Identity](../core/Inno.Core.Identity.md):公开引用边界由实际签名核对。 -- [Inno.Core.Mathematics](../core/Inno.Core.Mathematics.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md):公开引用边界由实际签名核对。 -- [Inno.Core.Serialization](../core/Inno.Core.Serialization.md):公开引用边界由实际签名核对。 -- [Inno.Core.Settings](../core/Inno.Core.Settings.md):公开引用边界由实际签名核对。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.References](../references/Inno.References.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Extensibility.Reload` | 实现依赖;不传递 compile surface | +| `Inno.Core.Execution` | 实现依赖;不传递 compile surface | +| `Inno.Core.Collections` | 实现依赖;不传递 compile surface | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Core.Identity` | 公开签名依赖 | +| `Inno.Core.Mathematics` | 公开签名依赖 | +| `Inno.Extensibility.Types` | 公开签名依赖 | +| `Inno.Core.Serialization` | 公开签名依赖 | +| `Inno.Core.Settings` | 公开签名依赖 | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.References` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | diff --git a/docs/scripting/Inno.Scripting.Compiler.md b/docs/scripting/Inno.Scripting.Compiler.md index a071f7fac..3f8913286 100644 --- a/docs/scripting/Inno.Scripting.Compiler.md +++ b/docs/scripting/Inno.Scripting.Compiler.md @@ -1,5 +1,14 @@ # Inno.Scripting.Compiler +## SDK 输入与 IDE 一致性 + +`ScriptCompilerOptions.compilationEnvironment` 是 Host 必须提供的 `IScriptCompilationEnvironmentProvider`。一次编译或投影只捕获一次 `ScriptCompilationInputs`,其中保存选定 SDK 身份、实际 framework references 与 SDK 生成的预定义符号。集合复制后只读;编译器在进入 Roslyn 前读取并冻结引用内容,内容哈希参与产物身份。 + +编译器不选择已安装 reference pack 的最高版本,也不引用 Build、MSBuild 或平台工程。标准 Host 使用 Build 的 `SdkScriptCompilationEnvironment`,复用现有 SDK resolver、进程执行及 Core IO 临时 owner。求值失败或取消会明确失败,不发布脚本产物,不改写用户 SDK 配置。 + +IDE 投影显式引用同一组 framework assemblies,并使用同一组符号。SDK 的隐式 framework references 与隐式符号注入关闭;目标仍为正常的 `net10.0` SDK 工程。实际 IDE SDK 与捕获身份不一致时报告错误,要求在预期 SDK 选择下重新生成投影。 + + ## Authoring 标注与 Player 边界 编译器根据 `ScriptingApiScope.Authoring` 的导出清单,在语义绑定后、目标 Player references 绑定前移除纯创作标注使用、自定义派生标注类及相关 using。规则由清单与继承符号推导,不依赖标注的具体实现,也没有属性类型白名单。创作编译与 IDE 保留完整标注 API;Player IL 不保留纯创作标注引用。普通 `SerializableProperty` 不在此 scope,继续表达持久化契约。 @@ -16,8 +25,12 @@ Compiler 拥有 Roslyn、裁剪 reference assemblies、logical namespace analyze 内部实现:每次编译或 IDE 投影固定本次 framework metadata、编译器身份和语言选项。公开契约指纹描述 API 形状;产物指纹同时包含实际 framework reference 内容、Roslyn 与 emitter 身份及选项。相同契约在编译输入变化时仍选择独立产物,失败或取消不能发布不完整 reference。该缓存身份不是公开持久化协议。 +程序集缓存和完整 generation 先在所属 Library 的私有 staging 中准备,再复用 Core IO 的 `AtomicDirectory.Publish` 发布到不存在的目标,或用 `AtomicDirectory.Install` 替换经校验无效的已有缓存。有效完整缓存直接复用;失效目标不能先用 best-effort 递归删除来替代事务。编译取消 token 贯穿发布,提交前失败或取消保留/恢复已有目录;临时 staging 清理与已发布产物替换是不同责任。Compiler 不添加平台分支、独立 retry 或另一套文件所有权管理器。 + +文件系统阻塞超过 Core 的既定有界等待后明确失败。安装后旧目录清理失败时,新缓存已完整提交,但编译操作仍报告 IO 失败且不激活 generation;调用方必须区分完整缓存与已激活运行代际。安装和恢复同时失败保留两次异常及旧树位置。下一次显式请求可以重新校验并复用完整产物,不能把失败转成静默成功。 + 用户源码先对与 IDE 相同的裁剪契约完成编译检查。只有通过后,生成注册代码的 emission 阶段才读取所属运行时程序集的完整元数据;这些实现引用不扩展用户 API,也不加入 IDE project。该顺序允许生成序列化访问和模块目录,同时继续拒绝标有 `ScriptingApiIgnore` 的成员。发行代码绑定所选 Support Pack,Editor 代码绑定当前 authoring Host。 -框架参考程序集优先从宿主输出目录的 `packs/Microsoft.NETCore.App.Ref` 读取;源码开发环境继续从 `DOTNET_ROOT` 或当前 .NET 安装目录读取。Editor Publish 会随发行目录复制与目标框架匹配的 Ref Pack,因此脱离引擎源码和本机 SDK 安装后仍可编译脚本、生成 IDE 工程。 +框架参考程序集由本次 `IScriptCompilationEnvironmentProvider` 捕获的冻结输入明确提供;Compiler 不从安装根或候选 pack 中自行选择。标准 SDK Host 的求值、发布 Host 的输入准备和编译器的消费分别属于各自 owner,缺失输入必须在编译前明确失败。 生成实现侧 reference assembly 时以 Ref Pack 的程序集身份排除框架实现,不能按 DLL 所在目录判断:自包含 Editor 将引擎和 .NET DLL 放在同一目录,按目录过滤会在首次生成脚本 API 缓存时误删引擎依赖。 ## 公开 API @@ -87,41 +100,7 @@ static bool AdvanceSampleImport( ## 当前源码公开 API 清单 -按类型列出当前 public 与可供外部派生实现使用的 protected 声明。签名包含 nullable、默认参数和泛型约束;源码链接可定位实现。表中的“—”表示该声明没有额外 XML 约束,不能据此推断线程安全或不会失败。生命周期与组合顺序见本页前文。 - -### `Inno.Scripting.Compiler.CSharpSampleSourceRewriter` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.CSharpSampleSourceRewriter.CSharpSampleSourceRewriter()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L17) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public void Inno.Scripting.Compiler.CSharpSampleSourceRewriter.Transform(Inno.Assets.Pipeline.AssetSampleTransformContext context)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L25) | Rewrites explicit stable type identities in cloned C# scripts and registers their asset mappings.
`context`:The private sample import stage and its identity map. | — | — | -| [`public sealed class Inno.Scripting.Compiler.CSharpSampleSourceRewriter : Inno.Assets.Pipeline.IAssetSampleSourceRewriter`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L17) | Gives imported C# sample scripts distinct type identities before their scene assets are remapped. | — | — | - -### `Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer` - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.compilationWideUsingDiagnosticId = "INNO2003"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L32) | Diagnostic identifier for a forbidden compilation-wide namespace import. | — | — | -| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.directImplementationNamespaceDiagnosticId = "INNO2001"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L22) | Diagnostic identifier for direct implementation namespace access. | — | — | -| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.missingLogicalNamespaceDiagnosticId = "INNO2002"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L27) | Diagnostic identifier for a missing logical namespace import. | — | — | -| [`public Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.LogicalScriptingApiAnalyzer()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L17) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public override void Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext context)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L73) | Initializes owned resources and establishes the instance's active lifetime.
`context`:The context that supplies state and services for this operation. | — | — | -| [`public override System.Collections.Immutable.ImmutableArray Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.SupportedDiagnostics { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L61) | Gets the complete compiler diagnostic set enforced by this analyzer. | — | — | -| [`public sealed class Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L17) | Enforces logical scripting namespaces and rejects direct implementation namespace access. | — | — | - -### `Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset` +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 @@ -132,61 +111,24 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.ScriptAssemblyDefinitionAsset()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L54) | Creates an empty definition asset for deserialization. | — | — | -| [`public Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.ScriptAssemblyDefinitionAsset(string assemblyName, Inno.Scripting.Compiler.ScriptAssemblyScope scope, string[]? references = null, string[]? defines = null, bool nullable = true, bool allowUnsafe = false)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L82) | Creates a configured script assembly definition for native asset export.
`assemblyName`:Stable managed assembly name.
`scope`:Runtime or Editor API scope.
`references`:Referenced script assembly names.
`defines`:Preprocessor symbols.
`nullable`:Whether nullable reference analysis is enabled.
`allowUnsafe`:Whether unsafe source is permitted. | — | `System.ArgumentException`:Thrown when assemblyName is empty. | -| [`public bool Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.allowUnsafe { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L49) | Gets whether unsafe source is permitted. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.assemblyName { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L19) | Gets the stable assembly name. | — | — | -| [`public string[] Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.defines { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L37) | Gets preprocessor symbols applied to this assembly. | — | — | -| [`public bool Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.nullable { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L43) | Gets whether nullable reference analysis is enabled. | — | — | -| [`public string[] Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.references { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L31) | Gets referenced script assembly names. | — | — | -| [`public Inno.Scripting.Compiler.ScriptAssemblyScope Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.scope { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L25) | Gets the assembly API scope. | — | — | -| [`public sealed class Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset : Inno.Assets.AssetObject`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L13) | Defines one project script assembly and its compilation policy. | — | — | - -### `Inno.Scripting.Compiler.ScriptAssemblyScope` - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`Inno.Scripting.Compiler.ScriptAssemblyScope.Editor = 1`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L16) | The assembly can use runtime and editor scripting APIs. | — | — | -| [`Inno.Scripting.Compiler.ScriptAssemblyScope.Runtime = 0`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L11) | The assembly can use runtime scripting APIs only. | — | — | -| [`public enum Inno.Scripting.Compiler.ScriptAssemblyScope : int`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L6) | Identifies whether a project script assembly can use editor-only APIs. | — | — | - -### `Inno.Scripting.Compiler.ScriptCompilationProgress` - - - - - - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptCompilationProgress.ScriptCompilationProgress(float fraction, string stage)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Describes monotonic progress through one script compilation.
`fraction`:The completed fraction in the inclusive range from zero to one.
`stage`:The current human-readable compiler stage. | — | — | -| [`public void Inno.Scripting.Compiler.ScriptCompilationProgress.Deconstruct(out float fraction, out string stage)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Scripting.Compiler.ScriptCompilationProgress.Equals(Inno.Scripting.Compiler.ScriptCompilationProgress other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Scripting.Compiler.ScriptCompilationProgress.Equals(object obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Scripting.Compiler.ScriptCompilationProgress.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptCompilationProgress.operator ==(Inno.Scripting.Compiler.ScriptCompilationProgress left, Inno.Scripting.Compiler.ScriptCompilationProgress right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptCompilationProgress.operator !=(Inno.Scripting.Compiler.ScriptCompilationProgress left, Inno.Scripting.Compiler.ScriptCompilationProgress right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Scripting.Compiler.ScriptCompilationProgress.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public float Inno.Scripting.Compiler.ScriptCompilationProgress.fraction { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L13) | The completed fraction in the inclusive range from zero to one. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptCompilationProgress.stage { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L14) | The current human-readable compiler stage. | — | — | -| [`public readonly record struct Inno.Scripting.Compiler.ScriptCompilationProgress : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Describes monotonic progress through one script compilation.
`fraction`:The completed fraction in the inclusive range from zero to one.
`stage`:The current human-readable compiler stage. | — | — | - -### `Inno.Scripting.Compiler.ScriptCompilationResult` - + + + + + + + + + + + + + + + @@ -197,22 +139,6 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public static Inno.Scripting.Compiler.ScriptCompilationResult Inno.Scripting.Compiler.ScriptCompilationResult.Failure(Inno.Scripting.Compiler.ScriptDiagnostic diagnostic)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L93) | Creates a failed result for an exception intercepted at an orchestration boundary.
返回:A failed immutable result with no output directory or module deployments.
`diagnostic`:The diagnostic describing why no candidate generation was produced. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.compiledAssemblyNames { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L77) | Gets assembly names compiled during this request instead of reused from the artifact cache. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.diagnostics { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L52) | Gets all diagnostics produced by the compilation. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.moduleDeployments { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L72) | Gets the immutable module artifacts and dependency topology produced by this compilation. | — | — | -| [`public string? Inno.Scripting.Compiler.ScriptCompilationResult.outputDirectory { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L57) | Gets the generation output directory when one was created. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.reusedAssemblyNames { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L82) | Gets assembly names reused from the deterministic artifact cache. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.runtimeAssemblyPaths { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L67) | Gets exact runtime-scope managed assemblies suitable for a deployed Player. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.stageTimings { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L62) | Gets completed compiler stage timings in execution order. | — | — | -| [`public bool Inno.Scripting.Compiler.ScriptCompilationResult.success { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L47) | Gets whether every discovered script assembly compiled or reused successfully. | — | — | -| [`public sealed class Inno.Scripting.Compiler.ScriptCompilationResult`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L13) | Reports the outputs and diagnostics of one authoring-generation or runtime-deployment script compilation. | — | — | - -### `Inno.Scripting.Compiler.ScriptCompilationStageTiming` - @@ -223,52 +149,6 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptCompilationStageTiming.ScriptCompilationStageTiming(string stage, System.TimeSpan elapsed)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L25) | Creates one immutable stage timing sample.
`stage`:The human-readable stage description.
`elapsed`:The wall time spent in the stage. | — | `System.ArgumentException`:Thrown when stage is empty or contains only whitespace.
`System.ArgumentOutOfRangeException`:Thrown when elapsed is negative. | -| [`public bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.Equals(Inno.Scripting.Compiler.ScriptCompilationStageTiming other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.Equals(object obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Indicates whether this instance and a specified object are equal.
返回:true if obj and this instance are the same type and represent the same value; otherwise, false.
`obj`:The object to compare with the current instance. | — | — | -| [`public override int Inno.Scripting.Compiler.ScriptCompilationStageTiming.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Returns the hash code for this instance.
返回:A 32-bit signed integer that is the hash code for this instance. | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.operator ==(Inno.Scripting.Compiler.ScriptCompilationStageTiming left, Inno.Scripting.Compiler.ScriptCompilationStageTiming right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.operator !=(Inno.Scripting.Compiler.ScriptCompilationStageTiming left, Inno.Scripting.Compiler.ScriptCompilationStageTiming right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Scripting.Compiler.ScriptCompilationStageTiming.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Returns the fully qualified type name of this instance.
返回:The fully qualified type name. | — | — | -| [`public System.TimeSpan Inno.Scripting.Compiler.ScriptCompilationStageTiming.elapsed { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L45) | Gets the wall time spent in the stage. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptCompilationStageTiming.stage { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L40) | Gets the human-readable stage description. | — | — | -| [`public readonly record struct Inno.Scripting.Compiler.ScriptCompilationStageTiming : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Describes the elapsed wall time of one completed script compilation stage. | — | — | - -### `Inno.Scripting.Compiler.ScriptCompiler` - - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptCompiler.ScriptCompiler(Inno.Scripting.Compiler.ScriptCompilerOptions options, Inno.Assets.Pipeline.AssetPipeline assets, Inno.Plugins.Authoring.PluginEnvironment plugins)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L40) | Creates a compiler over explicit authoring services owned by one host.
`options`:The project and derived-cache locations used by compilation.
`assets`:The authoring asset pipeline supplying committed source artifacts.
`plugins`:The Plugin environment supplying the candidate Plugin source generation. | — | `System.ArgumentNullException`:Thrown when an owner argument is null.
`System.ArgumentException`:Thrown when the project root is empty. | -| [`public int Inno.Scripting.Compiler.ScriptCompiler.CollectArtifacts(System.Collections.Generic.IEnumerable retainedDirectories)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L173) | Removes unreferenced compiler generations while retaining the supplied active directories.
返回:The number of obsolete generation directories removed from the compiler cache.
`retainedDirectories`:The generation directories that remain reachable from active runtime sessions. | — | — | -| [`public System.Threading.Tasks.ValueTask Inno.Scripting.Compiler.ScriptCompiler.CompileAuthoringGenerationAsync(System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.IAssetSourceSnapshot? sourceSnapshot = null)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L96) | Compiles a complete runtime and editor candidate generation without activating it.
返回:The immutable diagnostics and artifacts produced by the compilation attempt.
`progress`:Optional observer for monotonic compiler progress.
`cancellationToken`:The token that cancels compilation before an artifact generation is committed.
`sourceSnapshot`:Optional isolated authoring sources. Capture on its owner thread and keep its transaction alive until compilation drains; null selects the current Plugin candidate or active sources. | Source discovery captures live inputs on the owner thread. Reference generation, Roslyn and artifact writes run in the background; progress observers must accept callbacks from either thread. Retain the source generation until the returned operation drains, including after cancellation. | `System.OperationCanceledException`:Thrown when cancellationToken is canceled. | -| [`public System.Threading.Tasks.ValueTask Inno.Scripting.Compiler.ScriptCompiler.CompileRuntimeDeploymentAsync(string targetRuntimeDirectory, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L143) | Compiles only the runtime assembly closure required by a deployed Player and binds it to that Player runtime.
返回:The immutable diagnostics and runtime-only artifacts produced by the compilation attempt.
`targetRuntimeDirectory`:The verified Player Support Pack directory whose runtime assemblies will execute the emitted scripts.
`progress`:Optional observer for monotonic compiler progress.
`cancellationToken`:The token that cancels compilation before an artifact generation is committed. | — | `System.OperationCanceledException`:Thrown when cancellationToken is canceled.
`System.ArgumentException`:Thrown when targetRuntimeDirectory is empty.
`System.IO.DirectoryNotFoundException`:Thrown when targetRuntimeDirectory does not exist.
`System.IO.InvalidDataException`:Thrown when the target runtime has no valid Inno runtime assembly closure. | -| [`public void Inno.Scripting.Compiler.ScriptCompiler.GenerateProjectFiles()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L64) | Regenerates IDE project files from the current source graph and logical API references. | — | `System.InvalidOperationException`:Thrown when the authoring asset pipeline is not initialized. | -| [`public sealed class Inno.Scripting.Compiler.ScriptCompiler`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L16) | Produces deterministic runtime and editor script artifacts from one authoring snapshot. | — | — | - -### `Inno.Scripting.Compiler.ScriptCompilerOptions` - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptCompilerOptions.ScriptCompilerOptions()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L8) | C# 隐式构造函数;按类型声明执行字段初始化和基类构造。 | — | — | -| [`public required string Inno.Scripting.Compiler.ScriptCompilerOptions.projectRootDirectory { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L13) | Gets the project root containing Assets and Library. | — | — | -| [`public sealed class Inno.Scripting.Compiler.ScriptCompilerOptions`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L8) | Identifies the project and derived-cache locations used by one script compiler. | — | — | - -### `Inno.Scripting.Compiler.ScriptDiagnostic` - @@ -284,27 +164,6 @@ static bool AdvanceSampleImport( - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptDiagnostic.ScriptDiagnostic(string id, Inno.Core.Diagnostics.DiagnosticSeverity severity, string message, string? filePath, int line, int column)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Represents one source-oriented script compilation diagnostic.
`id`:The stable compiler, analyzer, or reload diagnostic identifier.
`severity`:The presentation severity of the diagnostic.
`message`:The human-readable diagnostic message.
`filePath`:The source path associated with the diagnostic, or null for a project-level diagnostic.
`line`:The one-based source line, or zero when no source location is available.
`column`:The one-based source column, or zero when no source location is available. | — | — | -| [`public void Inno.Scripting.Compiler.ScriptDiagnostic.Deconstruct(out string id, out Inno.Core.Diagnostics.DiagnosticSeverity severity, out string message, out string? filePath, out int line, out int column)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | C# record 生成的解构入口,按主构造声明顺序返回位置成员。 | — | — | -| [`public bool Inno.Scripting.Compiler.ScriptDiagnostic.Equals(Inno.Scripting.Compiler.ScriptDiagnostic? other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Indicates whether the current object is equal to another object of the same type.
返回:true if the current object is equal to the other parameter; otherwise, false.
`other`:An object to compare with this object. | — | — | -| [`public override bool Inno.Scripting.Compiler.ScriptDiagnostic.Equals(object? obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Determines whether the specified object is equal to the current object.
返回:true if the specified object is equal to the current object; otherwise, false.
`obj`:The object to compare with the current object. | — | — | -| [`public override int Inno.Scripting.Compiler.ScriptDiagnostic.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Serves as the default hash function.
返回:A hash code for the current object. | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptDiagnostic.operator ==(Inno.Scripting.Compiler.ScriptDiagnostic? left, Inno.Scripting.Compiler.ScriptDiagnostic? right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public static bool Inno.Scripting.Compiler.ScriptDiagnostic.operator !=(Inno.Scripting.Compiler.ScriptDiagnostic? left, Inno.Scripting.Compiler.ScriptDiagnostic? right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | C# record 生成的值相等性成员;比较当前 record 状态,并保持相等值的哈希一致。 | — | — | -| [`public override string Inno.Scripting.Compiler.ScriptDiagnostic.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Returns a string that represents the current object.
返回:A string that represents the current object. | — | — | -| [`public int Inno.Scripting.Compiler.ScriptDiagnostic.column { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L31) | The one-based source column, or zero when no source location is available. | — | — | -| [`public string? Inno.Scripting.Compiler.ScriptDiagnostic.filePath { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L29) | The source path associated with the diagnostic, or null for a project-level diagnostic. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptDiagnostic.id { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L26) | The stable compiler, analyzer, or reload diagnostic identifier. | — | — | -| [`public int Inno.Scripting.Compiler.ScriptDiagnostic.line { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L30) | The one-based source line, or zero when no source location is available. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptDiagnostic.message { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L28) | The human-readable diagnostic message. | — | — | -| [`public Inno.Core.Diagnostics.DiagnosticSeverity Inno.Scripting.Compiler.ScriptDiagnostic.severity { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L27) | The presentation severity of the diagnostic. | — | — | -| [`public sealed record Inno.Scripting.Compiler.ScriptDiagnostic : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Represents one source-oriented script compilation diagnostic.
`id`:The stable compiler, analyzer, or reload diagnostic identifier.
`severity`:The presentation severity of the diagnostic.
`message`:The human-readable diagnostic message.
`filePath`:The source path associated with the diagnostic, or null for a project-level diagnostic.
`line`:The one-based source line, or zero when no source location is available.
`column`:The one-based source column, or zero when no source location is available. | — | — | - -### `Inno.Scripting.Compiler.ScriptModuleDeployment` - @@ -313,42 +172,147 @@ static bool AdvanceSampleImport( + + + + + + + + + + + + + + + + + + + + + + + + + + + + -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | | --- | --- | --- | --- | -| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Scripting.Compiler.ScriptModuleDeployment.assemblyScopes { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L70) | Gets explicit scopes keyed by assembly simple name, with case-insensitive lookup. | — | — | -| [`public Inno.Extensibility.Modules.AssemblyDomain Inno.Scripting.Compiler.ScriptModuleDeployment.domain { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L50) | Gets the ownership domain of this module's assemblies. | — | — | -| [`public string Inno.Scripting.Compiler.ScriptModuleDeployment.mainAssemblyPath { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L45) | Gets the absolute path of the module's primary assembly artifact. | — | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset : Inno.Assets.AssetObject`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L13) | Defines one project script assembly and its compilation policy. | — | — | +| [`public string Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.assemblyName { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L19) | Gets the stable assembly name. | — | — | +| [`public Inno.Scripting.Compiler.ScriptAssemblyScope Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.scope { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L25) | Gets the assembly API scope. | — | — | +| [`public string[] Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.references { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L31) | Gets referenced script assembly names. | — | — | +| [`public string[] Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.defines { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L37) | Gets preprocessor symbols applied to this assembly. | — | — | +| [`public bool Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.nullable { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L43) | Gets whether nullable reference analysis is enabled. | — | — | +| [`public bool Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.allowUnsafe { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L49) | Gets whether unsafe source is permitted. | — | — | +| [`public Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.ScriptAssemblyDefinitionAsset()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L54) | Creates an empty definition asset for deserialization. | — | — | +| [`public Inno.Scripting.Compiler.ScriptAssemblyDefinitionAsset.ScriptAssemblyDefinitionAsset(string assemblyName, Inno.Scripting.Compiler.ScriptAssemblyScope scope, string[]? references = null, string[]? defines = null, bool nullable = true, bool allowUnsafe = false)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyDefinitionAsset.cs#L82) | Creates a configured script assembly definition for native asset export.
`assemblyName`: Stable managed assembly name.
`scope`: Runtime or Editor API scope.
`references`: Referenced script assembly names.
`defines`: Preprocessor symbols.
`nullable`: Whether nullable reference analysis is enabled.
`allowUnsafe`: Whether unsafe source is permitted. | — | `System.ArgumentException`: Thrown when is empty. | +| [`public enum Inno.Scripting.Compiler.ScriptAssemblyScope : int`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L6) | Identifies whether a project script assembly can use editor-only APIs. | — | — | +| [`Inno.Scripting.Compiler.ScriptAssemblyScope.Runtime = 0`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L11) | The assembly can use runtime scripting APIs only. | — | — | +| [`Inno.Scripting.Compiler.ScriptAssemblyScope.Editor = 1`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptAssemblyScope.cs#L16) | The assembly can use runtime and editor scripting APIs. | — | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptSourceAsset : Inno.Assets.AssetObject`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L11) | Describes an imported C# source snapshot and its parse diagnostics. | — | — | +| [`public Inno.Scripting.Compiler.ScriptAssemblyScope Inno.Scripting.Compiler.ScriptSourceAsset.scope { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L17) | Gets the default assembly scope inferred for the source. | — | — | +| [`public string[] Inno.Scripting.Compiler.ScriptSourceAsset.declaredTypeNames { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L23) | Gets syntax-level type names declared by the source. | — | — | +| [`public string[] Inno.Scripting.Compiler.ScriptSourceAsset.parseDiagnostics { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L29) | Gets parse diagnostics associated with the source snapshot. | — | — | +| [`public Inno.Scripting.Compiler.ScriptSourceAsset.ScriptSourceAsset()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L34) | Creates an empty script source asset for deserialization. | — | — | +| [`public sealed class Inno.Scripting.Compiler.CSharpSampleSourceRewriter : Inno.Assets.Pipeline.IAssetSampleSourceRewriter`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L17) | Gives imported C# sample scripts distinct type identities before their scene assets are remapped. | — | — | +| [`public void Inno.Scripting.Compiler.CSharpSampleSourceRewriter.Transform(Inno.Assets.Pipeline.AssetSampleTransformContext context)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L25) | Rewrites explicit stable type identities in cloned C# scripts and registers their asset mappings.
`context`: The private sample import stage and its identity map. | — | — | +| [`public Inno.Scripting.Compiler.CSharpSampleSourceRewriter.CSharpSampleSourceRewriter()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/CSharpSampleSourceRewriter.cs#L17) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L19) | Enforces logical scripting namespaces and rejects direct implementation namespace access. | — | — | +| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.directImplementationNamespaceDiagnosticId = "INNO2001"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L24) | Diagnostic identifier for direct implementation namespace access. | — | — | +| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.missingLogicalNamespaceDiagnosticId = "INNO2002"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L29) | Diagnostic identifier for a missing logical namespace import. | — | — | +| [`public const string Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.compilationWideUsingDiagnosticId = "INNO2003"`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L34) | Diagnostic identifier for a forbidden compilation-wide namespace import. | — | — | +| [`public override System.Collections.Immutable.ImmutableArray Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.SupportedDiagnostics { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L63) | Gets the complete compiler diagnostic set enforced by this analyzer. | — | — | +| [`public override void Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext context)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L75) | Initializes owned resources and establishes the instance's active lifetime.
`context`: The context that supplies state and services for this operation. | — | — | +| [`public Inno.Scripting.Compiler.LogicalScriptingApiAnalyzer.LogicalScriptingApiAnalyzer()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/LogicalScriptingApiAnalyzer.cs#L19) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptCompilationResult`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L13) | Reports the outputs and diagnostics of one authoring-generation or runtime-deployment script compilation. | — | — | +| [`public bool Inno.Scripting.Compiler.ScriptCompilationResult.success { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L47) | Gets whether every discovered script assembly compiled or reused successfully. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.diagnostics { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L52) | Gets all diagnostics produced by the compilation. | — | — | +| [`public string? Inno.Scripting.Compiler.ScriptCompilationResult.outputDirectory { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L57) | Gets the generation output directory when one was created. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.stageTimings { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L62) | Gets completed compiler stage timings in execution order. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.runtimeAssemblyPaths { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L67) | Gets exact runtime-scope managed assemblies suitable for a deployed Player. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.moduleDeployments { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L72) | Gets the immutable module artifacts and dependency topology produced by this compilation. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.compiledAssemblyNames { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L77) | Gets assembly names compiled during this request instead of reused from the artifact cache. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationResult.reusedAssemblyNames { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L82) | Gets assembly names reused from the deterministic artifact cache. | — | — | +| [`public static Inno.Scripting.Compiler.ScriptCompilationResult Inno.Scripting.Compiler.ScriptCompilationResult.Failure(Inno.Scripting.Compiler.ScriptDiagnostic diagnostic)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationResult.cs#L93) | Creates a failed result for an exception intercepted at an orchestration boundary.
`diagnostic`: The diagnostic describing why no candidate generation was produced. | A failed immutable result with no output directory or module deployments. | — | +| [`public readonly record struct Inno.Scripting.Compiler.ScriptCompilationStageTiming : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Describes the elapsed wall time of one completed script compilation stage. | — | — | +| [`public Inno.Scripting.Compiler.ScriptCompilationStageTiming.ScriptCompilationStageTiming(string stage, System.TimeSpan elapsed)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L25) | Creates one immutable stage timing sample.
`stage`: The human-readable stage description.
`elapsed`: The wall time spent in the stage. | — | `System.ArgumentException`: Thrown when is empty or contains only whitespace.
`System.ArgumentOutOfRangeException`: Thrown when is negative. | +| [`public string Inno.Scripting.Compiler.ScriptCompilationStageTiming.stage { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L40) | Gets the human-readable stage description. | — | — | +| [`public System.TimeSpan Inno.Scripting.Compiler.ScriptCompilationStageTiming.elapsed { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L45) | Gets the wall time spent in the stage. | — | — | +| [`public override string Inno.Scripting.Compiler.ScriptCompilationStageTiming.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.operator !=(Inno.Scripting.Compiler.ScriptCompilationStageTiming left, Inno.Scripting.Compiler.ScriptCompilationStageTiming right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.operator ==(Inno.Scripting.Compiler.ScriptCompilationStageTiming left, Inno.Scripting.Compiler.ScriptCompilationStageTiming right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Scripting.Compiler.ScriptCompilationStageTiming.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.Equals(object obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Scripting.Compiler.ScriptCompilationStageTiming.Equals(Inno.Scripting.Compiler.ScriptCompilationStageTiming other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationStageTiming.cs#L8) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public sealed record Inno.Scripting.Compiler.ScriptDiagnostic : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Represents one source-oriented script compilation diagnostic.
`id`: The stable compiler, analyzer, or reload diagnostic identifier.
`severity`: The presentation severity of the diagnostic.
`message`: The human-readable diagnostic message.
`filePath`: The source path associated with the diagnostic, or for a project-level diagnostic.
`line`: The one-based source line, or zero when no source location is available.
`column`: The one-based source column, or zero when no source location is available. | — | — | +| [`public Inno.Scripting.Compiler.ScriptDiagnostic.ScriptDiagnostic(string id, Inno.Core.Diagnostics.DiagnosticSeverity severity, string message, string? filePath, int line, int column)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Represents one source-oriented script compilation diagnostic.
`id`: The stable compiler, analyzer, or reload diagnostic identifier.
`severity`: The presentation severity of the diagnostic.
`message`: The human-readable diagnostic message.
`filePath`: The source path associated with the diagnostic, or for a project-level diagnostic.
`line`: The one-based source line, or zero when no source location is available.
`column`: The one-based source column, or zero when no source location is available. | — | — | +| [`public string Inno.Scripting.Compiler.ScriptDiagnostic.id { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L26) | The stable compiler, analyzer, or reload diagnostic identifier. | — | — | +| [`public Inno.Core.Diagnostics.DiagnosticSeverity Inno.Scripting.Compiler.ScriptDiagnostic.severity { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L27) | The presentation severity of the diagnostic. | — | — | +| [`public string Inno.Scripting.Compiler.ScriptDiagnostic.message { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L28) | The human-readable diagnostic message. | — | — | +| [`public string? Inno.Scripting.Compiler.ScriptDiagnostic.filePath { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L29) | The source path associated with the diagnostic, or for a project-level diagnostic. | — | — | +| [`public int Inno.Scripting.Compiler.ScriptDiagnostic.line { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L30) | The one-based source line, or zero when no source location is available. | — | — | +| [`public int Inno.Scripting.Compiler.ScriptDiagnostic.column { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L31) | The one-based source column, or zero when no source location is available. | — | — | +| [`public override string Inno.Scripting.Compiler.ScriptDiagnostic.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Returns a string that represents the current object. | A string that represents the current object. | — | +| [`public static bool Inno.Scripting.Compiler.ScriptDiagnostic.operator !=(Inno.Scripting.Compiler.ScriptDiagnostic? left, Inno.Scripting.Compiler.ScriptDiagnostic? right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Scripting.Compiler.ScriptDiagnostic.operator ==(Inno.Scripting.Compiler.ScriptDiagnostic? left, Inno.Scripting.Compiler.ScriptDiagnostic? right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Scripting.Compiler.ScriptDiagnostic.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Serves as the default hash function. | A hash code for the current object. | — | +| [`public override bool Inno.Scripting.Compiler.ScriptDiagnostic.Equals(object? obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Determines whether the specified object is equal to the current object.
`obj`: The object to compare with the current object. | if the specified object is equal to the current object; otherwise, . | — | +| [`public bool Inno.Scripting.Compiler.ScriptDiagnostic.Equals(Inno.Scripting.Compiler.ScriptDiagnostic? other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Scripting.Compiler.ScriptDiagnostic.Deconstruct(out string id, out Inno.Core.Diagnostics.DiagnosticSeverity severity, out string message, out string? filePath, out int line, out int column)`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptDiagnostic.cs#L25) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptModuleDeployment`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L14) | Describes the immutable assembly artifacts and dependency topology of one compiled script module. The receiving host chooses how these artifacts enter its module catalog. | — | — | | [`public string Inno.Scripting.Compiler.ScriptModuleDeployment.moduleName { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L40) | Gets the logical module name used to resolve dependencies within this compiled generation. | — | — | -| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptModuleDeployment.preloadAssemblyPaths { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L60) | Gets the additional assembly artifacts that belong to this same module generation. | — | — | +| [`public string Inno.Scripting.Compiler.ScriptModuleDeployment.mainAssemblyPath { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L45) | Gets the absolute path of the module's primary assembly artifact. | — | — | +| [`public Inno.Extensibility.Modules.AssemblyDomain Inno.Scripting.Compiler.ScriptModuleDeployment.domain { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L50) | Gets the ownership domain of this module's assemblies. | — | — | | [`public Inno.Extensibility.Modules.AssemblyScope Inno.Scripting.Compiler.ScriptModuleDeployment.scope { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L55) | Gets the dependency scope used for assemblies without an explicit scope override. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptModuleDeployment.preloadAssemblyPaths { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L60) | Gets the additional assembly artifacts that belong to this same module generation. | — | — | | [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptModuleDeployment.upstreamModuleNames { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L65) | Gets the logical names of modules that supply managed dependencies to this module. | — | — | -| [`public sealed class Inno.Scripting.Compiler.ScriptModuleDeployment`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L14) | Describes the immutable assembly artifacts and dependency topology of one compiled script module. The receiving host chooses how these artifacts enter its module catalog. | — | — | - -### `Inno.Scripting.Compiler.ScriptSourceAsset` - - - - - - - -| 声明 | 行为与结果 | 所有权 / 调用约束 | 失败或限制 | -| --- | --- | --- | --- | -| [`public Inno.Scripting.Compiler.ScriptSourceAsset.ScriptSourceAsset()`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L34) | Creates an empty script source asset for deserialization. | — | — | -| [`public string[] Inno.Scripting.Compiler.ScriptSourceAsset.declaredTypeNames { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L23) | Gets syntax-level type names declared by the source. | — | — | -| [`public string[] Inno.Scripting.Compiler.ScriptSourceAsset.parseDiagnostics { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L29) | Gets parse diagnostics associated with the source snapshot. | — | — | -| [`public Inno.Scripting.Compiler.ScriptAssemblyScope Inno.Scripting.Compiler.ScriptSourceAsset.scope { get; private set; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L17) | Gets the default assembly scope inferred for the source. | — | — | -| [`public sealed class Inno.Scripting.Compiler.ScriptSourceAsset : Inno.Assets.AssetObject`](../../src/runtime/scripting/Inno.Scripting.Compiler/Assets/ScriptSourceAsset.cs#L11) | Describes an imported C# source snapshot and its parse diagnostics. | — | — | +| [`public System.Collections.Generic.IReadOnlyDictionary Inno.Scripting.Compiler.ScriptModuleDeployment.assemblyScopes { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptModuleDeployment.cs#L70) | Gets explicit scopes keyed by assembly simple name, with case-insensitive lookup. | — | — | +| [`public interface Inno.Scripting.Compiler.IScriptCompilationEnvironmentProvider`](../../src/runtime/scripting/Inno.Scripting.Compiler/IScriptCompilationEnvironmentProvider.cs#L8) | Supplies the host-selected framework contract for one compilation or IDE projection operation. | — | — | +| [`Inno.Scripting.Compiler.ScriptCompilationInputs Inno.Scripting.Compiler.IScriptCompilationEnvironmentProvider.Capture(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/runtime/scripting/Inno.Scripting.Compiler/IScriptCompilationEnvironmentProvider.cs#L19) | Captures current SDK inputs without choosing a framework inside the scripting compiler.
`cancellationToken`: Cancels input preparation before any script artifact is published. | Complete immutable inputs; missing or inconsistent SDK inputs must fail explicitly. | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptCompilationInputs`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationInputs.cs#L11) | Describes framework references and predefined symbols selected by the authoring host's SDK. | — | — | +| [`public Inno.Scripting.Compiler.ScriptCompilationInputs.ScriptCompilationInputs(string sdkIdentity, System.Collections.Generic.IEnumerable frameworkReferences, System.Collections.Generic.IEnumerable predefinedSymbols)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationInputs.cs#L31) | Freezes a complete C# 14 and .NET 10 reference contract without retaining SDK objects.
`sdkIdentity`: The exact SDK identity that evaluated this contract.
`frameworkReferences`: Absolute framework assembly paths selected by SDK reference resolution.
`predefinedSymbols`: Framework and configuration symbols produced by SDK target execution. | — | `System.ArgumentException`: The identity, reference set or a reference path is invalid.
`System.ArgumentNullException`: A collection is null. | +| [`public string Inno.Scripting.Compiler.ScriptCompilationInputs.sdkIdentity { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationInputs.cs#L50) | Gets the exact SDK identity used for IDE consistency checks and artifact identity. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationInputs.frameworkReferences { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationInputs.cs#L55) | Gets the SDK-selected framework paths, whose content is frozen before Roslyn compilation. | — | — | +| [`public System.Collections.Generic.IReadOnlyList Inno.Scripting.Compiler.ScriptCompilationInputs.predefinedSymbols { get; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationInputs.cs#L60) | Gets SDK-produced symbols shared by runtime compilation and IDE projects. | — | — | +| [`public readonly record struct Inno.Scripting.Compiler.ScriptCompilationProgress : System.IEquatable`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Describes monotonic progress through one script compilation.
`fraction`: The completed fraction in the inclusive range from zero to one.
`stage`: The current human-readable compiler stage. | — | — | +| [`public Inno.Scripting.Compiler.ScriptCompilationProgress.ScriptCompilationProgress(float fraction, string stage)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Describes monotonic progress through one script compilation.
`fraction`: The completed fraction in the inclusive range from zero to one.
`stage`: The current human-readable compiler stage. | — | — | +| [`public float Inno.Scripting.Compiler.ScriptCompilationProgress.fraction { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L13) | The completed fraction in the inclusive range from zero to one. | — | — | +| [`public string Inno.Scripting.Compiler.ScriptCompilationProgress.stage { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L14) | The current human-readable compiler stage. | — | — | +| [`public override string Inno.Scripting.Compiler.ScriptCompilationProgress.ToString()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Returns the fully qualified type name of this instance. | The fully qualified type name. | — | +| [`public static bool Inno.Scripting.Compiler.ScriptCompilationProgress.operator !=(Inno.Scripting.Compiler.ScriptCompilationProgress left, Inno.Scripting.Compiler.ScriptCompilationProgress right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public static bool Inno.Scripting.Compiler.ScriptCompilationProgress.operator ==(Inno.Scripting.Compiler.ScriptCompilationProgress left, Inno.Scripting.Compiler.ScriptCompilationProgress right)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public override int Inno.Scripting.Compiler.ScriptCompilationProgress.GetHashCode()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Returns the hash code for this instance. | A 32-bit signed integer that is the hash code for this instance. | — | +| [`public override bool Inno.Scripting.Compiler.ScriptCompilationProgress.Equals(object obj)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Indicates whether this instance and a specified object are equal.
`obj`: The object to compare with the current instance. | if and this instance are the same type and represent the same value; otherwise, . | — | +| [`public bool Inno.Scripting.Compiler.ScriptCompilationProgress.Equals(Inno.Scripting.Compiler.ScriptCompilationProgress other)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Indicates whether the current object is equal to another object of the same type.
`other`: An object to compare with this object. | if the current object is equal to the parameter; otherwise, . | — | +| [`public void Inno.Scripting.Compiler.ScriptCompilationProgress.Deconstruct(out float fraction, out string stage)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilationProgress.cs#L12) | Inherited or compiler-provided contract. | — | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptCompiler`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L16) | Produces deterministic runtime and editor script artifacts from one authoring snapshot. | — | — | +| [`public Inno.Scripting.Compiler.ScriptCompiler.ScriptCompiler(Inno.Scripting.Compiler.ScriptCompilerOptions options, Inno.Assets.Pipeline.AssetPipeline assets, Inno.Plugins.Authoring.PluginEnvironment plugins)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L40) | Creates a compiler over explicit authoring services owned by one host.
`options`: The project and derived-cache locations used by compilation.
`assets`: The authoring asset pipeline supplying committed source artifacts.
`plugins`: The Plugin environment supplying the candidate Plugin source generation. | — | `System.ArgumentNullException`: Thrown when an owner argument is .
`System.ArgumentException`: Thrown when the project root is empty. | +| [`public void Inno.Scripting.Compiler.ScriptCompiler.GenerateProjectFiles()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L66) | Regenerates IDE project files from the current source graph and logical API references. | — | `System.InvalidOperationException`: Thrown when the authoring asset pipeline is not initialized. | +| [`public System.Threading.Tasks.ValueTask Inno.Scripting.Compiler.ScriptCompiler.CompileAuthoringGenerationAsync(System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Inno.Assets.Pipeline.IAssetSourceSnapshot? sourceSnapshot = null)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L109) | Compiles a complete runtime and editor candidate generation without activating it.
`progress`: Optional observer for monotonic compiler progress.
`cancellationToken`: The token that cancels compilation before an artifact generation is committed.
`sourceSnapshot`: Optional isolated authoring sources. Capture on its owner thread and keep its transaction alive until compilation drains; null selects the current Plugin candidate or active sources.
约束:Source discovery captures live inputs on the owner thread. Reference generation, Roslyn and artifact writes run in the background; progress observers must accept callbacks from either thread. Retain the source generation until the returned operation drains, including after cancellation. Complete cache trees use Core IO atomic publication and replacement. A committed cache remains valid if cleanup of its previous tree fails; compilation does not activate either generation. | The immutable diagnostics and artifacts produced by the compilation attempt. | `System.OperationCanceledException`: Thrown when cancellationToken is canceled.
`System.IO.IOException`: Artifact publication fails, or cleanup fails after a complete cache tree commits.
`System.UnauthorizedAccessException`: The filesystem continues denying artifact access after the bounded Core IO wait.
`System.AggregateException`: Cache replacement and restoration both fail; the exception identifies the preserved previous tree. | +| [`public System.Threading.Tasks.ValueTask Inno.Scripting.Compiler.ScriptCompiler.CompileRuntimeDeploymentAsync(string targetRuntimeDirectory, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L170) | Compiles only the runtime assembly closure required by a deployed Player and binds it to that Player runtime.
`targetRuntimeDirectory`: The verified Player Support Pack directory whose runtime assemblies will execute the emitted scripts.
`progress`: Optional observer for monotonic compiler progress.
`cancellationToken`: The token that cancels compilation before an artifact generation is committed.
约束:Complete cache trees use Core IO atomic publication and replacement. Cancellation before commit preserves the previous tree; cancellation does not undo a committed cache. Cleanup failure after commit preserves the installed tree and remaining backup without activating a generation. | The immutable diagnostics and runtime-only artifacts produced by the compilation attempt. | `System.OperationCanceledException`: Thrown when cancellationToken is canceled.
`System.ArgumentException`: Thrown when targetRuntimeDirectory is empty.
`System.IO.DirectoryNotFoundException`: Thrown when targetRuntimeDirectory does not exist.
`System.IO.InvalidDataException`: Thrown when the target runtime has no valid Inno runtime assembly closure.
`System.IO.IOException`: Artifact publication fails, or cleanup fails after a complete cache tree commits.
`System.UnauthorizedAccessException`: The filesystem continues denying artifact access after the bounded Core IO wait.
`System.AggregateException`: Cache replacement and restoration both fail; the exception identifies the preserved previous tree. | +| [`public int Inno.Scripting.Compiler.ScriptCompiler.CollectArtifacts(System.Collections.Generic.IEnumerable retainedDirectories)`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs#L200) | Removes unreferenced compiler generations while retaining the supplied active directories.
`retainedDirectories`: The generation directories that remain reachable from active runtime sessions. | The number of obsolete generation directories removed from the compiler cache. | — | +| [`public sealed class Inno.Scripting.Compiler.ScriptCompilerOptions`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L8) | Identifies the project and derived-cache locations used by one script compiler. | — | — | +| [`public required string Inno.Scripting.Compiler.ScriptCompilerOptions.projectRootDirectory { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L13) | Gets the project root containing Assets and Library. | — | — | +| [`public required Inno.Scripting.Compiler.IScriptCompilationEnvironmentProvider Inno.Scripting.Compiler.ScriptCompilerOptions.compilationEnvironment { get; init; }`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L18) | Gets the host-owned source of SDK framework inputs for each operation. | — | — | +| [`public Inno.Scripting.Compiler.ScriptCompilerOptions.ScriptCompilerOptions()`](../../src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs#L8) | Inherited or compiler-provided contract. | — | — | ## 项目依赖 -- [Inno.Core.Collections](../core/Inno.Core.Collections.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md):公开引用边界由实际签名核对。 -- [Inno.Scripting.Api](Inno.Scripting.Api.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Assets](../assets/Inno.Assets.md):公开引用边界由实际签名核对。 -- [Inno.Assets.Pipeline](../assets/Inno.Assets.Pipeline.md):公开引用边界由实际签名核对。 -- [Inno.Plugins.Authoring](../plugins/Inno.Plugins.Authoring.md):公开引用边界由实际签名核对。 -- [Inno.Core.Diagnostics](../core/Inno.Core.Diagnostics.md):公开引用边界由实际签名核对。 -- [Inno.Runtime.Generators](../runtime/Inno.Runtime.Generators.md):实现依赖,PrivateAssets="compile"。 -- [Inno.Core.Serialization.Generators](../core/Inno.Core.Serialization.Generators.md):实现依赖,PrivateAssets="compile"。 +| 工程 | 边界 | +| --- | --- | +| `Inno.Core.Collections` | 实现依赖;不传递 compile surface | +| `Inno.Core.IO` | 实现依赖;不传递 compile surface | +| `Inno.Extensibility.Modules` | 公开签名依赖 | +| `Inno.Scripting.Api` | 实现依赖;不传递 compile surface | +| `Inno.Assets` | 公开签名依赖 | +| `Inno.Assets.Pipeline` | 公开签名依赖 | +| `Inno.Plugins.Authoring` | 公开签名依赖 | +| `Inno.Core.Diagnostics` | 公开签名依赖 | +| `Inno.Runtime.Generators` | 实现依赖;不传递 compile surface | +| `Inno.Core.Serialization.Generators` | 实现依赖;不传递 compile surface | diff --git a/docs/testing/Inno.Editor.Interactions.Tests.md b/docs/testing/Inno.Editor.Interactions.Tests.md index c8018b3b2..bb99ae0e8 100644 --- a/docs/testing/Inno.Editor.Interactions.Tests.md +++ b/docs/testing/Inno.Editor.Interactions.Tests.md @@ -22,7 +22,7 @@ - [Inno.Editor.PlayMode](../editor/Inno.Editor.PlayMode.md) - [Inno.Editor.Settings](../editor/Inno.Editor.Settings.md) - [Inno.Editor.Panel.FileBrowser](../editor/Inno.Editor.Panel.FileBrowser.md) -- [Inno.Editor.Panel.Global](../editor/Inno.Editor.Panel.Global.md) +- [Inno.Editor.Workbench](../editor/Inno.Editor.Workbench.md) - [Inno.Editor.Panel.Logging](../editor/Inno.Editor.Panel.Logging.md) - [Inno.Extensibility.Modules](../extensibility/Inno.Extensibility.Modules.md) - [Inno.Extensibility.Types](../extensibility/Inno.Extensibility.Types.md) diff --git a/docs/testing/Inno.Scripting.Compiler.Tests.md b/docs/testing/Inno.Scripting.Compiler.Tests.md index 069191fc5..0996e47c3 100644 --- a/docs/testing/Inno.Scripting.Compiler.Tests.md +++ b/docs/testing/Inno.Scripting.Compiler.Tests.md @@ -17,5 +17,6 @@ | `FrameworkContentChangesSelectNewArtifacts` | reference pack 内容变化后重新生成 API 与脚本产物;该用例修改进程环境,使用独立 testhost。 | | `GeneratedProjectsIgnoreAmbientBuildConfiguration` | 父目录存在拒绝构建的 `Directory.Build.props/targets` 时,生成的 Runtime 与 Editor 工程仍使用自己的完整输入完成构建。 | | `NestedSolutionRestoresOnlyItsOwnProjects` | 仓库子目录的 Solution 正常还原自己的工程;引擎的 Solution 还原规则只作用于所属 `InnoEngine.sln`。 | +| `ScriptCachePublicationTests` | 从公开编译结果与进度观察拥有的产物:临时读取释放后发布,持续阻塞和取消不破坏前一 generation,失效缓存替换失败保留整棵原树,释放后显式编译完成修复。依赖 Windows 目录 sharing 语义的用例在其他宿主明确标为未执行。 | Fixture 使用临时项目、公开资产挂载和序列化服务;测试完成后释放宿主及临时文件。没有 `InternalsVisibleTo`、私有反射或测试后门。 diff --git a/docs/testing/Inno.Testing.Source.md b/docs/testing/Inno.Testing.Source.md index 748c80f14..4b69b95f3 100644 --- a/docs/testing/Inno.Testing.Source.md +++ b/docs/testing/Inno.Testing.Source.md @@ -6,13 +6,14 @@ ## 职责与边界 -只为需要真实源码的测试解析显式 checkout 输入,不进入生产或发布闭包。读取器只有这个程序集作为源码 owner;消费项目通过 `tests/RepositoryInput.targets` 声明 `Inno.Testing.RepositoryRoot` assembly metadata 和项目引用,不重复编译共享 C# 文件。 +为需要真实源码和 SDK 输出的测试解析显式输入,不进入生产或发布闭包。消费项目通过 `tests/RepositoryInput.targets` 声明 checkout metadata 和项目引用,不重复编译共享 C# 文件。输出 owner 在 SDK 完成路径求值后、生成程序集属性前记录,涵盖显式 `ArtifactsPath` 与 `UseArtifactsOutput`;普通输出不伪造独立 owner。 ## 公开 API | API | 语义与失败 | | --- | --- | | `TestRepository.GetRoot(Assembly testAssembly)` | 返回所属测试程序集明确声明的绝对 checkout。空程序集抛出 `ArgumentNullException`;缺失、重复、相对路径或没有引擎 Solution 标记的声明抛出 `InvalidOperationException`。不猜测执行目录、进程当前目录或其他 checkout。 | +| `TestRepository.GetArtifactsPath(Assembly testAssembly)` | 返回编译该测试程序集时 SDK 选定的绝对输出 owner;没有独立输出声明时返回 null,继续使用正常 SDK 输出求值。空程序集、重复、空或相对声明明确失败,不从旧 bin/obj 猜测。 | ## 使用与生命周期 diff --git a/docs/testing/README.md b/docs/testing/README.md index f50bd9c58..0300cc1db 100644 --- a/docs/testing/README.md +++ b/docs/testing/README.md @@ -99,3 +99,6 @@ Native 测试部署在成功 Build 后执行;CI 的目标 Build 明确设置 I `BgfxShutdownTests.ShutdownAfterAnOffscreenGraphReleasesTheProcessOwner` 使用真实窗口、离屏 Graph 与设备所有资源,验证关闭后的进程 owner 可再次取得且借用窗口仍有效。默认使用当前测试宿主的 D3D11 / Metal;显式设置 `INNO_TEST_GRAPHICS_API=Vulkan` 或 `Direct3D12` 可在对应设备单独执行同一回归。该 ID 必须与实际能力一致,不提供失败后切换后端的 fallback。 脚本测试覆盖 C# 14 的 `field`、空条件赋值、Span、开放泛型 `nameof`、推断类型 Lambda 参数修饰符,以及跨插件扩展成员的引擎/IDE 一致性;另验证 framework reference 内容变化和 SDK target 生成符号的缓存失效。 + + +隔离测试入口保留每个程序集的 `.discovery.log`,发现阶段的 stderr 异常即使伴随零退出码也会失败。程序集模式由选定 `DOTNET_HOST_PATH` 让 VSTest 推导运行根,避免重复传入同一环境键。`Invoke-EditorSmoke.ps1 -GraphicsApi ` 通过正式 Editor CLI 选择图形 API,不在脚本中维护平台列表。 diff --git a/docs/tooling/Inno.Tooling.Architecture.md b/docs/tooling/Inno.Tooling.Architecture.md index e66bc43c9..7066e4c08 100644 --- a/docs/tooling/Inno.Tooling.Architecture.md +++ b/docs/tooling/Inno.Tooling.Architecture.md @@ -1,6 +1,13 @@ # Inno.Tooling.Architecture -[Tooling 索引](README.md) +[Tooling 索引](README.md) · [Wiki 首页](../README.md) + +## 独立输出目录的检查 + +使用 `--dotnet ` 和 `--artifacts-path ` 检查独立 SDK 输出。求值图的 `TargetPath` 决定公开程序集的 owner,Wiki 的 `ReferencePath`、`Compile`、语言和符号也使用同一输出选择。不要把独立输出复制回默认 bin 来满足检查。 + +`--configuration` 支持 `Debug`、`SourceDebug` 和 `Release`。`--artifacts-path` 要求显式 `--dotnet`;SDK 失败、缺失引用或取消均不能当作通过。 + 退休 catch 的 AST 规则拒绝直接捕获 `RetirementPendingException` / `RetirementTimeoutException`(包括全限定名)。 应捕获 Exception,并用 Core `RetirementPendingException.Find` 检查整棵异常树后原样传播。 @@ -15,6 +22,8 @@ Registry 派生实现不得直接调用 `OnCleanupFailed` 代替退休失败传 默认检查手写源码的多参数声明:逐参数换行、右括号对齐,以及紧随参数列表的 `) {`。 检查基于 C# 语法节点,覆盖方法、构造函数、委托、主构造函数、局部函数和 Lambda,不改写源码。 +Editor 源码还检查同类型重复别名、重复 XML summary,以及循环中依赖手动 PopId 收尾的 ImGui ID 作用域。循环行必须在局部 finally 中释放 ID,确保异常、break 和正常完成均保持配对。该 AST 门禁识别已确认的源码模式,不替代其他 Begin/End、Push/Pop 的所有权审查或真实 Native 异常测试;分组取得与包装方法仍需沿实际调用方核对。 + ## 检查范围 SDK 工程图检查读取实际程序集身份、产品目标、ProjectReference、Compile 和 using 项,验证依赖方向、源码 owner 与可执行入口。`--project-graph` 的非公开诊断模型只描述真实求值结果,不维护 NuGet 解析规则;[依赖还原](../build/DEPENDENCY_SUBMISSION.md)直接使用标准 SDK 流程。 @@ -78,24 +87,7 @@ ProjectReference、Compile、程序集身份、产品与 Native target。不会 BGCS/Cpp2C target profile 必须显式声明 Native owner 内的输出目录,与宿主输出不能相同或互相包含。 此规则覆盖 managed binding 和 C++ bridge,避免任一生成器的原子目录替换删除另一目标的产物。 -## 当前源码公开 API 清单 - -当前声明由源码语义清单核对,包含 public 和可供外部派生实现使用的 protected 成员。构建契约不作为游戏脚本导出;线程、失败与所有权要求见前文及每项 XML。 - -### `Inno.Tooling.Architecture.ArchitectureValidator` - - - - -| 声明 | 行为、参数与结果 | -| --- | --- | -| [`public static int Inno.Tooling.Architecture.ArchitectureValidator.Execute(string[] arguments, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../tools/Inno.Tooling.Architecture/ArchitectureValidator.cs#L61) | Executes repository validation or an explicitly requested documentation maintenance operation.
`arguments`:The optional repository path, configuration, XML maintenance flag, explicit SDK, evaluated graph output, and Wiki inventories with explicitly selected runtime and editor script reference assemblies. --wiki-target limits product compilation to that explicit target; shared Wiki checks remain complete.
`cancellationToken`:Cancels owned SDK evaluation processes and drains them before returning.
返回:Zero when validation succeeds, or one when any invariant is violated.
T:System.ArgumentException:An option is unknown or more than one repository path is supplied.
T:System.IO.DirectoryNotFoundException:The requested path is not inside an engine checkout.
T:System.OperationCanceledException:The caller cancels evaluated SDK project inspection.
T:System.IO.InvalidDataException:A selected Wiki script reference is not the declared logical API assembly. | -| [`public static class Inno.Tooling.Architecture.ArchitectureValidator`](../../tools/Inno.Tooling.Architecture/ArchitectureValidator.cs#L16) | Checks repository dependency, API documentation and source ownership invariants. | - -## 项目依赖 -- [Inno.Extensibility.Catalogs](../extensibility/Inno.Extensibility.Catalogs.md):公开引用边界由实际签名核对。 -- [Inno.Build.Toolchains](../build/Inno.Build.Toolchains.md):实现依赖,PrivateAssets="compile"。 ## 缺失目标契约的诊断 @@ -108,3 +100,22 @@ BGCS/Cpp2C target profile 必须显式声明 Native owner 内的输出目录, ## 开发入口项目边界 项目归属检查要求 entry 仅包含两个普通可执行项目:Editor 以宿主条件项目引用选择真实产品,Build 直接引用共享 CLI。共同生产层不得反向依赖 entry;backend、平台基础模块与 integration 的原有边界继续检查。 + +## 当前源码公开 API 清单 + +此清单对应当前 SDK 求值与源码符号;内部类型不构成公开 API。 + + + + +| API 与源码 | 契约与参数 | 返回值 | 重要异常 | +| --- | --- | --- | --- | +| [`public static class Inno.Tooling.Architecture.ArchitectureValidator`](../../tools/Inno.Tooling.Architecture/ArchitectureValidator.cs#L16) | Checks repository dependency, API documentation and source ownership invariants. | — | — | +| [`public static int Inno.Tooling.Architecture.ArchitectureValidator.Execute(string[] arguments, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))`](../../tools/Inno.Tooling.Architecture/ArchitectureValidator.cs#L61) | Executes repository validation or an explicitly requested documentation maintenance operation.
`arguments`: The optional repository path, configuration, XML maintenance flag, explicit SDK, evaluated graph output, and Wiki inventories with explicitly selected runtime and editor script reference assemblies. --wiki-target limits product compilation to that explicit target; shared Wiki checks remain complete.
`cancellationToken`: Cancels owned SDK evaluation processes and drains them before returning. | Zero when validation succeeds, or one when any invariant is violated. | `System.ArgumentException`: An option is unknown or more than one repository path is supplied.
`System.IO.DirectoryNotFoundException`: The requested path is not inside an engine checkout.
`System.OperationCanceledException`: The caller cancels evaluated SDK project inspection.
`System.IO.InvalidDataException`: A selected Wiki script reference is not the declared logical API assembly. | + +## 项目依赖 + +| 工程 | 边界 | +| --- | --- | +| `Inno.Extensibility.Catalogs` | 公开签名依赖 | +| `Inno.Build.Toolchains` | 实现依赖;不传递 compile surface | diff --git a/entry/InnoEngine.Editor/InnoEngine.Editor.csproj b/entry/InnoEngine.Editor/InnoEngine.Editor.csproj index 2d7afb91b..9a56e1e3f 100644 --- a/entry/InnoEngine.Editor/InnoEngine.Editor.csproj +++ b/entry/InnoEngine.Editor/InnoEngine.Editor.csproj @@ -8,6 +8,7 @@ $(InnoDevelopmentTarget) $(InnoDevelopmentTarget) $(InnoDevelopmentEditorBuildProperties) + $(InnoDevelopmentEditorProject) Exe InnoEngine.Editor InnoEngine.Editor diff --git a/records/architecture/EDITOR_UI_CLOSURE_PLAN.md b/records/architecture/EDITOR_UI_CLOSURE_PLAN.md new file mode 100644 index 000000000..349f8ceac --- /dev/null +++ b/records/architecture/EDITOR_UI_CLOSURE_PLAN.md @@ -0,0 +1,69 @@ +# Editor UI closure execution ledger + +Production candidate: `dd5979c613b0fe8ebd9f7026f25001ceee9f8cee` on `editor-ui`. All applicable **local** implementation and acceptance gates are complete. The final documentation/evidence-only delivery SHA, exact-head Windows/macOS CI and Ready transition are recorded in the [delivery attachment](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338); historical green CI is not reused. + +This replaces intermediate checkpoints as the current ledger. Original failed attempts remain in the owned evidence and dated reports; they are not silently converted to passes. The [final acceptance report](acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md) is the local result authority. The [307-row requirement matrix](acceptance/editor-ui-2026-10-30/original-plan-requirements.csv) explicitly distinguishes functional requirements, workflow rules, status vocabulary and future post-merge conditions. Its fifteen delivery conditions are resolved by the exact-head attachment, not inferred from local success. + +## Issue execution map + +Issue creation preceded implementation. Existing #13, #16 and #19 are reused; #27 is the parent, #28–#39 are E01–E12, and #40–#55 are necessary defects observed during implementation/acceptance. These 32 issues remain Open until master merge. Only fully verified issues receive individual `Fixes` lines in PR #26. Broader API documentation issue #18 receives `Refs #18` and remains open globally. + +| Issue | Actual owner | Implementation commits | Applicable local evidence | +|---|---|---|---| +| [#13](https://github.com/Wolfiora/InnoEngine/issues/13) | Editor Interactions History | `fa794a5a` | Contracts: EditorHistoryTests non-LIFO Commit/Rollback/Dispose and retained stack — local gates passed; delivery CI in attachment | +| [#16](https://github.com/Wolfiora/InnoEngine/issues/16) | SDK-evaluated Restore and process-owned loaded-package test | `564e00d0`, `4d0fdb0a` | Contracts: NuGetLoadedIdentityTests; e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios — local gates passed; delivery CI in attachment | +| [#19](https://github.com/Wolfiora/InnoEngine/issues/19) | Build SDK capture -> neutral compiler inputs -> Host/IDE | `63d4f7dc` | Contracts: frozen multi-SDK/CSharp14 inputs; Companion-Canvas; Companion-Rendering2D; Companion-FlappyBird — local gates passed; delivery CI in attachment | +| [#27](https://github.com/Wolfiora/InnoEngine/issues/27) | Approved whole-plan traceability and final acceptance | `199ae0dc` | All final functional, product, visual, performance and CI gates — local gates passed; delivery CI in attachment | +| [#28](https://github.com/Wolfiora/InnoEngine/issues/28) | Session/context scopes and actual presentation callers | `65d222ae`, `e2e6ca32`, `a25d6302` | Contracts/Visual: extension failure and next-frame scopes; Lifecycle: owner and retirement protocols; e28/export-label: prior-producer negative and corrected public provider failure — local gates passed; delivery CI in attachment | +| [#29](https://github.com/Wolfiora/InnoEngine/issues/29) | Generation-owned Interactions menu/shortcut catalog | `88df3c79`, `bc88dc04` | Contracts: fresh Query/conflict/last-good/index reuse; Lifecycle: actual generation loop; Quantitative: menus, shortcut hit/miss — local gates passed; delivery CI in attachment | +| [#30](https://github.com/Wolfiora/InnoEngine/issues/30) | Shared presentation and domain-owned styles | `63d4f7dc`, `f7dce89c` | Architecture dependencies/API; Visual: frame-consistent Zoom/DPI/control matrix; Matched original/current native UI and viewport readback — local gates passed; delivery CI in attachment | +| [#31](https://github.com/Wolfiora/InnoEngine/issues/31) | Visible-order navigation and Diagnostics occurrence views | `63d4f7dc`, `2bb4da40`, `bc88dc04` | Workflows/Visual: native Hierarchy/Browser/Console input; 10k search/range/scroll/drag/rename journeys — local gates passed; delivery CI in attachment | +| [#32](https://github.com/Wolfiora/InnoEngine/issues/32) | Neutral Graph document/controller and Graph.ImGui geometry/intents | `63d4f7dc` | Contracts: graph layout/pointer/controller; Visual: independent non-Shader native consumer/failure; Workflows: Shader document/History/Missing; Quantitative: deterministic 2k graph — local gates passed; delivery CI in attachment | +| [#33](https://github.com/Wolfiora/InnoEngine/issues/33) | EditorStatistics authority and bounded Stats Panel sampler | `b7a8924d`, `63d4f7dc` | Contracts: unchanged snapshot reuse/frame ordering; Visual: actual native trends, interval/window/reset; Products: actual Stats Panel 100 cycles; Quantitative: statistics — local gates passed; delivery CI in attachment | +| [#34](https://github.com/Wolfiora/InnoEngine/issues/34) | Visible tree/grid/Console rows and neutral graph display projection | `63d4f7dc`, `973a1aa7`, `bc88dc04` | Visual/Workflows: 10k native range/edit/drag and clipped content counts; Contracts: 2k affected-node geometry reuse; Quantitative: five paired static and input producers — local gates passed; delivery CI in attachment | +| [#35](https://github.com/Wolfiora/InnoEngine/issues/35) | Feature edits, unified History and actual Panel composition | `fa794a5a`, `a4d975ab`, `f7dce89c`, `8fd31cee`, `2bb4da40` | Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys; Contracts: History atomicity and Missing barriers; Products: eight FlappyBird journeys in six configurations — local gates passed; delivery CI in attachment | +| [#36](https://github.com/Wolfiora/InnoEngine/issues/36) | Real ImGui public input/geometry and matched content review | `d7f383f0`, `e3f02ebe`, `bc88dc04` | Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases; Final current and retained original 6000-preparation-frame capture; Physical monitor/IME/manual Mac explicitly unexecuted — local gates passed; delivery CI in attachment | +| [#37](https://github.com/Wolfiora/InnoEngine/issues/37) | Session/Panel/document/generation retirement | `d7f383f0`, `bced2aef`, `e3f02ebe` | Lifecycle: document cycles and ten success/reject/Missing/recovery reloads; Products: every discovered actual Panel 100 cycles, strict GC resource series; Host exit state/layout publication — local gates passed; delivery CI in attachment | +| [#38](https://github.com/Wolfiora/InnoEngine/issues/38) | Controlled public-host static/input/command/product measurements | `973a1aa7`, `bc88dc04`, `1917b253` | Frozen input/runtime hashes before/after; 150 five-pair original/current trial rows; Ten absolute complete-inspection processes; All regression flags require explicit review — local gates passed; delivery CI in attachment | +| [#39](https://github.com/Wolfiora/InnoEngine/issues/39) | Current per-owner API contracts, design standard and final report | `0974c317`, `163c1938`, `5e329bbe` | Final architecture/XML/Wiki/project coverage; Eleven quality dimensions and complete requirement mapping; English issue verification, individual Fixes and partial Refs #18 — local gates passed; delivery CI in attachment | +| [#40](https://github.com/Wolfiora/InnoEngine/issues/40) | Compiler-tool graph and SDK-owned output identity | `6a656b67`, `8dcceb94`, `870a0af0` | Contracts: CompilerToolBuildScope/GeneratorRestoreScope/owned Task inputs; Repeated official source, Editor and isolated output builds; Final Windows/ARM64 CI — local gates passed; delivery CI in attachment | +| [#41](https://github.com/Wolfiora/InnoEngine/issues/41) | Tooling consumes current SDK-evaluated source/compiled output | `14338d61`, `e053be5b`, `a38437f7` | Contracts: public API graph default/independent/stale output cases; Final real complete architecture/XML/Wiki validation — local gates passed; delivery CI in attachment | +| [#42](https://github.com/Wolfiora/InnoEngine/issues/42) | Scene-owned root placement indices | `63d4f7dc` | Scene: creation/reorder/parent/transfer/failure indices; Quantitative: five paired 10k Hierarchy producers; Original profile retains repeated IndexOf evidence — local gates passed; delivery CI in attachment | +| [#43](https://github.com/Wolfiora/InnoEngine/issues/43) | Asset source reconciliation | `273c42ad` | Workflows: stable source IDs through atomic refresh/recovery; Products: Scene/Asset save/close/reopen and installed sources — local gates passed; delivery CI in attachment | +| [#44](https://github.com/Wolfiora/InnoEngine/issues/44) | Asset source content verification | `e23f4ba5` | Workflows: same-length/same-mtime external content mutation; Source content/semantic identity and read-only source regressions — local gates passed; delivery CI in attachment | +| [#45](https://github.com/Wolfiora/InnoEngine/issues/45) | Existing Core Writer/member capture and encoding | `f7dce89c`, `6df06585` | Core Serialization: exact single-member/context/converter/failure semantics; Quantitative: five paired 10k/2k workloads and complete product — local gates passed; delivery CI in attachment | +| [#46](https://github.com/Wolfiora/InnoEngine/issues/46) | Scene-owned complete capture inputs and Core canonical output | `0b0a821c`, `2434cd96`, `1917b253` | Core97/Scene67 and complete Workflows; Ten independent idle/edit absolute inspection processes; Failed earlier tails and corrected focused ten-process proof retained — local gates passed; delivery CI in attachment | +| [#47](https://github.com/Wolfiora/InnoEngine/issues/47) | Standard Hosting layout resource, passive Core state document | `bb5b4bb9` | Contracts/Visual: absent/custom/empty/docking/resizing; Core IO concurrent creators and final ARM64 CI; Default layout focused report — local gates passed; delivery CI in attachment | +| [#48](https://github.com/Wolfiora/InnoEngine/issues/48) | Core IO atomic create-only publisher and compatible document reader | `318e30aa`, `98fd5a7d` | Core IO38 and ten independent publication/held-handle process repeats; Final Windows/ARM64 concurrency suites; Original deterministic reader-negative evidence retained — local gates passed; delivery CI in attachment | +| [#49](https://github.com/Wolfiora/InnoEngine/issues/49) | Existing native Context/atlas supplemental font lifetime | `e3f02ebe`, `23f0ceb2`, `397ae3ed` | Native font four-face/scale/CJK/Latin/icon/missing/corrupt cases; Products and matched visual content; Source bytes16,437,364, official Noto2.004/OFL unchanged, one pinned source per Context — local gates passed; delivery CI in attachment | +| [#50](https://github.com/Wolfiora/InnoEngine/issues/50) | Scene Workspace assigned source and existing recovery transaction | `8fd31cee` | Workflows: clean/edited source removal, pipeline reconciliation, exact restoration/rename; Save rejects unavailable assignment, explicit SaveToDirectory establishes new source — local gates passed; delivery CI in attachment | +| [#51](https://github.com/Wolfiora/InnoEngine/issues/51) | Application/context native retirement registration | `e3f02ebe` | Lifecycle: ten contexts collected while another remains active; Pending/failure preserves registration, successful direct Dispose/replacement; Native application shutdown — local gates passed; delivery CI in attachment | +| [#52](https://github.com/Wolfiora/InnoEngine/issues/52) | Core IO reserved publication name and frozen shader compiler workspace | `329568ba` | Contracts: ShaderBuildInputsTests inventory/content/empty directory/failure cases; e52: simultaneous independent SDK outputs with deliberately held candidate; Authored inventory/hash unchanged; final complete builds/CI — local gates passed; delivery CI in attachment | +| [#53](https://github.com/Wolfiora/InnoEngine/issues/53) | Core immutable snapshot/Writer proof, Scene complete neutral row inputs | `be44532f`, `2434cd96`, `1917b253` | Core97: callback retirement/thread/metadata/identity/converter/effect/mutation/GC guards; Scene67: complete writes/restore/references/Missing/Prefab and removed-System GC; Ten complete-inspection processes, architecture/Wiki/products/CI — local gates passed; delivery CI in attachment | +| [#54](https://github.com/Wolfiora/InnoEngine/issues/54) | Scene property reference scope through exact Core allocator | `e5268920`, `163c1938` | Scene67: current/stale/foreign/wrong-kind/alias/throw/nested scopes and canonical graph behavior; Actual SceneEdits History continuous-input comparison; Formal companion SDK/IDE and complete products/CI — local gates passed; delivery CI in attachment | +| [#55](https://github.com/Wolfiora/InnoEngine/issues/55) | Core atomic directory publication and Compiler-owned complete cache trees | `dd5979c6` | Public Compiler red/green temporary-reader regression; persistent failure, cancellation and exact whole-tree replacement preservation; Core IO41 and Compiler17 complete focused suites; five independent cold/warm formal publications; Corrected-source full matrices, formal companions, products, quantitative results and exact-head CI — local gates passed; delivery CI in attachment | + +## Final execution results + +- Eleven source/tool/test/product preparation stages and fourteen complete profile/companion/product/capture stages passed at the frozen production candidate. +- Contracts 822 pass +2 Unix-only skips on Windows; Workflows704, Visual524, Lifecycle15 pass. Profiles overlap and are not summed into a distinct-test count. Core Serialization97, Scene67 and CoreIO41 pass. +- Actual Windows Editor Debug/Release × D3D11/D3D12/Vulkan complete smoke, eight real feature journeys and nine actual Panel types ×100 open/close cycles. Scene and Shader documents ×100 and ten generation reloads retain strict recovery/ALC requirements. +- Architecture/XML/Wiki:241/241 project ownership evaluations,34,582 API entries,197/197 actually compiled examples. Three foreign product API compilations are outside this local selected Windows target, not fabricated successes. +- Original producer b303d9f1 is runnable with recorded test-only adapters and matching gameplay/assets/layout. Five alternating independent pairs produce150 initial trial rows. All16,406 runtime/input files match before/after. +- The initial2k graph P95/max flag is retained. A predeclared additional five pairs using the unchanged public fixture's normal five repetitions yields50 frames/process; no pair exceeds+10%P95. All outliers and initial data remain. This is not a stable60FPS guarantee. +- Ten independent complete dirty-inspection processes satisfy original median<16ms/P95<25ms/<8MiB budgets without changing100ms policy. WorstP95 is24.5695ms, with limited margin. +- Five fresh formal cold/warm publications pass. The first external observer setup failure remains: cold first-import byte-identical metadata timestamp writes are distinguished from strict warm source/cache invariants. No production validation is weakened. + +## Boundaries reviewed + +Actual Core Events/Identity/IO/Serialization and the existing History/TypeRegistry remain authoritative. Graph presentation is neutral; domain controllers own edits; Panels compose features. SDK facts enter Compiler through immutable neutral inputs. Core owns atomic publication and rollback. No BGCS/vendor/package/generated-binding change, parallel foundational manager, global/implicit import, legacy reader, IVT/private reflection or test production API is introduced. + +The [actual file tree](acceptance/editor-ui-2026-10-30/file-tree.md), [deep ownership review](acceptance/editor-ui-2026-10-30/ownership-review.md), necessary public API semantics and eleven individual quality judgments are part of the final report. A directory tree alone is not architecture evidence. + +## Delivery and remaining external boundaries + +The only remaining delivery gate at report-authoring time is the exact containing commit's CI/issue verification/Ready transition, tracked in the attachment. It must complete before Ready. No automatic merge. After the user merges, verify the actual issue-close results; that conditional post-merge check cannot be executed before authorization. + +Physical displays, OS IME, subjective usability and manual Mac interaction remain unexecuted; automated native contexts/simulated metrics and exact-head ARM64 CI are separately scoped. Independent engine issues#14/#15/#17/#20/#21/#22 and the broader#18 are not closed by this UI work. Five previously policy-rejected diagnostic runtimes remain untouched; cleanup never deletes global SDK/NuGet or user content. Windows lacks the specified Glass.aiff; silent operation is respected. + +Commit author and committer are Aaron Liao , English type(scope):description, no co-author. PR#26 targets master; never create a replacement PR or auto-merge. diff --git a/records/architecture/EDITOR_UI_IMPLEMENTATION_PLAN.md b/records/architecture/EDITOR_UI_IMPLEMENTATION_PLAN.md new file mode 100644 index 000000000..a13cd927e --- /dev/null +++ b/records/architecture/EDITOR_UI_IMPLEMENTATION_PLAN.md @@ -0,0 +1,31 @@ +# Editor UI implementation ledger + +The approved implementation and closure work is implemented and locally verified on production candidate `dd5979c613b0fe8ebd9f7026f25001ceee9f8cee`, branch `editor-ui`. This current ledger supersedes the intermediate partial-implementation labels. The [complete report](acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md), [issue ledger](EDITOR_UI_CLOSURE_PLAN.md) and [requirement matrix](acceptance/editor-ui-2026-10-30/original-plan-requirements.csv) provide actual scope and evidence. + +Final delivery-head CI, English issue verification and PR Ready are separately recorded in the [exact-head attachment](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338). Local verification is not substituted for that gate. Issues stay Open until PR#26 merges to master; no automatic merge is authorized. + +## Implemented outcomes + +| Responsibility | Current behavior and proof | +|---|---| +| History | Stack validation precedes completion; continuous gestures publish immediately into one History; discrete operations, cancellation, rollback failure and Missing barriers preserve their contracts. | +| Domain ownership | Asset and Scene operations belong to features/SceneEdits; Panels do not reference each other or own parallel domain transactions. An unreachable direct Hierarchy deletion path is removed. | +| Session and resources | One Session per interactive Host; frame/window/thread/context borrows; state and callbacks retire with owner/generation; successful native context retirement unregisters precisely its application entry. | +| Controls and styles | Shared controls, domain-owned Assets/Shader presentation,2:3 forms, bounded selectors, one scroll owner and frame-boundary Zoom/DPI. | +| Selection and commands | Core identities, neutral/weak deferred targets, visible-order keyboard/range navigation, fresh dynamic Action state and generation-owned static indexes. | +| Generic Graph | Neutral geometry/ports/intents shared by Shader and an independent non-Shader consumer; domain controllers retain History/semantics. | +| Console and Stats | Existing diagnostic occurrence filtering with Unicode search; existing statistics authority and immutable snapshots; Panel-owned bounded numeric trends. | +| Large views |10k lists/trees/grid visible work and preserved edit/drag/selection;2k graph equivalent geometry reuse; actual CPU/allocation/count measurements. | +| SDK and scripts | Actual selected SDK references/symbols/language are frozen once, mapped to neutral compiler inputs and shared by IDE/runtime. Compiler publication reuses Core atomic transactions. | +| Layout and current data | Standard Host initializes absent readable editor.ini atomically; custom/empty files survive; state restores before capture; final exit captures current Module/Panel/layout without persisting selection/Undo/runtime state. | +| Foundation reuse | Core Events/Identity/IO/Serialization and one History remain authoritative; no parallel managers or compatibility protocols. | + +## Verification + +Eleven preparation and fourteen complete matrix stages pass. Contracts822pass/2Windows-inapplicableUnixskips, Workflows704, Visual524, Lifecycle15; Core97/67/41. Counts overlap. Six Windows product configurations, real interface journeys,100cycles/type, ten reloads, native scale/input and matching content images are recorded. Wiki/project/public API gate evaluates241projects and compiles197examples. + +The original production baseline b303d9f1 is reconstructed with matching gameplay/assets/layout, not a legacy production reader.150initial paired trials and ten additional graph-tail process trials retain every outlier. Absolute complete-inspection budgets pass without reduced validation. Initial failures, observer mistakes, performance flags and failed setup attempts remain part of the evidence. + +## Completion review + +Every original requirement has an issue/owner/commit/command/evidence mapping, including ten interface journeys that a first text extractor omitted. Thirty real Editor projects appear in the actual file tree. Eleven quality dimensions are evaluated against explicit engineering criteria with limits. Physical displays, OS IME, manual Mac experience and unrelated engine issues are not certified. The final attachment resolves delivery/CI conditions before PR Ready; a future user-authorized merge remains separate. diff --git a/records/architecture/acceptance/ATOMIC_FILE_PUBLICATION_ACCEPTANCE_2026_10_29.md b/records/architecture/acceptance/ATOMIC_FILE_PUBLICATION_ACCEPTANCE_2026_10_29.md new file mode 100644 index 000000000..b44c18842 --- /dev/null +++ b/records/architecture/acceptance/ATOMIC_FILE_PUBLICATION_ACCEPTANCE_2026_10_29.md @@ -0,0 +1,44 @@ +# Atomic create-only file publication + +Tracking: [#48](https://github.com/Wolfiora/InnoEngine/issues/48), [#47](https://github.com/Wolfiora/InnoEngine/issues/47), [#40](https://github.com/Wolfiora/InnoEngine/issues/40), [parent #27](https://github.com/Wolfiora/InnoEngine/issues/27). + +## Confirmed failures + +At `bb5b4bb9`, the complete Windows and macOS CI suites rejected the output-owner fixture's hard-coded `bin/Fixture/debug` path. The SDK builds succeeded. Product-aware `ArtifactsProjectName` now participates in the actual output owner, so the fixture must inspect SDK outputs rather than restore an obsolete path convention. + +The same macOS ARM64 run reproduced a separate production defect: competing new-project layout initializers observed different winning documents. Core `AtomicFile` delegated create-only installation to `File.Move(..., overwrite: false)`. The exact [.NET 10.0.13 Unix implementation](https://github.com/dotnet/runtime/blob/v10.0.13/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs#L193-L214) can check destination absence and then perform a replacing rename. That sequence cannot satisfy Core's stronger atomic no-clobber promise. + +This is not a BGCS failure. No SDK, package, BGCS, generated binding or vendor file was modified. + +## Correct ownership and implementation + +- Core IO retains its existing portable atomic publication responsibility. Editor, asset creation and document consumers all receive the correction through the same API. +- Windows retains no-replace file movement. Other hosts must provide the public POSIX `link` filesystem primitive: a complete same-directory candidate gains its destination name atomically, then the candidate name retires. +- Native host details remain private to Core IO. There is no engine platform ID, backend, target, product routing table, new provider registry or Editor-specific lock manager. The existing platform ownership rule already distinguishes portable Core IO primitives from product policy. +- An existing file, empty file, directory or competing creator cannot be replaced in create-only mode. Losing `Install` candidates remain caller-owned. `WriteAllBytes` owns its staging file and cleans it on exit. +- Missing host primitives fail with `PlatformNotSupportedException` and their original cause. Unsupported filesystem operations fail with `IOException`; there is no copy fallback exposing partial destination content. +- Failure to retire a candidate after successful linking explicitly reports that the complete target has already been published. Parent and candidate ownership remain the caller's responsibility. +- The SDK fixture now reads `TargetPath`, `ProjectAssetsFile` and the actual `IntermediateAssembly` item through the SDK's structured result output. It checks existence, containment and disjoint owners while retaining two concurrent real SDK builds. + +## Verification + +Evidence owner: `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. + +| Gate | Result | Evidence and limits | +| --- | --- | --- | +| Core IO, Windows Release | 31 passed | `e48/io-publication-capability.trx`; final capability-based correction, no analyzer suppression | +| Simultaneous candidates | Passed within Core IO suite | 12 rounds, 16 independent publishing threads per round, exactly one complete winner, losing candidates preserved | +| Independent processes and reader | Passed within Core IO suite | Eight actual .NET publishing processes, public atomic API, 1 MiB distinct payloads, complete repeated reads, exactly one success, no staging leftovers | +| Existing owners and replacement | Passed within Core IO suite | Empty/custom destination, directory collision, normal replacement, parent boundary and existing filesystem safety contracts | +| Actual SDK output-owner fixture | Passed, Windows Release | `e40/sdk-owner-executed.trx`; two concurrent real SDK builds, actual output/assets/intermediate files, containment and disjoint owners | +| Real new-project layout concurrency | Pending final candidate | Earlier Windows passes do not substitute for the final source or actual ARM64 CI | +| macOS ARM64 | Pending exact-head CI | No local Mac; the observed red CI result is retained and will only be superseded by a real successful remote job | +| Architecture / Wiki | Pending final candidate | An intermediate explicit Browser check was rejected by the existing shared-layer policy and removed; capability absence now comes from the actual native boundary without a product-platform branch or validator exception | + +An intermediate Task runtime build correctly rejected changed Core IO source during compilation. That failed preparation was not cached. The source is frozen for the replacement build; no input check or publication guarantee was relaxed. + +Two SDK fixture preparation failures are retained: the first omitted the structured-result parent directory; the second omitted the explicit Build target, so SDK get* switches performed evaluation only. The final request explicitly executes Build and reads its resulting values. Neither attempt is counted as a successful build or a production defect. + +## Deep self-review + +The fix acts at the authoritative filesystem operation, not at the Editor symptom. It does not introduce a second IO/lease protocol, weaken atomic visibility, guess package/output locations or expand the public API. Independent process contention exercises a real public boundary. The Unix implementation still requires actual ARM64 CI; Windows success cannot certify it. Issues remain Open until complete verification and merge through PR #26. diff --git a/records/architecture/acceptance/EDITOR_DEFAULT_LAYOUT_ACCEPTANCE_2026_10_29.md b/records/architecture/acceptance/EDITOR_DEFAULT_LAYOUT_ACCEPTANCE_2026_10_29.md new file mode 100644 index 000000000..7746d18bd --- /dev/null +++ b/records/architecture/acceptance/EDITOR_DEFAULT_LAYOUT_ACCEPTANCE_2026_10_29.md @@ -0,0 +1,65 @@ +# 新项目默认 Editor 布局验收 + +[Issue #47](https://github.com/Wolfiora/InnoEngine/issues/47) · [PR #26](https://github.com/Wolfiora/InnoEngine/pull/26) · [执行台账](../EDITOR_UI_CLOSURE_PLAN.md) · [Host Wiki](../../../docs/editor/Inno.Editor.Hosting.md) + +## 结果与范围 + +本地专项验收通过。新项目首次打开时,标准 Host 为不存在的 `editor.ini` 原子安装默认文档,然后再加载 presentation。布局采用当前 FlappyBird 的通用停靠比例;原始样例文件 SHA-256 为 `a62d29ad965db98e7ba1ebab357b5444b58633530922daa9a82db0c992758cca`,未修改该文件。 + +左上 Scene/Game 并排、左下 File、中间 Hierarchy、右上 Inspector/Shader Editor 标签、右下 Stats/Console 标签。模板不包含场景、Asset ID、Plugin 窗口、导航状态、Selection、History、编译或导出弹窗;屏幕绝对位置不从样例复制。现有布局(包括刻意留空的文件)在初始化时保持原样;正常使用后的统一状态保存仍会记录最新布局。 + +本项不代表完整 UI 原计划已经闭合。PR 保持 Draft;最终提交的远端 CI 独立报告,issue 合并时才关闭。 + +## 实际职责与文件树 + +```text +src/composition/editor/ +├── framework/Inno.Editor.Core/ +│ ├── Runtime/EditorContext.cs +│ ├── Runtime/EditorLayoutSettings.cs +│ └── Inno.Editor.Core.csproj +└── hosting/Inno.Editor.Hosting/ + ├── Layout/ + │ ├── DefaultEditorLayout.ini + │ └── EditorDefaultLayout.cs + ├── Hosting/EditorHost.cs + └── Inno.Editor.Hosting.csproj + +tests/editor/Inno.Editor.PlayMode.Tests/ +├── EditorLayoutInitializationTests.cs +└── DefaultLayoutNativeTests.cs +``` + +Host 拥有默认窗口与布局数据,Core 不知道标准面板或示例项目。唯一新增公开边界是现有 Host-only `EditorContext` 构造入口的可选 `initialLayoutDocument` 参数;它传递中立文档,不进入 Scripting API,也不要求项目作者填写配置。保留一个构造实现,没有旧 overload 或转发层。 + +首次创建和普通保存都复用 `Inno.Core.IO.AtomicFile`,删除了原先私有固定 `.tmp` 写入协议。首次安装使用 `overwrite: false`,并发创建后读取实际完成者的文档;真实 IO 失败传播,不发布半成品。Core IO 引用属于实现依赖,没有泄漏到公开签名。 + +## 本地验收 + +证据根为 `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27/e47`。本节记录基于 `870a0af0` 的工作区候选,源码/资源哈希保存在 `source-inputs.json`;提交后以 PR 的实际 head 与 CI 为准。 + +| 检查 | 结果与证据 | +| --- | --- | +| 新增专项 | 9 项通过,`initial-layout.trx`:缺失创建、已有/空文件保护、16 路并发、失败清理、保存重开、无默认值时的被动 Context,以及两组真实 Native docking/resize。 | +| PlayMode 相关套件 | 150 项通过,`layout-related-suite.trx`;包含新增 9 项,不能再次相加。 | +| Interactions | 131 项通过,`Inno.Editor.Interactions.Tests.trx`;覆盖现有状态、History 与设置工作流。 | +| Hosting | 10 项通过,`Inno.Editor.Hosting.Tests.trx`;预取消和配置拒绝继续不创建项目。 | +| 标准 Editor Debug | 构建通过,0 编译错误;`layout-product-build.log/binlog`。保留 4 个 NU1900 网络/漏洞服务警告。 | +| 完整 SourceDebug Solution | 构建通过,0 编译错误;`layout-source-solution.log/binlog`,同样保留网络警告。 | +| 当前 Player 引用闭包 | 正式项目构建通过;`wiki-player-reference-build.log/binlog`。不借用 Editor 或默认目录的引用。 | +| 架构/XML/Wiki/示例 | 最终通过,241/241 SDK 工程归属;`wiki-final-gate.log`、`wiki-final/api.json`、`wiki-final/examples.json`。 | +| 实际空项目首开 | 隐藏窗口 D3D12 Host 完成 300 帧并有序退出;`first-run-hidden-result.json` 与 stdout/stderr。自动生成的 INI 含八个实际 docked 窗口,没有示例或 Untitled 场景。 | + +Native 几何测试通过真实 `ImGuiEditorRuntime` 加载标准 dock tree,再从 1600×900 调整到 1280×720、800×600,检查窗口边界、列比例、上下关系和两个标签组。它没有创建桌面操作或冒充物理显示器视觉验证。实际隐藏 Host 进一步证明嵌入资源进入了正式产品初始化流程;隐藏运行不证明人工视觉体验。 + +首轮 Wiki 门禁发现新构造签名映射、API 页中的外部样例引用,以及独立输出 owner 缺少选定 Player 闭包,共五项诊断。签名按 SDK 清单更新,样例出处留在本验收记录,Player 按正式工程构建。最终门禁全部通过,没有忽略或降低校验规则。 + +## 深层自查 + +- 默认值归组合 owner,Core 只接收不可变字符串并管理同一可读文档;没有平台名单、渲染 API 分支或领域类型反向依赖。 +- 原子创建、失败清理、保存和竞态复用 Core IO;没有平行 Writer、事件系统、身份表、Workspace 或格式兼容机制。 +- 首次初始化发生在 presentation 读取之前;已有文档的读取权威、模块恢复与退出原子保存顺序不变。没有隐式创建 Scene。 +- 未修改 BGCS、NuGet 包、第三方或生成绑定,没有新增 global/implicit using、IVT、私有反射或生产测试入口。 +- 用户停止电脑操作后未再使用 computer use;本专项通过原生 Context 测试与隐藏 Host 验收,不操作桌面焦点。 + +指定的 `/System/Library/Sounds/Glass.aiff` 在本 Windows 环境不可用,未播放。 diff --git a/records/architecture/acceptance/EDITOR_UI_ACCEPTANCE_2026_10_27.md b/records/architecture/acceptance/EDITOR_UI_ACCEPTANCE_2026_10_27.md new file mode 100644 index 000000000..00d47fc35 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_ACCEPTANCE_2026_10_27.md @@ -0,0 +1,128 @@ +# Editor UI acceptance — 2026-10-27 + +[Current acceptance](../../../docs/architecture/CURRENT_ACCEPTANCE.md) · [Architecture](../../../docs/architecture/EDITOR_UI_ARCHITECTURE.md) · [Implementation ledger](../EDITOR_UI_IMPLEMENTATION_PLAN.md) + +## Scope and conclusion + +This report covers the approved Editor UI refactor on `editor-ui`, starting at `b303d9f10cbaf5a905f7cb529e52cf8293b0d8bd`, together with the Rendering2D authoring caller updates and the officially exported FlappyBird package. It does not certify the entire engine, every platform or every interaction as perfect. + +The implemented subset passed its local correctness checks, including the final SDK Wiki gate. This is not completion of every requirement in the approved plan. The [plan-by-plan review](EDITOR_UI_PLAN_REVIEW_2026_10_27.md) supersedes the earlier completion assessment: menu indexing, generic graph presentation, Console/Stats workflows and the complete scale/lifecycle acceptance matrix remain incomplete. The results below retain their original scope. + +Remote CI subsequently passed all six checks at `2dc37114b7c9b5137e866efc057a66e0dfd72046`. The review records exact Windows/macOS outcomes and distinguishes those passes from plan requirements that CI does not exercise. PR #26 remains a draft pending that work. + +## Implemented boundaries and behavior + +- Headless Assets, Shaders, Rendering, Scripting and Exporting features own domain operations, drafts, compilation and export lifetime. ImGui presentation owns windows, controls and transient state. Panels do not reference other Panels. +- Reusable asset editing and its neutral History protocols belong to `Inno.Editor.Assets`; Browser presentation belongs to `Inno.Editor.Assets.ImGui`. Shader presentation belongs to `Inno.Editor.Shaders.ImGui`. Generic graph coordinates, curves and hit tests belong to `Inno.Editor.Graph.ImGui`. +- `EditorUiSession` owns controls, styles and per-owner transient data. Native pointers, generated binding types and native option enums stay inside the ImGui adapter and product hosting boundary. Draw-list borrows reject use outside their frame/window/context. +- Continuous input uses `EditorEditGesture` over the existing History transaction. Each sample records immediately. Release commits, Escape rolls back, and commands or document operations finish the gesture before their own transaction. Failed completion preserves ownership. +- Ordered selection resolves Core identities, retains Missing references and supports anchors/ranges/toggles. Batch asset and scene edits preflight the whole selection and use the existing atomic History protocols. Mixed vector edits affect only the selected scalar component. +- Menus, shortcuts, toolbars and command search use the same Action availability and execution path. Deferred targets resolve Core identities instead of retaining live scene objects. +- Temporary Play identity domains have a Host routing lease over the existing Core allocator. Selection, focus, queued actions and drag/drop use the same routing; Edit selection is restored before retiring the domain. No second object registry was created. +- Play Mode notifications and settings/UI lifecycle notifications use Core Events with explicit subscription ownership. No parallel event dispatcher implementation, Undo stack, object identity registry or persistence format was added. +- Rendering2D uses the current session and edit contract. Its package in FlappyBird was produced by the official exporter, never edited as an archive. + +- Pure identifier formatting moved out of session-bound widgets into `EditorDisplayNames` in Editor Core. Menu metadata can be queried without a native UI context. The old widget member was removed and every engine/plugin caller updated. +- Non-identity targets retain only known neutral scalars strongly. Boxed host structs can contain extension objects, so selection, focus and snapshots hold those targets weakly. A failing GC regression reproduced the retention before the correction; the complete 121-case Interactions suite passed afterward. +- The script SDK references reusable Assets, Shaders and Graph API owners directly. Hierarchy and Inspector remain direct SDK references for their own four public drop-target contracts. A fresh SDK output and a real EditorScripts probe verify these exports; no Panel is used only to carry another module's API. + +The full current file tree and execution flow are in the architecture document. Solution registration, project reference visibility, scripting exports, English XML and individual Editor Wiki pages were updated together. + +## Verification + +Evidence owner: `D:/InnoEngine-Verification/editor-ui-2026-10-27`. + +| Check | Final result | Evidence | +| --- | --- | --- | +| Windows UI source build | Passed; zero compiler warnings/errors | `ui-boxed-build.log` and binlog | +| Shared UI contracts | 558 passed; 0 failed/skipped | `boxed-suite/` TRX, with its earlier 150 architecture cases replaced by the final 152 in `architecture-sdk-owner-tests/` | +| Isolated contracts | 4 passed; 0 failed/skipped | One native retirement case in `boxed-suite/`; three unchanged Core/compiler/build cases in `isolation-final-tests/` | +| Rendering2D authoring | 42 passed, 0 failed/skipped against the fresh SDK output | `rendering2d-sdk-owner-tests/results/authoring.trx` | +| Export shutdown integration | Passed three consecutive runs; counted as one distinct case | `ci-export-retirement/export-retirement.trx`, `ci-export-retirement-repeat/` | +| Official package export | Passed | `rendering2d-export-accepted.log` | +| Architecture and public XML | Passed; final Wiki graph also checks evaluated ownership | `architecture-final.log`, `xml-domain.log`; new public source compiles with XML warnings as errors | +| SDK Wiki graph, signatures, links and examples | Passed; 241 project ownership evaluations, 34,493 API entries and 197 examples | `wiki-sdk-owner.log`, `wiki-sdk-owner-api.json`, `wiki-sdk-owner-examples.json` | +| Editor Debug and Release | Passed; zero warnings/errors | `editor-Debug-boxed.*`, `editor-Release-boxed.*`; earlier maintenance deferrals and cold vendor warnings remain in their original logs | +| Fresh formal SDK and public drop contracts | Passed; no old output directory required | `sdk-actual-owners.*`, `build-entry-sdk-owners.*`, `sdk-api-probe-after.log` | +| FlappyBird product | Debug: 15,000 frames; Release: 5,000 frames; both complete 300 Play frames and return to Edit. D3D12 and Vulkan: 1,000 renderer frames each; all orderly exits | `flappy-Debug-boxed*`, `flappy-Release-boxed*`, `flappy-Debug-d3d12.log`, `flappy-Debug-vulkan.log` | +| GitHub CI | Tracked separately on the pull request for this branch | Its exact head revision, run links and final results accompany delivery; this report does not claim an unexecuted remote pass | + +Initial failures remain in the evidence owner: the wrong native product context, a missing direct test annotation dependency, fixtures missing the new session/presentation boundary, an EventHub whose dispatcher was not retained, and boxed target retention (`boxed-before/`). The SDK probe also rejected an exploratory removal of actual Panel contract owners (`sdk-api-probe-before.log`); their required direct references were restored before delivery. Their corrective runs passed; these failures are not labeled as upstream BGCS defects. The full SourceDebug run interrupted during contract changes is not counted as a pass. The 605 distinct local passes are 558 shared contracts, four isolated contracts, 42 Rendering2D cases and the export shutdown integration case; focused reruns are not added again. + +## Performance and resource ownership + +The isolated UTF-8 benchmark uses .NET SDK 10.0.402, Release, the same text/input, 1,000 warm-up operations and seven samples of 10,000 operations. Hardware: AMD Ryzen 9 6900HX, 8 cores / 16 logical processors. Results are medians for the encoding buffer operation, not whole-editor frame performance. + +| Buffer capacity | Previous allocation/op | Current allocation/op | Previous time/10,000 | Current time/10,000 | +| --- | --- | --- | --- | --- | +| 256 | 328 B | 0 B | 1.158 ms | 0.440 ms | +| 1,024 | 1,096 B | 0 B | 1.472 ms | 0.588 ms | +| 4,096 | 4,168 B | 0 B | 5.853 ms | 1.773 ms | + +This gain comes from the implementation's stack/pooled buffer and span encoding. It is not attributed to a runtime upgrade. Native unchanged-input allocation checks are separate evidence. No claim is made about long-duration frame/GC performance or a whole-editor speedup. + +Native preparation recorded `processes=0`, `bindingGenerations=0` and `materializedBytes=0` on fixed inputs. Its complete input hashing still has a substantial cost; this refactor does not claim to solve the separately tracked native build performance work. + +## Deep self-review + +The review follows real call chains, not only folders: input → widget/session → Action or feature operation → owner serialization context → shared History → reload-safe handler. It also covers dispatcher ownership, observer release, failed rollback, per-axis batch changes, deferred identity resolution, session/owner retirement and native borrowed memory. Existing Core Events, Identity, Serialization and IO remain authoritative. + +BGCS is pinned to 4.1.2. The directly restored SDK package payloads matched their original NuGet archives; the BGCS repository remains at its baseline with no changes. No backend/platform/native/generated source changes were made by this refactor. A direct SDK payload comparison covered 122 files with zero mismatches. No global/implicit using, IVT, private reflection access, migration reader, compatibility overload or placeholder project was introduced. + +## Individual quality assessment + +These assessments describe verified engineering properties. “Enterprise-grade” and “phenomenal” are not automated test results. + +| Dimension | Assessment | Evidence and remaining improvement | +| --- | --- | --- | +| Architecture | Enforceable ownership; local architecture passed | Headless feature boundaries and no Panel-to-Panel dependencies are validator rules. ShaderEditor remains a substantial domain coordinator. | +| Code organization | Improved | Feature-oriented files, project namespaces and visible dependency groups. Further shorten the largest domain-specific canvas workflows when a concrete reusable boundary emerges. | +| State/lifetime cleanliness | Explicit ownership; local lifecycle tests passed | Explicit session/owner, subscriptions, identities and borrow guards. Continue covering future extension failure paths with the same ownership rules. | +| Abstraction | Purposeful, bounded | Domain mechanisms are separated from ImGui adaptation. This is not a promise that replacing the entire UI backend requires no presentation work. | +| Reuse | Core capabilities reused | Events, Identity, Serialization, IO, Graph and History remain shared mechanisms. No replacement infrastructure was added. | +| Visual consistency | Shared foundation; complete visual certification unavailable | Common metrics, fonts, icons, rows, mixed states and feedback; the existing dark ImGui identity is preserved. Full keyboard/DPI/accessibility review remains unexecuted. | +| Extensibility | Improved and guarded | Existing Registry/TypeCache and generation transactions remain the extension boundary; plugins use the current session and neutral operation contracts. | +| Interaction logic | Improved; product/manual limits apply | Ordered multi-selection, common properties, precise axes and batch preflight. Long real-user keyboard/drag/focus journeys remain a future acceptance exercise. | +| Undo/Redo | Strengthened; local tests passed | One authoritative History, immediate samples, rejected LIFO preservation, atomic batches and Missing barriers. No second undo stack. | +| Menu clarity | More consistent | One Action model across search/menu/shortcut/toolbar; enabled/checked rules are shared. Label taxonomy can still benefit from user observation. | +| Component granularity | Improved without empty abstractions | Assets/Shader/Graph presentation assemblies have real consumers. The largest ShaderEditor view remains an honest refinement opportunity. | + +## Unexecuted or limited acceptance + +- No local macOS device is available; local Metal and interactive macOS acceptance are unexecuted. Remote runner results are tracked separately and do not certify manual device journeys. +- Computer Use captured the real FlappyBird Scene/Game view, but window activation failed twice. Following the tool's recovery limit, further manual input was stopped. Mouse interaction was not certified; the owned visual session was terminated. Separate bounded runs verify orderly exit. +- Full accessibility, all focus/DPI combinations, audible audio quality, extended usability sessions and long-duration GPU/GC behavior are not certified by native layout tests or bounded smoke runs. +- Existing engine issues outside this UI plan remain independently tracked. This refactor cannot establish that the entire engine is perfect. + +The configured macOS `Glass.aiff` is unavailable on this Windows host; the completion sound cannot be played. + + +## Necessary public API changes + +- `EditorUiSession`, its controls, pointer-free options/font/input/layout values and guarded `EditorDrawList` express explicit host/session/frame ownership without generated native types in consumer APIs. +- `EditorSelectionSnapshot`, ordered selection operations and `EditorEditGesture` provide shared selection and continuous History boundaries. The inspection edit contract carries an optional scalar component and merge key so batch edits preserve untouched values. +- Asset editing, export ownership and generic graph geometry now have reusable feature/presentation entry points. Existing registries remain their discovery mechanism. +- `RegisterIdentityDomain` extends the Host selection coordinator for temporary runtime worlds. The allocator remains Core-owned; callers retire the routing lease on the owner thread after restoring Edit selection. This coordinator is Host-only and is no longer a project scripting export. Scripts use `EditorInteractions` without registering domains. +- `EditorDisplayNames.Nicify` provides pure label formatting for menu metadata, Inspector and plugins. The session-bound widget method was removed. +- Play Mode and settings notifications return disposable Core Events subscriptions. The Console releases its subscription explicitly. + +## Verification entry and retained diagnostics + +VSTest assembly discovery derives `DOTNET_ROOT` from the selected `DOTNET_HOST_PATH`. Passing both as command-line environment settings caused a duplicate-key discovery exception with a zero exit code in this SDK. The shared runner now supplies the supported host input without the duplicate root, preserves discovery output and treats stderr discovery errors as failures. Four isolated facts were actually executed, rather than inferred from a zero exit code. + +One attempted Build-entry rebuild overlapped a running Wiki CLI and encountered the ordinary Windows lock on that executing CLI DLL. Rebuilding after the owned process exited passed; no production copy/loading workaround was added. + +Explicit owner-aware cache maintenance reclaimed 20,782,799,012 logical bytes and protected 174,384,757 bytes while active verification was running. C: free space rose from about 92.5 to 95.5 GiB; logical bytes and physical volume recovery are different measurements. SDK and NuGet global caches were not cleaned. + +The full local SourceDebug build interrupted during contract changes remains uncounted. The final focused UI graph, three additional isolation projects and standard Debug/Release product entries passed; the complete SourceDebug matrix belongs to remote CI. No test filter excludes an unresolved UI failure. + +The first complete remote matrix at `61f855c0` passed both platforms' Debug/Release products, renderer smoke, BGCS identity, ten hot builds, Solution, architecture, Support Pack and Wiki. Its sole failing fact on both platforms was `EditorShutdownDrainsSupportPackCleanupBeforeReleasingExportServices`: the Build fixture drew a modal without the new UI session and still selected an arbitrary module rather than the export service. The fixture now explicitly composes both owners, enters the session, resolves `EditorExportService` and retries the existing Pending retirement protocol after releasing the fake cleanup barrier. All original UI, cancellation, release and generation-readiness assertions remain. The production implementation and BGCS were not changed for this correction. The replacement commit's complete CI result is tracked on the PR, not inferred from the earlier head. + + +## Companion source commits + +- Rendering2D: `067d4eb023b4dea0df114872247d9b44f69fe20f` — current-session controls, precise edit contract, Core label formatting and generation-only History queries. +- Samples: `3e331fd0fae08491fe11938faa5955c6e79e5089` — officially exported Rendering2D package in FlappyBird. +- Canvas and BGCS repositories have no task changes. Companion commits were created on their existing local branches; no remote main branch was updated. + +Both author and committer are Aaron Liao ; no co-author trailer is used. diff --git a/records/architecture/acceptance/EDITOR_UI_CAPTURE_TAIL_ACCEPTANCE_2026_10_30.md b/records/architecture/acceptance/EDITOR_UI_CAPTURE_TAIL_ACCEPTANCE_2026_10_30.md new file mode 100644 index 000000000..58906916f --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_CAPTURE_TAIL_ACCEPTANCE_2026_10_30.md @@ -0,0 +1,35 @@ +# Scene capture tail-latency refinement + +This is focused evidence for #27/#38/#46/#53/#54, not final approved-plan closure. PR #26 remains Draft until the replacement production candidate completes its applicable full matrices and CI. + +## Confirmed failure + +At `98fd5a7d`, five alternating idle/edit process pairs retained all fifty complete 10,000-object dirty inspections per process. Nine of ten failed the unchanged16/25ms median/P95 gate. Edit P95 was28.22–33.50ms; allocations were2,361,582 idle and2,514,872 edit bytes per operation. Raw evidence is `e46/inspection-pairs-43d7a2040c324d8d90e3d6359647150d` under the existing verification owner. + +The exact-producer EventPipe trace identifies per-row capture-result objects, interface array enumeration and the complete source-ID dictionary rebuilt for every graph capture. Profiling includes allocation samples and sampled-thread stacks; it is diagnostic, not quiet acceptance. A first small refinement reduced allocation by640KB but passed only8/10 processes; its failures remain retained in `e46/tail-refinement/inspection-pairs.log`. + +## Implementation and boundaries + +- The internal capture attempt is a readonly value. The borrowed public builder still retires after its callback, with the same identity, metadata and mutation checks. +- Scene iterates its frozen component list by index during row-key capture. +- `SceneStructureSnapshot`, already owned/inactivated by SceneStore, retains a read-only object/component source-ID projection. Every capture checks current Core persistent IDs; a mismatch creates a replacement without mutating an already borrowed projection. +- Systems belong to a separate lifecycle owner. Their indexed lookup is frozen only for the current graph write; the structure cache retains no System. Subtree/Prefab source mappings remain independent. +- The projection cannot resolve live objects or become another Identity registry. No public API, event bus, serializer, global pool/cache, persistent format, platform branch, timer change or BGCS/vendor/generated edit is introduced. +- Existing Writer, canonical bytes, complete serialization contexts, failure publication and strict generation retirement remain authoritative. + +## Focused acceptance + +Ordinary SDK Core Serialization97 and Scene67 pass. The added public Scene test holds the Scene alive, captures/removes a System, performs full GC/finalizer/full GC, verifies collection, adds a replacement and checks canonical bytes. The ordinary native PlayMode leaf builds successfully. + +Five new independent alternating idle/edit process pairs pass **10/10** unchanged absolute gates, retaining every50-operation process and all group/max outliers: + +| Workload | Median range, ms | P95 range, ms | Allocated bytes/operation | +|---|---:|---:|---:| +| Idle inspection |8.65–9.53|10.26–13.39|964,706–964,747| +| Inspection after each owned write |12.00–12.93|15.45–21.77|1,117,912–1,119,243| + +Evidence: `e46/inspection-pairs-9910c829afbd4a24b522c3361adbe441/measurements.json` and every TRX; build/Scene regression logs are in `e46/source-projection-indexed`. Verification root: `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. SDK10.0.402, runtime .NET10.0.13, Release, fixed10,000-object input, five warmups plus five groups of ten measured operations, unchanged110ms outside-timing sleep and100ms production throttle. No result is removed or timer relaxed. + +## Required continuation + +Freeze this replacement candidate, rebuild the complete applicable source/product/test graphs and rerun the final contracts, native layout/input, lifecycle, products, controlled original/current performance, companion projections, architecture/Wiki and exact-head CI. Prior98fd product/visual passes remain historical evidence, not an assertion that the new candidate has completed them. Reconcile every issue checklist and all eleven quality dimensions before Ready; close issues only through merge. diff --git a/records/architecture/acceptance/EDITOR_UI_CLOSURE_ACCEPTANCE_2026_10_28.md b/records/architecture/acceptance/EDITOR_UI_CLOSURE_ACCEPTANCE_2026_10_28.md new file mode 100644 index 000000000..a6ba2b55d --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_CLOSURE_ACCEPTANCE_2026_10_28.md @@ -0,0 +1,300 @@ +# Editor UI 闭合实施与验收记录 + +[执行台账](../EDITOR_UI_CLOSURE_PLAN.md) · [设计规范](../../../docs/architecture/EDITOR_UI_DESIGN_STANDARD.md) · [总 issue #27](https://github.com/Wolfiora/InnoEngine/issues/27) · [PR #26](https://github.com/Wolfiora/InnoEngine/pull/26) + +## 结论与适用范围 + +本轮已经完成实质性源码实现和多项真实原生、SDK、插件及产品验证;**尚不能声明原计划全部闭合,也不能把“企业级、现代级、现象级”作为已经通过的测试等级**。PR 保持 Draft,未授权自动合并。各 issue 维持 Open,完整验收的条目才可由合并时的 `Fixes` 关闭。 + +主要实现提交为 `63d4f7dce8b21daa22a6f7e5a00825be3f46dd2d`;资产刷新修复为 `273c42ad32be511c21fa21b1e99626af708f29c8`。author、committer 均为 Aaron Liao,无 co-author。后续修复提交为 `8dcceb94`(引导 scope)、`e23f4ba5`(源内容完整性)、`14338d61`(SDK 架构 owner)、`081f41f9`(测试临时 owner);均为同一作者及提交者,无 co-author。后续提交 `af6eac31` 修正 smoke 的产品预期;`e053be5b` 统一测试的 SDK 输出 owner;`a4d975ab` 修复 Settings 原子 Apply 与异常退休;`0974c317` 同步当前文档。 + +本机是 Windows;macOS 真实显示器与人工 Metal 操作未执行。模拟 DPI、Native Host 和远程 CI 分别报告,不互相替代。 + +## 最新候选复验状态 + +下表优先于后文按阶段记录的历史结果。原始失败、无效运行和未执行项仍保留,不由新结果改写。 + +| 范围 | 状态与证据 | +| --- | --- | +| 完整十二工程测试图 | 通过;`final-candidate-suites.log/binlog`,0 编译错误;保留 NU1900 漏洞服务网络警告。 | +| Core / Scene | 当前编码及并发改良后 59 + 53 项通过,`e46/core-scene-current.trx`;独立 Release Core 59 项通过。 | +| Workflows | 通过;305 项,`final-candidate/9409749fa03d4badb6f3b34c99a3dcdc`。 | +| Visual / Lifecycle | 通过;分别 136、11 项,`final-candidate` 中对应 profile 的 summary / TRX。套件有交叠,不能直接相加。 | +| Editor Debug / Release、Build 入口 | 通过;`final-candidate-editor-*.log/binlog`、`final-candidate-build-entry.log/binlog`,0 编译错误。 | +| FlappyBird 产品 | 通过;独立的当前 Samples 源码副本,Debug/Release × D3D11/D3D12/Vulkan 六组,每组 300 帧、正常退出;`products/final-candidate-*.log`。该 smoke 不替代完整人工旅程。 | +| Canvas / Rendering2D / FlappyBird | 正式脚本编译及 IDE 投影通过;`companion-projections-final/*/generation.log`。未编辑派生代码或插件包。 | +| 完整 Contracts | `e2e6ca32` 普通及隔离执行 721 项通过,另有 2 个 Windows 不适用的 Unix link 用例跳过;`closure-final/5c4cfef780fe4107bf0822dbff8a8432`。本轮后续 SDK 输出修复另有 9 项真实 SDK 专项通过。 | +| 最终 Wiki / 架构 | 通过;`wiki-final-candidate`,241/241 工程实际归属、API 清单与可编译示例门禁。网络 NU1900 警告原样保留。 | +| 最终成对性能 | 三类负载各五组交替原版/当前进程、共 30 次,以及菜单/统计五组对照均完成;输入与依赖 12,778 文件、583,137,380 bytes 前后完全一致。详见下方性能结果。完整 E11 产品/交互矩阵仍未闭合。 | +| CI | `f7dce89c` 的完整 Windows、macOS ARM64、两端 Managed 和 NuGet 检查全部通过,完整 job 含 tests、Support Pack 和 Wiki;[workflow 40653896090](https://github.com/Wolfiora/InnoEngine/actions/runs/40653896090)。后续本轮提交尚需其自身 CI,不能借用旧 head 的绿灯。 | + +Core 内部现在只在实际需要时创建回调、循环检测和恢复去重集合,复用 BCL reference equality;上下文仅在全部 owner 身份一致时复用。正式输出的同一属性 bytes 保持一致,分配测量为 fresh 1,888 / prepared 1,168 bytes,原 prepared 为 2,096。新公开边界回归先失败再通过;没有修改 `SerializationContext.With` 的新实例语义。该观察不是整帧或全产品 CPU 收益证明。 + +原生 command-key 模式会在 ImGui 内交换 Control / Super。`EditorUiInput` 现在还原物理 Core 修饰键含义,列表选择复用该边界;原生文本控件保留自身配置。四个真实输入组合验证两种 primary modifier 与两种原生模式。共享 Editor 不增加 OS 检测或平台引用,fixtures 显式选择键盘配置。 + + +## 本轮收尾复验与深层自查 + +- `6df06585`:二进制编码先计算精确长度,直接写入最终 caller-owned buffer;wire bytes、字符串边界、特殊数值与错误语义由独立 BinaryWriter oracle 验证。1 MB 值的旧编码分配测试先失败,改良后通过。Converter lookup 继续属于同一个 generation,热命中使用 ConcurrentDictionary,首次构造由原 owner 锁保证唯一,最后一个 lease 才能退休类型和实例;没有全局 converter cache。 +- `e2e6ca32`:Shader settings 六处行 ID 和一处 capability tree 采用局部 finally;合并 30 个重复 alias、176 个语义符号引用,删除 3 段重复 XML summary。AST 防线仅覆盖明确模式,不伪称证明任意动态 scope。相关 source-contract 16 项通过。 +- `closure-final`:Contracts 721、Workflows 305、Visual 136、Lifecycle 11 项通过;各 profile 有交叠。Core/Scene 的后续专项为 59/53 项,不与 profile 计数相加。 +- 新增的真实 SDK 输出回归先以两项 ArtifactsPath 失败复现,普通 Debug/Release 两项通过;修复后初始四项全部通过。扩展后的 `e40/output-entry-final.trx` 共 9 项通过,覆盖 Debug/Release、普通/独立输出、真实 RID、工具中立、工具/产品/fingerprint 输入、等价 props 路径、IDE execution 属性、并行编译、编译器工具引用和入口 owned restore。 +- 全双入口构建暴露了两个关联问题:中立/产品实例争写 DLL,以及入口直接引用先于产品还原。前者使用 SDK ArtifactsProjectName 的早期选择,后者复用 RestoreOwnedProjectTask 和 Core IO lease;没有串行构建、手拷 DLL、借用默认 obj、删除 RID 或增加平台名单。`e40/two-entry-restore-fixed.log/binlog` 已通过,0 编译错误,NU1900 原样保留。规范化路径修正后的全新输出复验与最终 CI 另行记录。 +- 实际 FlappyBird D3D12 运行 15,000 帧,9 个实际 Panel 各关闭/打开 100 次,完整退出与 sRGB 检查通过。两份完成 TSV 必须存在,不能只依赖进程退出。`products/closure-panel-lifecycle-resources.tsv` 在 0/10/.../100 次 closed-state 进行 Full GC → finalizers → Full GC:Selection 始终 0、Scene 1、History resident/disk 0、Core pending events 0;managed 从 26,168,896 到 26,179,976 bytes,private 从 880,267,264 降至 864,219,136 bytes。该结果是公开状态和内存观察,不证明每一个私有 native owner。 +- 最初短 smoke 的期望字符串写错,未产生 fixture 完成标记,因此没有计入通过。增强 fixture 通过公开 Workspace API 打开实际 FlappyBird 源场景,不让引擎为验收自动创建 Untitled。测试辅助方法曾有一次编译错误,已修正,后续构建 0 错误、专项 9 项通过;失败日志保留。 +- 并发序列化对照 `e46/parallel/summary.json`:同输入、五组交替进程,8 workers / 80,000 次 decode 的中位 wall 55.46 → 42.63 ms,CPU 375 → 265.625 ms,分配基本相同;1 worker 的 10,000 次为 25.11 → 23.36 ms。Monitor contention 计数不代表所有锁种类,因此收益以独立 wall/CPU 结果说明。此负载不等同完整 UI 帧性能,完整 Scene dirty-scan 的 #46 仍 Open。 + +自查已深入执行链:平台策略仍由平台贡献;入口只声明选中的产品;Restore 与 compilation 保留隔离;共享层使用 SDK 求值路径;History、Identity、Core Events、Serialization 与 IO 没有平行实现;新的 converter 缓存仍由 generation/lease 拥有。BGCS、NuGet 包、第三方及生成绑定未修改。没有因本轮通过项而宣布 E08–E12 或整个引擎已经完美;下方未完成矩阵仍然有效。 +## 最终受控性能结果 + +源码候选为 `f7dce89c0d91ecb0f2333b8ca3117fefc71db96a`;原生产基线为 `b303d9f10cbaf5a905f7cb529e52cf8293b0d8bd`,原版只移植验收 harness,不修改生产实现。两侧使用同一 .NET 10.0.402、Debug 配置、机器、冻结数据和 Native command-validation 方法;各进程预热五帧,再采样十帧,五组进程交替顺序运行,没有构建/测试/产品并发。原始各组数据保留在 [性能证据目录](editor-ui-performance-2026-10-28/README.md)。 + +| 负载 | 原版 median / P95 | 当前 median / P95 | 原版 → 当前分配/帧 | +| --- | --- | --- | --- | +| 10,000 对象层级 | 5117.21 / 5594.09 ms | 345.14 / 402.69 ms | 709.02 → 139.06 MB | +| 10,000 资源目录 | 4104.39 / 4320.28 ms | 18.73 / 27.58 ms | 249.23 → 0.98 MB | +| 2,000 Shader 节点 | 29.01 / 33.17 ms | 28.78 / 33.39 ms | 4.36 → 3.63 MB | + +这里 MB 使用十进制字节。图负载的 P95 为 +0.65%,CPU 中位数相同,前次超过 10% 的回退已消除;三类负载均通过本次相对回退与分配门槛。菜单打开的每次分配 8544.29 → 4240.14 bytes,时间 10.61 → 6.18 µs;无变化统计读取 16792 → 400 bytes,时间 30.40 → 0.24 µs。后两项是公开菜单/统计契约负载,不能等同完整产品帧收益。 + +**绝对表现仍有严重限制:345 ms/帧、139 MB/帧的大层级负载不能称为优秀。** 当前 `IsDirty` 仍周期性序列化完整 Scene 再散列,检查超过既有 100 ms 周期时会持续占用后续帧。[#46](https://github.com/Wolfiora/InnoEngine/issues/46) 已记录真实调用链、职责、设计约束和验收要求;没有用延长周期、隐藏检查或不完整 mutation cache 掩盖。#45 的 Core 改良不代表 #46 或完整 E07/E11 已完成。 + +本次是 Native command-validation / headless public-contract 比较,不是 GPU 或完整 FlappyBird 产品帧性能认证。原版与当前 FlappyBird API 输入不相容,以及滚动、搜索、属性拖动、冷启动等完整性能矩阵仍按对应 issue 保持未闭合。 + +证据根目录:`D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`。表中的路径均相对此目录。日志、TRX、binlog、截图和大规模输入不进入源码仓库;本记录与执行台账提供可追溯索引。 + +## 实现与职责自查 + +| 位置 | 实际改动与边界 | +| --- | --- | +| Interactions | 静态菜单及快捷键结构按 generation 建索引,动态可用性与请求数据继续即时解析;不建立全局缓存或第二套 Action 系统。 | +| 通用 Graph / Graph.ImGui | 文档与 History 保持中立;节点、端口几何、命中与 pointer intent 可供非 Shader 图使用。Shader controller 保留语义解释和领域修改。 | +| ImGui presentation | Session 拥有 transient state;原生帧准备与绘制分离,同一帧不能提交新的 Zoom/Density。真实 callback 失败结束所属帧并保留原异常。 | +| Domain presentation | Browser、Console、Hierarchy、Settings、Inspector Tag/Layer 的专属值归对应 owner;通用 palette/metrics 只保存基础与复用组件值,删除旧成员后直接更新调用方。 | +| 大视图 | Hierarchy、Browser 使用 revision/展开/过滤决定的中立可见序列与原生裁剪;Console 使用有界槽位与测量缓存;Graph 局部更新几何。 | +| Statistics | 无变化时复用不可变快照,Panel 有界保存 0.5 秒间隔、60 秒窗口、最多 120 个 timing 样本。 | +| Scripting Compiler | 只消费中立冻结输入,不引用 Build、MSBuild 或命名平台;实际 SDK 求值由 Host/Build provider 提供,取消与失败不发布半成品。 | +| Tooling / Build | 读取 SDK 选定的 assets、references、compile items、defines 与输出 owner,不用过期默认 bin/obj 代替当前请求。 | +| AssetFileSystem | 在既有 Core Identity/store 内准备完整候选,复用未变化 entry,失败保留原视图,只退休移除内容;没有 UI fallback 或平行 live-object authority。 | + +已经沿实际消费链复核:Feature 不依赖 Native presentation,Graph 不依赖 Shader 世界观,平台事实没有移入通用执行器,History 继续使用既有中立 payload 协议。跨帧选择和延迟目标使用 Core Identity/弱引用;同步 `EditorInteraction` 对目标的强借用在 XML 和规范中明确,不能误当成长生命周期缓存。 + +新增公开表面仅用于实际消费者:通用 Graph 的呈现输入及交互结果、SDK 冻结脚本输入、真实 Host 调用的帧准备协议和 hierarchy revision。内部 Console 过滤、样式值与呈现缓存没有因测试而公开。 + +没有增加 Events、Identity、History、Serialization、插件发现或缓存管理的平行实现。BGCS 源码、NuGet 包、第三方源码和生成绑定不属于本轮修改;负向程序集内容仅在测试自有副本中构造。没有加入 global/implicit using、IVT、私有反射穿透、兼容 reader 或旧 API 转发。 + +## 已执行证据 + +以下列出当前候选对应的专项结果;它们存在交叠,**不能相加成互不重复的总测试数**。 + +| 验证 | 实际结果 | 证据 | +| --- | --- | --- | +| 完整 SourceDebug 源码编译 | 通过 | `source-frame-final.log`、对应 binlog | +| Windows Editor Debug 构建 | 通过,0 errors | `editor-frame-final.log`、对应 binlog | +| Native ImGui context/frame/metrics/controls | 72 通过 | `e03/all-native-frame-contracts.trx` | +| 统一 Workflows profile | 297 通过 | `unified-final/2a003d59a27a4c22b65ff5ac2b285170/summary.json` 及 TRX | +| 统一 Visual profile | 135 通过 | `unified-final/*/summary.json` 中 Visual 记录及 TRX | +| 统一 Lifecycle profile | 5 个循环用例通过 | `unified-final/*/summary.json` 中 Lifecycle 记录及 TRX | +| 实际 package / loaded assembly 身份 | 普通 3 通过;独立进程 1 通过 | `e16/loaded-package-shared.trx` 与 isolated TRX | +| SDK / C# 14 / IDE 冻结输入 | 11 通过 | `e19/sdk-contract-final.trx` | +| SDK-owned tooling owner | 完整 153 项通过,包含实际 SDK/当前输出 owner 的仓库级检查 | `corrections/Inno.Tooling.Architecture.Tests/formal-complete.trx` | +| Asset pipeline / source identity / writable content | 完整 123 项通过,包括保留时间戳的源与依赖变化、被锁定源的 last-good 诊断 | `corrections/Inno.Assets.Pipeline.Tests/complete.trx` | +| Console Clear / 真正 eviction | 2 通过 | `e07/console-retired-real-before.trx` | +| 实际九类 Panel 100 次循环 | 执行完成;不替代完整资源计数矩阵 | `e10/panel-lifecycle-final.tsv`、产品日志 | + +真实 package/load 负例保持程序集名称及 MVID 相同,仅改变测试副本内容,实际加载后由内容身份拒绝;随后执行 Full GC → finalizers → Full GC 并确认 collectible ALC 不可达。没有通过删除默认 obj 或清理全局 NuGet 使测试变绿。 + +C# 14 用例覆盖 `field`、空条件赋值、Span 重载、泛型 `nameof`、lambda 修饰符、扩展成员与跨插件 IDE 引用。正式输入一致性已验证。额外公开 SDK 探针验证了两个真实 CLI 根、显式 10.0.402 选择、干扰 pack 目录和不兼容宿主,详见下文;其后完成真实 10.0.401/10.0.402 的不同 patch 验证,详见后续复验。 + +### 真实 FlappyBird 操作 + +独立副本 `products/FlappyBird-functional-final` 使用当前正式插件包和 D3D12 Editor: + +1. 正常打开 FlappyBird Scene,Scene/Game、Hierarchy 与 Browser 内容正常。 +2. 使用公开快捷键/可见 toolbar 进入 Play,并返回 Edit;观察实际 action tooltip 和场景恢复。 +3. 返回 Edit 后 Browser 目录树及 `~Authoring` 文件列表保持可见;之前 #43 的空列表问题未再出现。 +4. 从 Edit 菜单打开 Settings,修改 Density,确认 Apply/Cancel 状态;Cancel 后重新打开仍是保存的 Comfortable。 +5. 正常关闭窗口,进程 exit code 0,日志明确记录 product resources released 和 Dispose end。 + +证据:`products/editor-functional-final.log`、`products/FlappyBird-functional-final/Logs/EditorBoot.log`、`e09/flappybird-final-play-stop-browser.png`、`e09/flappybird-final-settings-cancel-reopen.png`。 + +此前同一实现阶段的真实 Inspector 单轴编辑、Undo、Redo 及回到原值保留于 `e09/flappybird-inspector-*.png`。它们是实际功能证据,最终版本完整多功能/失败/取消矩阵仍须独立说明。 + +### Release 工具图回归 + +最终 Release 冷构建发现 `BuildingInsideVisualStudio=false` 与未声明该属性造成两个 generator build identity;binlog 显示两个 Csc 同时写同一输出。SDK 提到可能的防护进程不是本报告确认的根因。已在原有 SDK-negotiated compiler-tool boundary 规范化该宿主事实,没有停止用户进程、关闭安全设置或串行化整个构建。 + +扩展的真实 SDK fixture 在修复前 Debug/Release 均失败,修复后两项通过;每项包含冷构建、未变化热构建和修改 Tool 源码后的重建。证据为 `editor-release-final.binlog`、`e40/release-graph-failure.json`、`e40/ide-graph-before-selected-host.trx`、`e40/ide-graph-after.trx`。随后产品 Release 又复现普通 Catalogs 输出冲突;此失败不能算通过。进一步定位到 Foundation 引导剥除调用方属性却写同一源码输出。`8dcceb94` 把中立引导放到独立且固定的 SDK scope,保留发布参数隔离;27 个完整引导回归通过,包括 Debug/Release 并行图、热构建和 Browser/AOT/RID 请求。证据 `e40/stable-bootstrap.trx`。最终产品重跑另列。第一次直接 VSTest 没有传入选定 DOTNET_HOST_PATH,使用了机器另一个 muxer;该运行不作为图缺陷证据。 + +### 证据纠正与无效运行 + +- 第一轮完整 Contracts 操作缺少其自有 Native deployment,之后又发生与仍运行 testhost 重叠的测试输出构建。该操作已经中断,`contracts-interrupted.json` 明确标为无效,不能计作全部通过。 +- 统一 builder 已传递正式 `InnoTestNativeTarget` 和 `InnoTestNativeProduct`。未放宽文件锁协议、生产校验或超时。 +- Console eviction 初始用例没有达到实际最小容量 16,不能证明 eviction 缺陷。已纠正前提;真实 Clear/evict 测试通过,推测性的 production focus 改动撤回。 +- 诊断 dump 在保留文本栈和原因后,仅从本轮明确 owner 删除;未清理用户文件或 SDK/NuGet 全局缓存。 + +### 后续完整性与编排修复 + +- [#44](https://github.com/Wolfiora/InnoEngine/issues/44):Windows CI 暴露同长度快速改写返回旧内容,保留时间戳的用例确定性复现。可写源及 Source 依赖现验证实际 SHA-256;采用标准库流式读取,保持既有失败诊断/last-good 恢复,不修改 ID、sidecar、BGCS 或只读安装 generation 协议。123 项完整 Asset Pipeline 测试通过。增加必要内容读取的成本单独报告,不能称为免费优化。 +- #41:图检查原本过早 ResolveReferences,给纯 graph fixture 创建输出并对无 Foundation 的 fixture 抛错;改为按语义用途惰性获取框架引用。完整仓库测试改用 SDK 及编译时记录的实际 ArtifactsPath。轻量 fixture 仍为两分钟;新增的完整 SDK 检查独立设置十五分钟预算,实际完整套件为 6 分 13 秒。SDK 单请求与 ALC unload barrier 的原超时没有放宽。 +- 测试临时目录改为显式 `-TemporaryDirectory`;默认保留 OS 环境。本轮短 D: owner 下原失败的 9 个 SDK/CMake 用例通过。深结果目录和源码调整期间中断的旧运行均不计入通过。 + +## 当前关键文件树 + +```text +build/msbuild/Inno.Build.Tasks.targets # 中立 SDK Foundation scope +build/pipeline/Inno.Build/Scripting/ + SdkScriptCompilationEnvironment.cs # SDK -> 中立冻结输入 +src/runtime/scripting/Inno.Scripting.Compiler/ + IScriptCompilationEnvironmentProvider.cs + ScriptCompilationInputs.cs +src/content/assets/Inno.Assets.Pipeline/ + Sources/AssetFileSystem.cs # 候选索引与稳定身份 + Importing/AssetLoader.SourceIO.cs # 内容验证与稳定读取 + Importing/AssetLoader.Dependencies.cs +src/composition/editor/ + framework/Inno.Editor.Interactions/ # Action/Menu/Selection/History + presentation/Inno.Editor.Graph.ImGui/Canvas/ + presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs + presentation/Inno.Editor.ImGui/Styling/ + presentation/Inno.Editor.ImGui/Widgets/ + presentation/Inno.Editor.Assets.ImGui/Styling/ + panels/Inno.Editor.Panel.Logging/Console/ + panels/Inno.Editor.Panel.Logging/Styling/ + panels/Inno.Editor.Panel.Hierarchy/Presentation/ + panels/Inno.Editor.Panel.Hierarchy/Styling/ + panels/Inno.Editor.Panel.Inspector/Styling/ + panels/Inno.Editor.Panel.Settings/Styling/ + panels/Inno.Editor.Panel.Stats/ +tools/Inno.Tooling.Architecture/ + DocumentationSourceModels.cs + WikiProjectReferences.cs +tests/Invoke-EditorUiAcceptance.ps1 +tests/Invoke-IsolatedTests.ps1 +tests/fixtures/editor-ui/PanelLifecycle.editor.cs +``` + +该树列出职责 owner 与关键改动;其余已有功能位置保持,完整文件差异以对应提交为准。没有为目录对称新建空项目。 + +## 必须保留的阻塞与未完成项 + +| 项目 | 当前事实及重新验收条件 | +| --- | --- | +| E11 原版 FlappyBird 同输入比较 | 原基线 `b303d9f1` 的正式工具构建成功,但当前插件源码依赖原版不存在的 `EditorUiSession`,正式脚本编译失败。日志 `e11/original-flappy-projection.log`。不能替换输入或加兼容层后称相同输入。需要可在双方正式接口上运行的、经认可的共同产品输入/基线定义。 | +| 原始完整计划文本 | 桌面文件现为闭合计划,hash `cbb7d50a…`;旧原始 UI 文档只有复审记录的 hash,不能把二者说成同一文件。现按获批闭合计划和复审矩阵执行。 | +| 完整工作流 | 已执行多条真实旅程和 handler/contracts,但每类界面正常、失败、取消、关闭重开四条路径尚不能全部逐格记为通过;Settings 跨 owner 部分失败已经真实复现并修复,四项 Native 回归通过;其余完整旅程仍需逐项闭合。 | +| 完整视觉矩阵 | 真实 Native simulated metrics 与部分产品截图已执行;匹配原版/当前的完整截图、所有浮动停靠与物理显示器组合没有全部实测。 | +| 完整生命周期收敛 | 100 次 Panel、Scene/Shader document 和 reload 用例不等于每个 owner 的全部订阅、手势、引用计数已证明收敛。不能用单次 Host 正常退出替代。 | +| #19 多 SDK 环境 | 已验证真实官方 10.0.401/10.0.402、167 个实际 references、Runtime/IDE 一致性及 SDK 切换缓存身份;Mac 实机未执行。 | +| 最终完整 Contracts 与 CI | 分别记录最终运行结果。旧 head 的绿灯、已中断运行或单个测试不替代同候选完整结果。 | +| macOS 人工设备 | 本机无 Mac;远程 ARM64 CI 仅证明其实际自动步骤,不能证明人工 Metal、音频、显示器和操作体验。 | + +因此总 issue #27、E08–E12 等不得被无条件标记完成。没有验证的项继续保持 Open;阻塞没有被改写成缺陷已解决,也没有归责 BGCS。 + +## 十一项质量评价 + +判断针对可复查工程标准,不提供虚构的行业认证或“完美”分数。 + +| 维度 | 判断与证据 | 仍需改进/证明 | +| --- | --- | --- | +| 架构 | 分层已有实质改善:领域、交互、呈现、Host/SDK 有真实消费者和依赖边界。 | 完整最终门禁及所有生命周期 owner 证明完成前,不宣布整个 Editor 达到全部原标准。 | +| 代码整洁度 | 文件按功能 owner 收拢,删除旧转发,API/引用/Wiki 同步。 | 持续审查复杂 Panel 的职责规模;不把更多拆文件当作质量本身。 | +| 代码干净程度 | 修复实际 source identity、frame failure、同帧尺度问题,未用 fallback 掩盖。 | 完整异常、取消、资源收敛矩阵仍需补齐。 | +| 抽象程度 | Graph 有非 Shader 消费者;SDK 输入中立;帧准备有真实 Host 调用。 | 第二种完整图产品旅程与所有扩展失败路径尚不等于全部验证。 | +| 可复用程度 | 复用 Core Identity/Events、既有 History/Serialization 和 Session controls。 | 继续以真实调用方证明复用,不建立通用却无消费者的设施。 | +| 样式统一 | 基础/组件/领域三层归属落实;同帧尺度契约已测。 | 完整匹配截图、窄布局与物理 DPI 体验仍有缺口。 | +| 可拓展程度 | generation-owned 索引、最小 Graph contract、SDK provider,无中心 Shader 白名单。 | 完整 reload/ALC 和第三方实际扩展旅程需要持续验证。 | +| 操作逻辑性 | 键盘、Console 搜索、真实 Play/stop、Settings Cancel 已测。 | 全界面的失败、取消、关闭重开逐格证据尚不完整。 | +| Undo/Redo | 继续单一 History,真实 Inspector edit/undo/redo 与原子 handler 回归有证据。 | 各结构化批量操作、Settings 多 owner 失败、重试、单次 Undo/Redo 已补齐;其余结构化批量修改及 Missing 恢复旅程仍需完整矩阵。 | +| 菜单清晰度 | 静态结构复用、动态状态即时,真实 Edit 菜单 disabled Undo/Redo 正确。 | 全部 area/动态贡献、冲突和可发现性的最终旅程与性能需闭合。 | +| 组件颗粒度 | 通用与领域 owner 更明确,内部缓存/样式未扩大 public 表面。 | 保持公开边界最小且完整;复杂 controller/session 的长期规模仍需定期复核。 | + +总体判断:有实质性工程进展,仍未达到原计划的全部完成条件。不能将局部强项概括为所有十一项已经“超级无敌优秀”。 + +## 资源、提交与后续结果 + +大型输入、baseline、binlog、TRX 与截图集中在 D 盘。保留现行有效产物和活动 reader,维护通过既有 owner/lease 协议进行,不手删协调锁或全局依赖缓存。完成时核对实际磁盘空间、companion/BGCS 状态、最终提交和 CI,并在此追加精确结果。 + +最终采样后通过正式 cache clean 先 dry-run 再执行:逻辑 inventory 10,787,641,712 bytes,保护 174,384,757,退休 10,613,256,955,skipped 0。C 盘实际可用从 92.090 增至 93.501 GiB,D 盘约 97.782 GiB;hardlink 的逻辑回收量不能当作实际卷空间。日志为 `cache-after-final-preview.log` 与 `cache-after-final-maintenance.log`。没有清理 SDK/NuGet 全局缓存或用户内容。 + +本环境没有 `/System/Library/Sounds/Glass.aiff`,不能播放指定提示音。 + +## Settings 与测试输入 owner 的专项复验 + +### 实际缺陷、失败证据与修复 + +Release Editor 在独立样例副本中复现:Editor Density 已写入后,Build settings 的独占文件锁使 Apply 失败;原 Settings Modal 随后被隔离,留下部分成功的修改。证据为 `products/settings-partial-apply-before.log`、`e08-settings-partial-apply-before.json`、`e09/settings-partial-apply-before.jpg`。这是现行实现的真实失败,不是 BGCS 缺陷。 + +修复沿原有 `EditorInteractions.history` transaction 处理 Editor、Project、Build 的一次 Apply;成功后才接受 draft baseline。失败保留 staged values,局部显示状态并保留完整 Console 诊断。回滚失去完整性时保持 History Faulted,不能继续 Apply。Settings 窗口拥有当前 generation 的借用与原有 reload guard;异常 Drawer 经标准隔离流程退休 transient owner,重建 catalog 后再允许打开。没有增加平行 History、Recovery、Events 或 Identity。 + +`e08-settings/green-official.trx` 四项通过: + +1. Editor/Project 中途失败后全部恢复,重试成功,形成一次 Undo/Redo。 +2. 回滚本身失败后保持 Faulted,修复磁盘前提也不会偷偷恢复 Apply。 +3. 100 次打开/关闭后,公开 Drawer 接收的值可被收集,reload admission 恢复。 +4. Drawer 抛异常后释放 reload guard,菜单禁用直到真实 catalog rebuild,再正常打开与关闭。 + +第 4 项在修复前实际失败(`red-official.trx`)。第 3 项在旧实现也通过:它观察的是 Drawer 获得的副本,**不能证明旧 Session 泄漏,也不证明全部私有 Session 或 ALC 已回收**。移除 Modal 对旧 session 的不必要强引用是代码所有权修正,不能伪称已经复现了该泄漏。各运行使用 SDK 选定的 Editor product props;早期错误选择普通 ProductBuild.props 的诊断运行单列,不作为最终产品构建。 + +### 完整 Contracts 失败与实际 owner + +`unified-stable/554c93c1e0234bb880a7ae94623fbf2f` 是一次完成的失败运行,不能改写成通过:Build.Tests 379 通过、2 失败、2 个平台条件跳过;其他六个普通/隔离文件分别为 26、2、130、153、13、1、1 通过。各 profile 有重叠,不累计成唯一测试总数。 + +两个失败分别为 Wiki 漏传独立 ArtifactsPath,以及 Task fixture 猜测已经退出的默认 Foundation 输出。Wiki 原先已经传了 SDK 入口;问题是输出 owner,不能说成它完全没有 SDK 输入。Task fixture 改为读取实际 MSBuild TargetOutputs,保留文件句柄释放、两次私有 publisher 身份和零 Copy 重放断言。 + +测试 source 项目新增 `TestRepository.GetArtifactsPath(Assembly)` 只读取 SDK 生成的公开 assembly metadata。统一 targets 在 SDK 算出输出之后记录该事实;没有引擎内部访问后门、生产 API 扩大或第二个路径 resolver。六项真实 SDK/metadata 用例通过,包括默认/显式 owner 和损坏 metadata 拒绝。目标注册测试正式复验通过;同轮 Wiki 产生新的 241 工程库存,并指出新增 Dispose 漏 XML 一项,现已补齐。首次直接 VSTest 漏了官方入口的 PATH 环境而用了 .NET 9 muxer,该运行保留但不归责实现。最终完整 Wiki 复验另列。 + +### 多 root 与 pack 干扰 + +`e19-root-probe/result.json` 使用生产公开 SDK capture:已安装根与测试自有 D 盘 CLI 根都明确选择 10.0.402,167 份 framework references 内容一致;D 根只消费自己的 pack 路径,`10.0.999` 干扰目录不被选择。不兼容 .NET 9-only 宿主明确失败,没有 fallback。临时求值工程正常退休。 + +D 根的 host/sdk/shared 使用测试自有 junction 指向现有安装;干扰 pack 是测试副本,不是另一个实际 SDK 安装。没有修改安装 SDK、NuGet 或消费者 global.json。因此这是根选择和干扰拒绝证据,不冒充多个不同 SDK patch 或 Mac 实机矩阵。 + +## 后续实际 SDK 与产品复验 + +以下结果对应 660962a5 及此前实现;后续 Core/Graph 改动需要自己的最终验证。 + +- SDK 10.0.401 选择 reference pack 10.0.12;10.0.402 选择 10.0.13。401 根下存在真实 13 pack 的受控 junction,也继续选择 12。相同 C# 14 源码的 Runtime 编译与实际生成 IDE 工程均通过;167 个 reference 路径及内容在各自操作中一致。官方 401 安装包经 SHA-512 验证,只安装在本次 D 盘 owner。 +- 同一测试 Project 执行 401 → 402 → 402 → 401:新编译、新编译、缓存复用、缓存复用。不同 SDK 使用不同产物。证据 e19-dual-sdk/verified-current-result.json、switch-0.json 至 switch-3.json。引擎副本与当前正式 producer 内容一致。 +- Canvas、Rendering2D、实际 Samples/FlappyBird 正式完成脚本及 IDE 投影;companion 仓库 Git clean。证据 companion-projections//generation.log。 +- Editor Debug/Release 和 Build 入口构建 0 errors;editor-Debug-settings-final.log、editor-Release-settings-final.log、build-entry-settings-final.log。 +- 七个 UI 工程正式图编译 0 warnings/errors,final-ui-suites-build-corrected.log。660962a5 修复统一入口遗漏 SDK 实际 ToolTarget 的问题,没有新增平台推断。 +- Workflows 301、Visual 135、Lifecycle 6 个用例通过,unified-settings-final/ 的 summary/TRX;完整 Wiki 与 Task registration 两项通过,final-owner/final-owner.trx。各项结果存在交叠。 +- Debug/Release × D3D11/D3D12/Vulkan 六个 FlappyBird smoke,各 300 帧且正常退出,products/settings-final--.log。只证明有界启动、渲染和资源退休。 +- 最终真实 Settings:Build 文件独占时 Apply 失败,Editor 设置文件 SHA-256 保持基线;释放故障后重试成功,一次 Undo 恢复两份基线,一次 Redo 恢复两份成功文件。证据 e08-settings/final-failure-atomicity.json、after-single-undo.json、after-single-redo.json 及 e09/settings-atomic-after-*.jpg。随后 Play/stop 和正常退出完成。 + +## 性能复审与修复追踪 + +首次有效比较 e11/pairs-57543dce399247dfa6551b6351621c39:三个负载各五组交替原版/当前进程,共 30 个成功进程。相同冻结输入、SDK、配置及原生命令校验,不代表真实 GPU 或整个产品性能。 + +| 负载 | 原版 median / P95 ms | 修复前当前 median / P95 ms | 修复前当前 bytes/frame | +| --- | --- | --- | --- | +| 10,000 Hierarchy 对象 | 4703.73 / 4959.41 | 470.16 / 511.64 | 655,611,454 | +| 10,000 Assets 目录 | 3678.02 / 4049.01 | 16.29 / 24.56 | 998,359 | +| 2,000 Shader 节点 | 25.00 / 27.55 | 30.21 / 36.83 | 4,171,160 | + +Graph 回退不满足要求;Hierarchy 即使更快,绝对分配仍过高。输入及 2,150 个依赖文件采样前后保持一致,e11/before-correction-integrity.json。更早首轮有准备重叠风险,整轮无效,e11/invalid-first-release.json;未挑选较好数据。 + +真实 [#45](https://github.com/Wolfiora/InnoEngine/issues/45) 跟踪 Core 修复:小属性编码不再预分配 16 KiB;单属性捕获不执行无关 getter。沿既有 Serialization pipeline 实现,没有新 serializer、池、缓存或 schema。修复前 4 项失败,修复后 Core 全部 46 项通过,e45/red.trx、e45/complete.trx。输出 bytes、大小增长、异常及属性恢复有公开契约覆盖。 + +受控副本只替换该 Core producer 的诊断探针,Hierarchy 分配约 655.6 MB → 171.8 MB/frame。该次与 trace 重叠,耗时不能作最终结果;剩余 full Scene dirty 检查成本如实保留,不延长间隔或使用不完整 revision 隐藏直接属性修改。 + +Graph 修复归 [#34](https://github.com/Wolfiora/InnoEngine/issues/34):复用当前窗口绘制 borrow、已测标签宽度及帧 mouse;每次真实绘制保留窗口、帧、线程及 Context 校验。同一修复候选仍需正式构建、回归与五组性能复验。 + +## 浮点边界与测试产品归属复查 + +- 第一轮 Graph 借用/测量修复后的正式七工程图编译成功,6 项 Graph/Settings Native 回归通过;e45/graph-settings.trx。单次诊断为 median 29.55 ms、P95 30.96 ms,尚不足以通过相对原版的性能门槛。 +- 随后的十二工程图编译成功,final-correction-suites.log/binlog;但颜色原生 oracle 在 NaN 上失败:原生结果 2147483648,普通托管转换结果 0。e45/color-oracle.trx。该操作在此停止,未运行后面的 Core/Scene 或统一 profiles,不能记为整体验收通过。 +- 当前修复使用标准库 Single.ConvertToIntegerNative,保留原有 Session/Context/线程准入、饱和与取整协议。原生 oracle 覆盖通道顺序、上下溢出、非有限值、全部半整数边界和 4,096 个确定性输入。既有 Core Color 的 byte 转换使用截断语义,不能直接替换原生 UI 的舍入协议;没有为此增加另一套通用数学系统。 +- 统一测试脚本不再全局设置 InnoTestNativeProduct;它覆盖了各工程的声明,也使带有传递 native 引用的纯契约套件反复准备完整 Editor。实际 Native UI 工程保留自己的产品声明,Build.Tests 根据此前真实脚本引用验收所需依赖显式声明。目标、工具和产品参数仍来自正式 SDK 求值;生产准备协议与校验没有跳过。 +- 原生准备日志中的 processes=0、bindingGenerations=0 证明没有实际 Native 重编译,却不证明输入验证便宜。重复读取约 4.5 GB 的既有构建成本仍保持独立问题,不以减少生产完整性检查来使本次 UI 验收变快。 +- SDK API 清单准备完成 241/241 工程求值;该次未提供当前逻辑脚本 references,且文档当时尚未同步,exit 1 是保留的准备失败,不是最终 Wiki 通过。另有环境 NU1900 审计网络告警;没有关闭审计或忽略告警。 +颜色数值 oracle 在 ConvertToIntegerNative 修复后全部通过。额外断言错误地把 Render 后仍存活的 Context 当成无效 owner;该纯值操作原有准入只验证 Session、Context 和线程。已纠正为 owner 释放后的拒绝断言,没有修改生产准入。DrawList 的窗口/帧校验仍由 RequireDrawingFrame 承担。 + +## 最新统一功能门禁 + +同一生产实现候选完成:Core 序列化 46、Scene 53、Workflows 302、Visual 136、Lifecycle 11 项通过。它们存在重叠,不能相加成独立总数。完整 Contracts、最终产品构建及受控性能仍单独报告,不能由这些结果推断全部通过。 + +真实 UI 扩展的进程隔离用例保留第一代 immutable menu model,连续十轮先编译失败再成功替换,核对 last-good、当前标签和 Action ID;在下一轮前通过现有严格 barrier 确认旧 collectible Action Type 不可达。普通套件还验证了十轮运行脚本成功/失败、十轮删除/恢复、十轮破损插件更新/恢复及十轮 Play/stop/reload。没有缩短 timeout、清除 pending monitor 或放宽回收检查。证据 e10/final-reload-cases.json 和 unified-correction-final 对应 TRX。 + +原生测试产品归属经真实 SDK 求值确认:12 个选定工程中 4 个明确声明产品,其余没有被脚本强制声明。native-test-ownership.json 保存实际结果。该改动消除不必要的准备请求,不用不同输入的一次总构建计时宣称性能收益。 diff --git a/records/architecture/acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md b/records/architecture/acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md new file mode 100644 index 000000000..50d0e9a81 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md @@ -0,0 +1,147 @@ +# Editor UI 完整计划验收与交付记录 + +生产实现:`dd5979c613b0fe8ebd9f7026f25001ceee9f8cee`,分支 `editor-ui`。本报告之后的交付提交只允许包含文档及验收证据;其完整 SHA、差异证明、最终 CI 和 Ready 状态以 [PR #26 的精确提交附件](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338)为准。**本地通过不自动等于最终 CI 通过。** 附件确认全部适用检查后才转 Ready,不自动合并。 + +本地适用的功能、原生输入、六组 Windows 产品、生命周期、正式脚本/IDE、文档及受控性能检查通过。真实显示器、OS IME、人工 Mac 操作未执行。没有“现象级”认证,也不把本轮 UI 验收说成整个引擎无缺陷。 + +## 范围、身份与追踪 + +- 原计划:桌面批准计划的内容哈希及逐条来源见[计划映射](editor-ui-2026-10-30/original-plan-requirements.csv)和[证据清单](editor-ui-2026-10-30/manifest.json)。共 307 行,含十个界面工作流;其中 7 行是状态词汇、2 行是未来合并后核对规则,不能算作九项功能测试。 +- 在报告写入时,283 行本地要求通过,15 行交付/精确 CI 条件待外部附件确认,9 行明确不适用。最终附件逐行补齐交付条件,不能从本地数字推断 CI 已通过。 +- 实际 issue:已有 #13、#16、#19;总项 #27;E01–E12 对应 #28–#39;实现和验收中新发现的必要前置项 #40–#55。共 32 项,必须经分支验证后保持 Open,PR 逐行 `Fixes`,合并 master 后关闭。#18 仅关联本轮语义文档,用 `Refs #18`。 +- SDK 10.0.402、.NET 10、C# 14、BGCS 4.1.2。设备、驱动和系统原始数据见[环境](editor-ui-2026-10-30/environment.json):Ryzen 9 6900HX、RTX 3070 Ti Laptop、Windows 11;同机比较,不用共享 CI runner 推断性能。 +- 配套仓库:Canvas `eed5fd9b`、Rendering2D `067d4eb0`、Samples `3e331fd0`;本次收尾没有改它们的源码。正式 Canvas、Rendering2D、FlappyBird 脚本与 IDE 流程均通过,安装态调用由现有插件回归覆盖。 +- BGCS 仓库 `28945389` clean,Agent 文件为零;四份 4.1.2 包归档 SHA 与实施前一致,126 个被核对的安装条目与归档一致。见[包完整性](editor-ui-2026-10-30/bgcs-package-integrity.json)。这与真实 Restore/load 内容身份测试是不同证据,不能互相替代。 +- 原始日志 owner:`D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`。完整 TRX、binlog、失败尝试与运行文件清单保留在该 owner;可审查的原始采样及结果索引约 2 MiB,纳入本报告同目录。 + +## 实现闭合与职责 + +| 项目 | 实际实现与验收边界 | +|---|---| +| E01 / #28 | 作用域按实际 Begin/End、Push/Pop 规则配对;可扩展绘制失败只退休自身帧/owner;下一帧、另一 Session 与错误释放顺序有公开边界回归。 | +| E02 / #29 | 菜单路径、顺序、Action ID、gesture 候选按 extension generation 建索引;可用性、目标、参数和动态贡献每次请求重新求值;失败保留 last-good,旧 generation 不保留实例。 | +| E03 / #30 | 基础、通用组件与领域样式归属分开;Assets/Shader 规则不进入通用 metrics;2:3 表单、单滚动 owner、边界下拉和同帧尺度一致。 | +| E04 / #31 | Hierarchy、Browser、Console 使用过滤后可见顺序导航、anchor/range 和焦点策略;Console 搜索既有 occurrence/group,Collapse、详情和复制指向同一匹配结果。 | +| E05 / #32 | Graph 文档/controller/History 保持中立;Graph.ImGui 负责中立几何、命中和交互意图;Shader 自己解释类型与编译。第二种非 Shader 图真实使用相同公开呈现契约。 | +| E06 / #33 | EditorStatistics 是权威;未变快照与分组复用;Panel 自有 0.5 秒/60 秒/120 点数字趋势缓冲,暂停与关闭重置。 | +| E07 / #34 | 列表/网格按可见范围提交;树的显示序列按实际 revision/展开/过滤/排序更新;Console 高度缓存和 Graph 相关节点几何失效不成为第二份领域数据。 | +| E08 / #35 | 实际 Inspector、Hierarchy、Browser、Scene/Game、Shader、Settings、Export 与 Workbench 的正常、失败、取消和关闭边界有对应工作流;数据修改统一进入 feature 和同一 History。 | +| #13 / #16 / #19 | History 拒绝非 LIFO 完成时保持栈;SDK 实际 assets 与真实已加载内容一致、错配被拒绝;实际选定 SDK 向运行编译和 IDE 提供相同 references/符号/语言事实。 | +| E09 / #36 | 真实 ImGui Context + 公开输入覆盖 7 Zoom × 4 DPI × 2 尺寸,共 56 个尺度/尺寸组合;额外窄窗、遮挡、滚动、长文本、Unicode、各种状态单独覆盖。模拟 metrics 不称为物理显示器实测。 | +| E10 / #37 | 实际九类 Panel 各 100 次开关;Scene/Shader 文档各 100 次;连续十次成功/候选失败/Missing/恢复 reload;严格 GC→finalizer→GC 与弱 monitor 不放宽。 | +| E11 / #38 | 可运行原始 producer `b303d9f1` 与当前实现同 SDK、配置、输入成对交替,150 条初始记录;图尾部复查另加十个进程、每进程 50 帧,保留两批全部数据。 | +| E12 / #39 | 当前 XML、API Wiki、脚本导出、项目引用、Solution、文件树、十一维评价与逐条计划映射同步;移除确认无调用的直接 Hierarchy 删除路径。 | +| #40–#44、#52 | 输出锁问题修在 SDK 项目/输出 owner,而非杀 IDE 或全局串行化;工具验证使用本次 SDK 输入;Asset owner 保留身份并验证实际内容;Shader 输入冻结排除的仅是 Core 原子发布保留候选。 | +| #45–#46、#53–#54 | Core 既有 Writer、member capture、不可变规范快照和可选完整输入证明减少重复工作;Scene 对单次属性操作借用准确 Core allocator,完整 graph/subtree/Prefab 仍保留各自图引用语义。 | +| #47–#51 | 标准 Host 提供默认布局,缺失才原子创建;已有空/自定义文档不覆盖。Core reader 允许原子替换共享。官方未改 Noto CJK 字体按实际 Context/atlas 生命周期补字;成功退休后精确解除登记。Missing Scene 源分配和编辑 bytes 保留直到恢复。 | +| #55 | Compiler 完整 staging/校验仍归 Compiler;发布/替换复用 Core AtomicDirectory。取消前后、失败回滚与提交后清理语义明确,不增加脚本重试或平台分支。 | + +完整实际文件树见[源码 owner 树](editor-ui-2026-10-30/file-tree.md),实际 241 个工程、30 个 Editor 工程;不是未来目录图或空项目清单。执行链深审见[所有权复查](editor-ui-2026-10-30/ownership-review.md)。 + +## 最终生产实现的本地结果 + +| 检查 | 结果 | +|---|---| +| 源码、工具、两开发入口、Editor Debug/Release、选定 Player 与测试输出准备 | 11/11 阶段通过 | +| Contracts | 822 次通过、2 次 Unix 专用文件链接测试在 Windows 未执行 | +| Workflows | 704 次通过 | +| Visual | 524 次通过 | +| Lifecycle | 15 次通过 | +| Core Serialization / Scene / Core IO | 97/97、67/67、41/41 通过 | +| 正式配套项目脚本与 IDE | 三个项目全部通过 | +| 源码分层、工程图、公开依赖、XML、Wiki | 241/241 工程 owner 求值;34,582 API inventory;197/197 实际编译示例;零错误、零未求值示例 | +| Windows 产品 | Debug/Release × D3D11/D3D12/Vulkan,6/6 通过 | +| 完整输入冻结 | 16,406 个文件、3,207,829,122 bytes,测量前后完全相同 | +| 受控性能 | 初始 150 条记录、68 项比较逐项审阅;图尾部预先声明的额外十进程复验后,无未解决的可重复 >10% CPU/P95 或热分配回退 | + +各 profile 有重叠,不将这些次数相加当作独立测试数量。原始名称、outcome 和 TRX 路径见[用例索引](editor-ui-2026-10-30/test-cases.csv);全部产品循环资源样本、阶段状态与分配量见[本地结果](editor-ui-2026-10-30/local-acceptance.json)。不能把两次 Windows skip 记为 Unix 通过。 + +六组产品各完成 300 帧隐藏 smoke、八项真实 feature 工作流及实际 Panel 100 次开关。工作流会绘制实际 Panel,并通过公开 feature 操作;它与独立的原生指针/键盘测试共同提供证据,不能称为每一步物理鼠标端到端操作。循环资源采样 0–100 每十次记录,所有 pending events/selection 为零;最终 Full-GC managed 增量介于 -649,584 与 823,016 bytes,没有按已完成次数线性增长,均低于 4 MiB 预算。进程 working set 不要求回到一模一样。 + +GPU 观察记录了十二个确切归属的 workflow/lifecycle 进程与 NVIDIA C+G context 关联,不推断独占 GPU 或物理屏幕输出;WDDM memory 为 N/A。见[原始观察](editor-ui-2026-10-30/gpu-context-observations.json)。 + +## 性能:收益、回退审查与限制 + +同一 .NET 10 SDK 下比较源码实现,不把结果归因为运行时升级。以下是五个独立进程结果的中位数;不同负载不可互相比 FPS。 + +| 范围 | 原实现 | 当前 | 解释 | +|---|---:|---:|---| +| 10,000 Hierarchy 静态帧 median | 4,387.32 ms | 2.02 ms | Scene root 索引、可见提交与重复捕获改良;原始二次复杂度负载,不代表普通场景几千倍提速 | +| 10,000 资源静态帧 median | 3,431.32 ms | 15.39 ms | 相同输入与窗口下的可见工作;分配约 249.23 MB → 0.92 MB/操作 | +| 10,000 资源搜索 median | 10,467.93 ms | 73.16 ms | 显式搜索操作,仍有可观察延迟,不等同稳定帧时间 | +| 真实 History 连续属性编辑 median | 4,060.34 ms | 1.55 ms | 正常 SceneEdits/History 路径,未用直接标量赋值替换 | +| 菜单分配 | 8,544 B | 4,240 B | generation 静态结构复用,动态 Query 保持 | +| 未匹配 shortcut 分配 | 1,424 B | 48 B | 候选索引避免全量扫描 | +| 未变统计读取分配 | 16,792 B | 400 B | 复用权威快照;不是任意字符串解析缓存 | +| 产品 idle median / P95 | 9.88 / 30.86 ms | 9.63 / 25.52 ms | 实际同布局产品 CPU 帧范围,非 GPU fence/显示完成 | +| 产品 idle 分配 | 4.88 MB | 1.94 MB/帧 | 同输入测得约减少 60%;不是全产品所有场景保证 | +| 产品 Play median / P95 | 11.94 / 17.15 ms | 11.85 / 15.43 ms | Play 分配约减少 1.2%,不夸大为数量级改进 | +| 第一个应用帧 wall | 8.935 s | 8.929 s | 大致持平;源码激活 CPU 帧间隔反而 52.72 s → 56.65 s,约 +7.46%,仍在本轮门槛内,不能说所有启动阶段变快 | + +**原始图尾部标记保留。** 初始每进程十帧,nearest-rank P95 等于 max;P95 中位数 29.95 → 35.93 ms,约 +19.98%。按 #38 预先记录的复验规则,使用同一未修改 fixture 的正常五轮、每进程 50 帧,额外五组成对进程得到 P95 32.84 → 32.07 ms、CPU 40.00 → 36.875 ms/操作,分配 4.47 → 3.30 MB;没有一对 P95 超过 +10%。未挑最好一轮,所有帧和 outlier 保留。这支持“未重现持续回退”,不支持稳定 60 FPS、尾部完全无波动或已经找到噪声的具体外部来源。 + +十次独立 10,000 对象完整 dirty inspection(idle/edit)在不改变 100 ms 策略下均满足 median <16 ms、P95 <25 ms、分配 <8 MiB。最差 P95 **24.5695 ms**,余量较小,后续负载增加需要继续测量。不是只检查可见对象或跳过 getter/converter/Missing 验证。 + +[完整比较](editor-ui-2026-10-30/performance-summary.json)、[逐项审查](editor-ui-2026-10-30/performance-review.json)、[图尾部复验](editor-ui-2026-10-30/graph-tail-summary.json)及 `raw-*-measurements.json` 保留所有原始记录。短命令 CPU 的零值是计时量化,不宣称 CPU 开销为零。 + +## 视觉、输入和当前数据 + +[原始外观](editor-ui-2026-10-30/original-ui.png)与[当前外观](editor-ui-2026-10-30/current-ui.png)是同 1600×900 布局、相同数据的 native draw replay,包含真实 GPU viewport texture readback。Scene/Game 中游戏内容正常显示;主要停靠分区保持,菜单规整为统一动作入口,Stats 增加有界趋势。人工图像检查由本次助手完成,不冒称用户人工验收或物理屏幕截图。 + +当前 replay:1,827 triangles,零 unavailable GPU commands、零 unexecuted callbacks。未选择对象时 Inspector 为空是合法状态。两张图中已有的文件名裁切不当作新增回归。图上的瞬时 FPS 不作为性能采样。56 个 native metrics 组合的几何/交互断言与这一匹配图像对是两类证据,不声称拍了 56 组物理显示器截图。 + +默认布局在项目 `editor.ini` 缺失时由标准 Host 初始化;已有自定义或空文件保留。Selection、History、dirty Scene、runtime 引用不进入 INI。正常退出先捕获 Module/Panel,再取最新 layout,最后原子写入;恢复未成功不能覆盖已有状态。 + +## 深层自查与必要 API + +已自问并核对实际调用链:**职责是否真实清楚,是否为了性能破坏正确性,是否重复造基础设施?** 本轮未发现必须用妥协路径才能完成的剩余实现。具体依据: + +1. Core Events 输入进入一个 Session;Action generation 索引不是事件总线,Host 逆序释放栈也不是监听器注册表。 +2. 跨帧目标经 Core Identity;Scene 的 source-ID 投影只用于当前序列化输入,不成为 persistent-ID→live-object 的第二权威。移除的 System 不被结构缓存长期持有。 +3. Snapshot 和规范编码仍属于 Core Writer;可选 capture proof 必须完整,含副作用/上下文/不确定性的 Converter 继续使用权威捕获。同步借用 builder 退出回调即退休;中立缓存不保存 source、Type、delegate 或 ALC。 +4. 单属性修改使用当前 Core allocator 的同步 scope,逐引用校验 domain、注册、存活和 Scene。Graph local ID、Prefab 和 subtree 的完整映射仍由原 owner 处理。 +5. Graph 呈现不解释 Shader;Panel 不拥有领域事务;History payload 中立,失败不移动栈或提前释放回滚 owner。 +6. SDK 决策来自实际选定的 MSBuild 求值,Compiler 不依赖 Build/MSBuild/平台;没有最高 reference pack 猜测或配置 fallback。 +7. Compiler 发布使用现有 Core 原子协议,旧树回滚使用未取消 token,提交后清理失败不谎报未提交。原始外部 handle owner 未确认,不归责 BGCS/Roslyn/杀毒软件。 + +Core IO 的通用宿主文件系统实现也必须如实说明:create-only 发布在 Windows 使用 SDK 的不覆盖 move,在 Unix 使用公开 POSIX `link`,不支持相应原子能力的宿主/文件系统明确失败。这里包含 Windows/POSIX 实现区别,归既有 Core IO 原语,不等同于把产品平台 SDK、ABI、图形 backend 或部署策略放入 Core。Editor/Scripting 只消费中立契约;不能把“没有泄漏产品平台策略”写成“Core 没有 OS 分支”。 + +必要公开边界:中立 Graph node/port/layout/input/result;Host 注入不可变 ScriptCompilationInputs;Core member/snapshot/capture proof;EditorContext 可选默认文档;既有 AtomicDirectory.Install 的可选 CancellationToken;AtomicFile.IsCandidatePath 让外部构建输入 owner 复用准确的原子候选命名协议,识别结果不授予删除权限。它们有真实跨程序集消费者,且同步 XML、Wiki、项目引用和失败/生命周期语义;没有为了测试扩大 internal,没有保留旧 overload/facade。菜单、Console、Stats、source-ID 投影和编码缓存保持内部。 + +新增 Action/Drawer/Panel 使用现有发现协议;新增图领域只提供自己的 controller/语义和中立呈现;新平台仍实现其 provider/integration 并验证真实 SDK/ABI,UI 不增加平台 switch。没有为了基准降低 validation、reload barrier、Missing、History 原子性或输出完整性。 + +## 十一项评价 + +“满足”限定为本计划可执行的工程标准,不是没有后续改善空间。 + +| 维度 | 判断与证据 | 限制与合理改进点 | +|---|---|---| +| 架构 | 满足:feature/presentation/Host 方向明确;241 工程、公开依赖图与真实执行链一致 | 不替代全引擎其他开放 issue 的审计 | +| 代码整洁度 | 满足:按功能 owner 组织,删除无调用的旧 mutation 路径,项目/namespace/XML/引用分组有门禁 | 较大的领域协调器继续按实际独立职责演化,不追求机械拆文件 | +| 代码与状态干净程度 | 满足:Session/frame/document/generation 所有权、异常、取消、100 次退休与十次 reload 有证据 | 不能以 working set 不归零断言泄漏;新扩展需同样验证 | +| 抽象程度 | 满足:第二种非 Shader Graph、SDK→中立输入、可替换 Converter 有真实消费者 | 不声称未来任何能力都无需扩展契约 | +| 可复用程度 | 满足:Core IO/Events/Identity/Serialization、统一 History 实际复用 | 派生显示投影和有界采样属于领域状态,不应强塞入 Core | +| 样式统一 | 满足自动规则与已审图像范围:三层样式、表单、滚动、下拉、56 组合 | 物理屏幕、IME、可访问性和更广用户研究未执行,不能称视觉完美 | +| 可扩展程度 | 满足当前插件/图/菜单/Drawer 扩展标准,无中央领域 switch | 长期第三方采用和 API 演进仍需真实经验 | +| 操作逻辑性 | 满足已列工作流:可见选择顺序、焦点/遮挡、只读/Missing、失败/取消 | feature 驱动产品旅程不替代所有物理手势和主观易用性研究 | +| Undo/Redo | 满足:单一 History、即时手势、离散操作不错误合并、批量原子回滚、Missing barrier | 新领域操作需继续提供稳定 handler 与失败用例 | +| 菜单清晰度 | 满足结构标准:同一 Action 协议、fresh 状态、稳定分组、冲突诊断与索引复用 | 用语可发现性仍可根据实际使用改进 | +| 组件颗粒度 | 满足:Core/feature/presentation/Panel 以真实职责与资源边界划分 | 不为目录对称建空项目,不为行数再造 helper 层 | + +## 失败、未执行与资源说明 + +- 保留原始无法启动的基线尝试、旧候选失败、#40 输出竞争、#48 读写竞争、#55 raw Directory.Move 失败与红/绿回归。最终通过没有抹掉这些记录。 +- 第一次冷发布观察脚本误将首轮导入的 metadata 时间戳写入当作缓存失效:19 个 `.imeta` bytes/hash/ID 未变。修正的是外部观测分类;五个独立 cold/warm 请求确认 warm 源/metadata/cache 的集合、内容及时间戳均不变。没有放宽生产校验。 +- 三个非当前 Windows 目标的产品 API 编译没有在本地冒充执行;精确交付的 Mac 自动构建/测试/产品 smoke 由最终 CI 附件说明。人工 Mac、不同物理显示器、OS IME、Rider Run 点击体验、音频听感和长期运行未执行。 +- Browser Editor、新 Timeline、渲染模型和其他全引擎独立 issue 不属于本轮。此前 Browser Player/BGCS 专项证据保留原 revision,不并入本次 UI 数量。 +- 验收大输出优先在 D 盘;正式维护先 dry-run,再以默认预算、600 秒显式预算执行 `clean --scope cache`,exit 0。逻辑库存 90,213,428,162 bytes,退休 90,039,337,805 bytes,保护 174,090,357 bytes;C 盘空闲实测 77.79 → 90.32 GiB,增加约 12.53 GiB,D 盘仍约 5.78 GiB。见[维护日志](editor-ui-2026-10-30/cache-maintenance.log)与卷空间 JSON。只回收明确 owner、无活动读取者的可退休内容,不把 hardlink 逻辑字节当实际释放空间。 +- **自动审批曾拒绝**删除过期诊断 `e09/visual-capture/current/Runtime` 及 `e46/snapshot` 下四个旧诊断 runtime 目录,只返回 `blocked by policy`,未提供更具体理由。这五处保留,没有换工具绕过,也不宣称已释放其空间。 +- Windows 无指定 `/System/Library/Sounds/Glass.aiff`,并遵守静默操作要求,未播放提示音。 + +## 执行与复验入口 + +正式入口为 `tests/Invoke-EditorUiAcceptance.ps1`,提供 Contracts、Workflows、Visual、Lifecycle、Performance、Products、All,继续编排 xUnit/VSTest 及现有隔离测试;零发现、工具错误不算成功。产品需显式给独立项目副本、GraphicsApis 与实际初始化预期。 + +本次完整命令清单和阶段原始输出在 `candidate-dd5979c6/preparation.json`、`matrices.json`、各 profile summary/TRX;受控采样由同 owner 下 `e54/Invoke-FinalQuantitative.ps1`、预先声明的 `Invoke-GraphTailPairs.ps1` 编排正式公开 fixture。Wiki 使用正式 `tests/Invoke-WikiChecks.ps1`、Release、`snapshot-ordered` 输出及本次正式生成的逻辑 references。原始路径只作为证据 owner,不进入生产代码。 + +最终应再次核对:精确交付 diff 仅文档/证据,Git author/committer 均为 Aaron Liao,无 co-author;适用 CI 全绿;32 个 issue 的实际条件和英文结论齐全;PR 逐行 Fixes、#18 Refs;仍 Open 等待 merge。其最终签名、命令和检查链接统一记录在[精确提交附件](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338)。 diff --git a/records/architecture/acceptance/EDITOR_UI_OWNER_ACCEPTANCE_2026_10_29.md b/records/architecture/acceptance/EDITOR_UI_OWNER_ACCEPTANCE_2026_10_29.md new file mode 100644 index 000000000..7b5a512ca --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_OWNER_ACCEPTANCE_2026_10_29.md @@ -0,0 +1,108 @@ +## Continuation status — input coverage and product deadline + +This remains an in-progress report. Candidate `2434cd96` built the full neutral source, selected Player contract and twelve Release test suites; Core 97 and Scene 56 passed. Four actual input/edit preflights pass on each current/historical producer. Full profiles, controlled pairs, final products and CI remain required. A concurrent-load D3D11 trial completed all eight workflow stages but exceeded its unchanged 600-second process deadline, so it is **Failed**, not product acceptance. Retain its logs and investigate/repeat without weakening assertions. The historical visual replay now has both actual GPU viewport readbacks and no unavailable commands; final current replay/shutdown remains pending. The root closure ledger contains the authoritative remaining sequence. + +# Editor UI owner and acceptance continuation + +Latest refinement: #53 uses the current structured Writer's optional per-element capture callback, exact Guid inputs and bounded canonical encoding blocks. The all-or-nothing Scene root hint and rejected property-only prototype are removed. Core owns proof, effect classification, immutable nodes and encoding; Scene owns complete row inputs. Arbitrary nested converters invalidate enclosing reuse, including effects propagated from independent property payloads. Metadata certification, current identity validation, retired builders, canonical bytes and ordinary serialization remain required. No legacy overload, second serializer, identity table, event service or global block cache is introduced. + +Core 97 passes, covering independent non-Scene row reuse, changed/reordered/deleted rows, exact Guid validation, failed/declined keys, stateful nested property conversion, parent-converter effects, concurrent encoding, failed destinations, caller-owned arrays, bounded large-node streaming and the existing collectible barrier. Scene 56 passes before the final encoding refinement. A diagnostic producer using the ordinary SourceRelease graph and latest SDK-built Core passes all ten selected native cases; edit medians are 12.8–14.7 ms with ~2.51 MB allocation per inspection, idle medians about 9.5 ms with ~2.36 MB. All first-group spikes are retained. Formal full-graph, quiet five-process idle/edit pairs, complete profiles/products and exact-head CI remain pending. Earlier checkpoints below are historical, not the current architecture or completion claim. + +[Execution ledger](../EDITOR_UI_CLOSURE_PLAN.md) · [Parent #27](https://github.com/Wolfiora/InnoEngine/issues/27) · [PR #26](https://github.com/Wolfiora/InnoEngine/pull/26) + +This is a scoped continuation record, not whole-plan completion. Evidence root: `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. SDK 10.0.402 / .NET 10 / C# 14 / BGCS 4.1.2 remain unchanged. + +## Latest continuation: explicit capture inputs (#53) + +The dated checkpoints below remain historical evidence. The corrected complete Wiki case passed under `e52/wiki-corrected`; all six prior-candidate FlappyBird Debug/Release/API combinations passed under `candidate-panel-prepared`. Each includes renderer smoke, eight actual workflows, and nine actual panels with 100 cycles/full-GC observations. Mac physical/manual operation remains unexecuted. + +#53 now implements the reviewed Core serialization capture-key capability. The Converter callback remains optional, executes on every snapshot and must declare all output/context inputs. Key storage contains only current domain-qualified runtime identities, revisions, metadata stamps and neutral values. Core validates borrowed identities before reuse and before/after a changed write. A retained retired builder contains no context, metadata, source or generation references. Snapshots keep no reader, Type, delegate or live object. Failure leaves previous snapshots usable. + +The existing authoritative Converter resolver supplies proof; a key query never constructs converters or maintains a second resolver. Untracked/plugin properties, custom/stateful converters, unknown declarations, Missing and Prefab state retain complete ordinary capture. Fresh equivalent metadata keeps valid capture but declines reuse. Nullable private counters stop certifying at exhaustion; no serialized counter or public Transform revision member is added. Scene owns every relevant mutation; reflection remains in its existing DotNet adapter. Static providers can omit optional proof and keep identical complete serialization. + +Ordinary SDK results: Core 90 passed (`e46/production/core-capture-keys-final.trx`), Scene 56 passed (`scene-capture-keys.trx`). Cases cover canonical output, exact float bits, source/context/generation changes, actual local/world/property/full-scene restoration, arbitrary plugin state, stateful converter order, detached/rebound identity, callback thread/retirement and actual collectible Assembly/ALC retirement with a held snapshot and retired builder. A preliminary native run also passes the three source recovery/rename cases. It overlaps the build; 7.8–9.1 ms due medians and ~2.16 MB per inspection are feasibility evidence only. Quiet independent repeated performance and the complete final-source profiles/products/CI remain pending. + +Public boundary necessity: the protected Converter hook and borrowed builder allow actual external Converter implementations to certify inputs; metadata capability methods and the revision attribute let replaceable providers certify complete pure access without a central type list. No new project or production test entry exists. Core now explicitly references its existing Identity owner; dynamic member discovery stays in the adapter. Source/API documentation is synchronized and still requires the full signature/example gate. + +The complete twelve-suite ordinary SDK build passed with zero warnings/errors. A later repeat was explicitly canceled for allocation refinement and is not passed evidence. First quiet preliminary due groups measured 6.7–7.1 ms medians but retained a 39.6 ms P95, so #46 remains open. The actual identity-check scratch now borrows a BCL array during the callback and always returns it cleared in `finally`; unchanged-node reuse validates the borrowed checks before retirement and avoids a redundant copy. Changed writes retain independent checks until post-write validation. Core 90 passes again (`core-capture-borrowed.trx`). This eliminates measured repeated scratch allocation without introducing a custom pool, retained source or parallel identity authority; final quiet measurements must verify its effect rather than assume the tail cause. + +### Export-form failure and changed-state performance + +E01 now includes an actual provider failure in Game Export. The form balances its three Table scopes, including property-column setup, with local `finally` blocks. Native verification through registered `IGameBuildTarget.displayName` checks the active table, preserved diagnostic, quarantine and drawing after refresh. `e28/export-label/export-table-current.trx` passes; the copied prior official Workbench producer fails the exact open-Table assertion in `export-table-prior.trx`. This is expected negative evidence, not a passing test count. Initial ambiguous enum imports and a missing log-delivery barrier are corrected in the fixture; ordinary SDK leaf compilation reports zero warnings/errors. Runtime retires before its Session/context. No production test hook or assertion suppression is used. + +At #53 candidate `be44532f`, neutral source and selected Player contract builds pass; the expanded suite initially fails the new fixture's enum imports, corrected above. Five independent idle processes retain all fifty samples each: median 5.72–6.02 ms, P95 17.84–19.57 ms, ~563 KB per inspection and maximum 33.9–35.4 ms. The unfiltered raw ten-sample groups remain available. A new changed-state test and actual History/direct-write dirty correctness pass functionally, but every owned change still costs 68.6–71.1 ms and ~16.8 MB per inspection. This is an unresolved editing-path performance gap, not whole #46 closure. Next work must reuse the existing property capture and complete input proof without another cache authority, reduced checks or timer changes. + +## Implemented owners + +- `e3f02ebe`, `23f0ceb2`, `397ae3ed`: the existing SDL/ImGui backend owns one content-validated, pinned CJK font source per Context; four text faces borrow it until native destruction. Successful Context retirement unregisters the exact application owner. Pending/failure preserves ownership; application shutdown freezes its set and rejects new Context publication. The upstream font/license bytes are preserved, without OS font discovery, a font manager, BGCS or generated-binding changes. +- `8fd31cee`: Scene Workspace preserves an assigned persistent source identity, path, content and saved baseline when Asset Pipeline reconciliation marks the source unavailable. Ordinary Save refuses that unavailable assignment; explicit SaveToDirectory can establish a different source. Recovery, rename, Save and reopen use the existing asset/document protocols. +- `6277db99`: GameObject borrows the existing Core-indexed SceneObjectRecord for owner-local liveness checks. Add/remove/Clear/transfer preserve exact-owner attachment. Core scalar captures avoid repeated ordinal lookup while retaining duplicate rejection, canonical bytes and converter execution. No new identity table, event bus, serialization format, public API or compatibility path is added. + +## Ordinary SDK results + +| Gate | Result | Evidence / precise scope | +| --- | --- | --- | +| Complete neutral Release solution | Passed | `candidate-next/Contracts/7e2833f5448346a09253da45e91aa2cc/Contracts/source-build.log`; zero warnings/errors, before the final scalar/attachment refinements | +| Native glyph/context/retirement | 12 passed | `candidate-next/native-owner/fonts-context-selected-sdk.trx`; actual SDK output, including copied missing/corrupt font input and ten-context strict collection | +| Core serialization | 75 passed | `candidate-next/native-owner/core-scene-assignment.trx`; includes reordered canonical scalar keys and duplicate rejection | +| Scene | 53 passed | Same TRX; includes destruction, hierarchy transfers, restore and collectible recovery | +| Assigned missing Scene source | 3 passed | Same TRX; two clean/edited recovery cases and explicit SaveToDirectory without recreating the missing original | +| Expanded automated Visual | 524 passed | `candidate-next/Visual/76a7afdc3abf477d9a4521f651ce10be/summary.json`; actual native style preparation with independent zoom/density/size inputs, Console/Shader/input coverage; profiles overlap | +| Complete Contracts | Failed; corrections/reverification in progress | The initial run lacked a selected Player compile closure and an explicit partial-type XML summary. Both corrections are implemented. A later test-owned DLL cleanup failure remains under #16 investigation. Preserve the failed run, not a success claim. | + +The first direct native command used the machine's older default dotnet for child processes and failed two cases. The official isolation entry already injects the selected SDK; the corrected direct command follows that same boundary and passes all twelve. Exploratory pre-reconciliation Missing assertions were invalid: ID loading intentionally retains last-good content until Asset Pipeline observes removal. The retained failed TRX and corrected public-boundary tests distinguish that premise from the actual #50 fix. + +## Acceptance orchestration + +The UI Contracts entry first prepares the neutral source graph and SDK-evaluated products for its declared target, then the selected test graph. It does not reuse an Editor/neutral DLL as a Player reference or relax Wiki checks. CI moves its existing selected-Player preparation before dependent architecture/tests; no duplicate product table is added. YAML parsing and prerequisite order/uniqueness checks pass. + +Actual product acceptance reuses the existing journey runner for Workflows and Lifecycle. The latter requires every actual Panel to complete 100 cycles, eleven full-GC observations and at most 4 MiB final live-memory growth. These requirements precede measurement; the new integrated product run is pending. Unattended windows are hidden. This does not certify physical monitors or manual visual quality. + +## Deep review and remaining sequence + +The execution-chain review preserves current owners, Core reuse, SDK output isolation, strict generation barriers, rollback, Missing state and failure diagnostics. Attachment backlinks and font completion callbacks do not create another authority; neither persists plugin instances or generation delegates. No BGCS, package, third-party source or generated binding is modified. + +1. Resolve the #16 cleanup observation without weakening context/assembly retirement or altering restored packages; complete corrected Contracts. +2. Freeze and verify all current source/workflow/visual/lifecycle gates and actual FlappyBird Debug/Release/API journeys and panel loops. +3. Complete #46 and controlled original/current performance. Earlier five independent due-inspection processes remain 41–43 ms and about 8.37 MB. Later 37 ms complete-inspection and 1.35–1.87 ms metadata-only prototype diagnostics are not final paired acceptance and do not pass the absolute latency budget. +4. Finish matching visual evidence and all original-plan rows, update each issue's actual checklist/evidence, then produce the eleven-dimension final assessment and exact-head CI result. + +PR #26 and parent #27 remain incomplete while these gates are open. No physical Mac/manual display claim is made. Previously rejected cleanup of four diagnostic runtime copies remains unperformed; automatic approval review returned `blocked by policy` without a detailed reason. User/SDK/NuGet global data is untouched. + + +## Exact-candidate continuation + +At `564e00d0`, ordinary Release Workflows passed 699 and Lifecycle passed 15. Debug and Release official Editor builds succeeded; each reported that automatic cache maintenance exceeded its five-second budget and retired no artifact. This warning is retained, not reported as a zero-warning product build. + +The complete Contracts source build failed on a live `Vertex.ishadersource.imeta.staging-` file. New issue #52 fixes actual source ownership: Core IO recognizes only its reserved candidate naming; source policy reuses it; shader build captures permanent sources and import metadata, hashes the same neutral declarations used by the Task, copies into a `ProcessOwnedDirectory`, and imports only the copy. Complete source inventory/content is verified before publication. Generic Native hashing still includes explicitly declared candidate-shaped files. + +Initial ordinary-SDK #52 checks: shader input cases 7 passed, Core IO suite 37 passed, build zero warnings/errors. Complete concurrent build and final Wiki acceptance remain pending. Related test-entry review also fixes a local product-evaluation variable overwriting its test solution selection. + +The strengthened #16 identity regression passed ten normal/isolated groups (40 executions), observing both loaded Assembly and collectible Context retirement. One historical test-copy deletion denial remains unexplained and preserved; no cleanup suppression, increased timeout or retry is added. + +A diagnostic catalog-only assignment prototype separately measures the existing `TryGetInfo` boundary for document path/status instead of importing source contents during a label query. It remains outside production pending semantic tests and controlled measurements. Its noisy complete inspection median is 31.24 ms, with an initial 123 ms P95; it does not meet #46 and is not closure evidence. + + +### Shader input boundary refinement + +The frozen shader identity includes permanent directory inventory as well as source/import-setting bytes. Empty authored directories are preserved; transient atomic file names and derived artifacts remain excluded. The Task consumes the backend-owned frozen fingerprint, not its physical file list. Core IO candidate-name recognition does not authorize deletion and general Native input hashing remains unchanged. + +The compiler now borrows an explicit `ProcessOwnedDirectory` supplied by actual callers. Standard distribution registers `artifacts/build-tools/shaders/requests`; Task/CLI retain the directory through completion, and existing owner-exclusive maintenance retires absent payload claims and leaf locks. There is no new unmanaged global temporary owner. Workspace retirement precedes authoritative Task publication. + +Nine updated focused cases pass using actual SDK compilation plus an evidence-owned copy of the already built native test runtime (`e52/frozen-input-workspace.trx`); this is distinct from a complete final normal build. Normal shader-library build and focused test compilation report no warnings/errors. Initial full workspace build passed with four xUnit blocking-operation warnings; the tests now use async/await, and the frozen compilation has no such warnings. The earlier eight-case run and all initial failures are retained. + +The first simultaneous source/Editor experiment is not a concurrency pass: source build passed, while Debug correctly rejected changed `bgfx-tools` inputs after the Core IO source was edited during the build. `NativeBuildRecipe.Create` explicitly includes `AtomicFile.cs`; the strict post-wait validation is retained. Re-run with a complete frozen source inventory. Cache maintenance inspected 29,060,240,489 logical bytes, protected 5,345,891,157, reclaimed zero under the current budget and skipped 441; no unknown data was deleted. C free space subsequently measured 83.27 GiB, D 29.66 GiB. + +The Contracts source prerequisite now selects the existing `SourceDebug` / `SourceRelease` Solution configuration, preserving a neutral graph and preparing selected product references separately. Product evaluation no longer overwrites the test-solution path. Final SDK/contract and Wiki checks remain required. + +### Frozen 329568ba verification and lifecycle preparation + +- Ordinary Editor Debug and Release builds passed with zero errors. Each reports the existing automatic cache-maintenance budget deferral warning; no artifact was retired after the budget expired. +- Two independent output owners completed while the exact test-created metadata candidate remained exclusively open. The source inventory contains ten permanent files before and after, with zero path/hash differences (`e52/frozen-authored-comparison.json`). The guard released and removed its exact candidate in `finally`; it did not modify authored metadata or authorize cleanup of unrelated candidates. +- Complete Contracts executed 816 passing cases, one failure and two platform workspace-link fixture skips. The single failure is a Wiki content-boundary violation in `CURRENT_ISSUES.md`, not a shader/compiler/build failure. Its external consumer name has been replaced with the engine-owned description; the validator remains unchanged. The complete focused Wiki rerun is recorded under `e52/wiki-corrected` and is pending at this checkpoint. +- The initial actual Debug/D3D11 product completed its eight workflows and all nine panels' 100 cycles, but retained 4,488,688 bytes against a 4 MiB budget. That run remains Failed. Public diagnostic heap observations identify initial asset/render preparation followed by a plateau; the tool warns that .NET 10 uses lossy heap-event buffering, so heap counts are diagnostic rather than complete ownership proof. +- A separately prepared diagnostic drew all panels for 120 frames before its baseline and retained 712,056 bytes over the subsequent 100 cycles. The ordinary fixture now records both cold preparation observations in `panel-lifecycle-preparation.tsv`, then runs the existing eleven full-GC observations and unchanged limit. Pending events must also return to zero. Fresh ordinary Debug/D3D11 and Debug/D3D12 product runs passed under `candidate-panel-prepared`; other configuration/API cases remain pending. +- The public presentation SPI produces actual native UI geometry and available CPU atlas pixels after rendering, without desktop control. Initial captures were rejected for stale readiness/initial GPU preparation. A later capture verifies both viewport image commands; software replay marks those two unavailable GPU textures explicitly. This is UI geometry/atlas evidence, not a physical screenshot or GPU-content claim. The matching original producer is being captured independently. +- #46 experiments, including metadata-only source queries, topology-owned capture maps, primitive-field comparison and explicit state revisions, remain outside production. Reduced allocation or a best timing does not meet the absolute latency gate. No new serialization/domain API is committed on the strength of these experiments. + +The deep review still finds outstanding quantitative and original-plan evidence. Parent #27 and PR #26 remain incomplete; none of the results above is whole-plan closure. No BGCS, package, generated binding or vendor source was changed. Rejected cleanup of four earlier owned runtime copies remains unperformed; no alternate deletion route was used. diff --git a/records/architecture/acceptance/EDITOR_UI_PACKAGE_PROBE_ACCEPTANCE_2026_10_30.md b/records/architecture/acceptance/EDITOR_UI_PACKAGE_PROBE_ACCEPTANCE_2026_10_30.md new file mode 100644 index 000000000..cb741a704 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_PACKAGE_PROBE_ACCEPTANCE_2026_10_30.md @@ -0,0 +1,26 @@ +# Loaded-package probe ownership + +This focused #16/#27 record does not close the complete UI plan. PR #26 remains Draft pending final applicable suites, products, measurements, documentation and CI. + +## Observed failure + +The complete Contracts run at `1917b253` reproduced three ordinary `NuGetLoadedIdentityTests` cleanup failures. `Directory.Delete` reported `UnauthorizedAccessException` for fixture-owned `BGCS.Runtime.dll`; the finalizer cleanup can obscure an earlier probe failure. The isolated case passed in the same run. Package file attributes were observed as Archive, not ReadOnly. No BGCS defect, independent native-loader defect or antivirus cause is inferred from these observations. + +Original logs/TRX are retained under `candidate-1917b253/Contracts/5696b1bd510d4465ae1544b4b6e61c05` in `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. The run remains Failed, not replaced by its later focused passes. + +## Correction + +The existing test-process invocation pattern now owns the physical negative DLL load. Parent cases still perform real SDK Restore, consume the evaluated ProjectAssetsFile, preserve stale/default versus independent output semantics, and compare the actual loaded expected runtime with its package content. A fresh writable copy is created from package bytes; the package itself is never modified. + +The owned child uses the existing public test entry, physically loads the same-name/same-MVID/different-content DLL through AssemblyLoadContext, verifies content rejection and performs the unchanged full-GC/finalizer/full-GC plus weak-context/weak-assembly assertions. A verdict marker is written only afterward. The parent requires that marker and successful child completion before normal directory deletion. No process-isolation assertion, empty discovery, timeout, cleanup error or package mismatch is ignored. No LoadFromStream, IVT, private reflection, production API or platform branch is added. + +ToolchainEnvironment and DotNetSdkEnvironment remain the existing process/SDK boundary. The change is confined to the test fixture and this evidence record; production code remains exactly `1917b253`. + +## Focused results + +- Independent BCL process-ownership prototype:10/10 actual mismatch/unload/exit/deletion trials pass. +- Ordinary SDK fixture build: succeeds. All four declared public fixture cases pass. +- Existing `Invoke-IsolatedTests.ps1`, selected single-project solution filter and common NuGetLoadedIdentity filter:10/10 entry runs pass. Each run executes three shared scenarios and one required isolated scenario, including their child probes:40 parent scenarios total. Actual restoration, content, MVID, weak collection, nonempty verdict and cleanup checks remain enforced. +- Evidence: `e16/process-owned-fixture/identity.trx`, `repeat-0` through `repeat-9` execution summaries/TRX; `e16/process-image-owner/trials.log` is prototype evidence only. + +Final full-suite and exact-head CI verification remain required. No unrelated engine issue or complete-plan requirement is marked resolved by these focused results. diff --git a/records/architecture/acceptance/EDITOR_UI_PLAN_REVIEW_2026_10_27.md b/records/architecture/acceptance/EDITOR_UI_PLAN_REVIEW_2026_10_27.md new file mode 100644 index 000000000..fdddc7f86 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_PLAN_REVIEW_2026_10_27.md @@ -0,0 +1,168 @@ +# Editor UI 原计划逐项复审 — 2026-10-27 + +[当前验收](../../../docs/architecture/CURRENT_ACCEPTANCE.md) · [当前问题](../../../docs/architecture/CURRENT_ISSUES.md) · [实现记录](../EDITOR_UI_IMPLEMENTATION_PLAN.md) · [前次验收](EDITOR_UI_ACCEPTANCE_2026_10_27.md) · [PR #26](https://github.com/Wolfiora/InnoEngine/pull/26) + +## 结论与审查范围 + +**尚未完整达到原计划的完成标准。** 当前已经形成较好的工程基础,尤其是领域与呈现分层、Session 所有权、Core Identity 路由、统一 History 和回归测试。但仍有明确的功能、抽象和性能实现缺口,也缺少原计划要求的完整规模、生命周期和视觉验收。不能把这些统一解释成“只剩人工看看”,也不能把 CI 全绿解释成整个计划完成。 + +前次报告中的 “The implementation and local correctness checks are complete” 范围过大,本次予以纠正。已有通过结果继续有效;改变的是对它们能够证明什么的判断。本次没有发现足以否定全部重构的证据,也没有据此认定存在新的 BGCS 缺陷。 + +- 审查源码:`2dc37114b7c9b5137e866efc057a66e0dfd72046`,分支 `editor-ui`。 +- 远端 master:`cf799c8280f74f6faa19916a89f216778391fcd9`;PR #26 已存在,base 为 master,状态为 Draft。 +- 原始计划:用户提供的 `InnoEngine_Editor_UI_Plan.md`;SHA-256 为 `a55f25b76db09befd6cdd2db06a607d0e2a70d660dca6ca5429c64f3d5623646`。 +- Editor 范围:30 个项目、507 个 C# 源文件;PR 相对 master 共 373 个变更文件。此次沿关键路径深查,并不等于对每个分支作形式化正确性证明。 +- 方法:对照原计划条目、项目依赖、执行链和测试源码,重新解析最终 Windows/macOS CI 的 TRX,并重新构建运行 Interactions 测试。没有使用 computer use,没有重新执行全产品矩阵。 +- 本次修改范围是审查记录、当前文档与规范;没有修改生产代码、测试实现、BGCS、平台或生成绑定。 + +## 十一个维度的逐项评价 + +| 维度 | 已经成立的工程性质 | 尚未达到的要求 / 改进点 | 判断 | +| --- | --- | --- | --- | +| 架构 | Features 不依赖原生 UI 或 Panels;Panels 不互相引用;Assets 操作、Export 生命周期与绘制分开;相关边界有架构测试 | Graph 通用呈现只抽出了坐标、网格、曲线和连线命中,节点呈现、节点命中、框选等仍在 Shader Canvas;不能仅凭新增程序集判定抽象完成 | 主干达标,原计划未全闭合 | +| 代码整洁度 | 功能目录与 owner 更明确,公开依赖分组、Solution、脚本导出及调用方同步 | Shader Canvas 主文件约 1,110 行,多个 partial 共享 draft/controller 状态;部分源码同时使用多个指向同一 ImGui facade 的别名,阅读仍有噪声 | 明显改善,尚非全面清晰 | +| 代码干净程度 | Session/owner 退休、native borrow 校验、弱目标、Core Events 订阅所有权均有实际实现;没有靠全局 UI 状态表或额外 Undo 栈解决问题 | 全量异常退出与长期资源回收证据不足;Console、Shader 绘制局部仍有手动 Push/Pop 缺少局部 finally 的路径,需要逐个证明异常安全 | 核心机制较强,不能认证无泄漏/无残留 | +| 抽象程度 | Action、History、Inspection、Graph 文档和领域 Feature 边界实际有消费者;没有单靠空接口实现“解耦” | Graph presentation 抽取不足;通用样式中仍保留具体 Browser 布局策略;应沿真实复用点提取,避免再造万能 UI 框架 | 部分达标 | +| 可复用程度 | Core Events、Identity、Serialization、IO、History 和 Graph 被复用;资产操作不再只能经 File Browser 使用;脚本 SDK 直接引用真实 API owner | 通用节点呈现/命中/框选尚不能独立复用于非 Shader 图;需要第二种真实图呈现验证抽象边界 | 主要基础能力达标,图呈现仍有限 | +| 样式统一 | Session 统一 metrics/palette,表单、树、菜单和有界下拉有共享控件,已有缩放和布局回归 | 三层样式尚未完全落实;Browser 专属列比例、图标尺寸策略等仍在通用 metrics;没有完整 Zoom/DPI 与同条件前后截图矩阵 | 部分达标,未完成视觉验收 | +| 可拓展程度 | Attribute/Registry、Action 查询与执行、Drawer 发现、脚本导出按现有协议接入;有候选切换、Missing 和恢复测试 | 新 Graph 呈现不改 Shader/核心的完整接入证明不足;连续 10 次 UI/plugin reload 与旧 ALC 回收证据缺失 | 扩展基础达标,完整承诺未验收 | +| 操作逻辑性 | 有序选择、anchor/range/toggle、多对象修改、Play identity 路由和编辑手势具备共同契约 | Console 搜索未实现;统一键盘选择链没有完成证明;Stats 趋势未实现;真实完整编辑→Undo→保存→重开旅程不能由帧数代替 | 部分达标 | +| UndoRedo | 非 LIFO 拒绝先校验再弹栈;同一 History 处理手势、批量操作和中立 payload;失败保留操作,Missing 可恢复;公开边界回归较强 | 全 Panel 生命周期、键盘/鼠标混合输入、连续 reload 的端到端矩阵仍未完成;不可将单独 handler 测试当成全部界面旅程 | 本轮最扎实的部分,核心契约达标 | +| 菜单清晰度 | 菜单/快捷键/工具栏/搜索共享 Action Query/Execute;已有冲突诊断和禁用原因契约 | 静态菜单结构及 shortcut 候选尚未按 generation 建索引;目录顺序/无重复/禁用原因覆盖和完整键盘旅程仍需验收 | 功能基础达标,结构与成本未完全达标 | +| 组件颗粒度 | 领域操作、呈现和 Panel 的项目粒度合理;大部分功能文件有明确 owner | Shader Canvas 仍集中多类交互,Console 手动布局与通用样式仍需收拢;应按可独立验证的职责拆分,不以文件数为目标 | 主干合理,局部仍偏粗 | + +因此,不能将当前整体认证为“企业级、现代级、现象级全部达成”。其中现代化的生命周期与分层基础已经存在;“现象级”还包含主观使用体验,自动测试不能给出这样的结论。 + +## 真实调用链与边界自查 + +### 命令、选择与修改 + +实际链路是输入 → Session/Widget → Action 或 Feature → owner 的序列化上下文 → 统一 History → 当前 generation 的 handler。Panel 不应自行建立第二套业务可用性、序列化、对象索引或 Undo 协议。 + +`EditorActionRouter` 的 Action ID 索引确实按 discovery 失效;执行时仍重新查询上下文。`EditorExtensionCatalog.ValidateActions/ValidateShortcuts` 会报告冲突,不能因为运行时排序存在 tie-break 就认定没有冲突检测。跨帧目标通过 Core Identity 或受约束的弱引用解析;Play allocator 的 routing lease 只路由,不是另一张 live-object 权威表。 + +### Session 与基础能力复用 + +`EditorUiSession.current` 是 ThreadStatic 同步作用域入口;状态由实例的 widgets/menus 持有,不能简单把出现 static 视为全局状态泄漏。Enter/Dispose 检查线程、嵌套和上下文,owner 退休会清理自己的控件/菜单状态。非 Identity target 只有明确中立标量强持有,包含引用的 boxed struct 不被错误视作纯值。 + +本轮关键链复用了 Core Events、Identity、Serialization 与现有 History;没有发现本次 UI 重构新增平行事件总线、Undo 栈或对象身份系统。这个判断限定于已检查的 UI 改动,不能替代全引擎所有事件/缓存实现的审计。 + +### 平台和第三方边界 + +这些 UI 缺口属于 Editor owner,不要求修改 BGCS、Native generated、第三方源码或平台层。前次 BGCS 4.1.2 包完整性与平台无改动证据继续有效,本次没有重新把 122 个文件的历史校验写成新执行。Mac CI 已通过真实 ARM64 自动运行;本地没有 Mac 交互设备,人工 Metal/视觉检查仍未执行。 + +## 确认的实现缺口与待验证风险 + +以下“确认”表示从源码可直接定位的现状;没有测量的数据不标为性能回退,没有复现的异常不标为已发生故障。 + +### U01 — 静态菜单与快捷键索引未落实 + +- 原计划:第三节 Action/菜单与性能条目,静态结构按 extension generation 索引,可用性 fresh 查询。 +- 位置:`framework/Inno.Editor.Interactions/Menus/EditorMenuCatalog.cs:14–112`;`Actions/EditorActionRouter.cs:179–220,350`(均相对 `src/composition/editor`)。 +- 当前 `Build` 每次遍历 registrations、收集 placement、规范化并拆分路径、排序和构造树;shortcut dispatch 每次收集全部 Action ID 和候选。 +- 已有 `m_byId` 不等于静态菜单或 shortcut 索引。动态菜单也不能被整体缓存成过期 enabled/checked 状态。 +- 完成方式:generation 内固定静态 metadata/候选索引;上下文、动态 contribution 和 Query 继续 fresh;候选切换时替换并释放旧 generation 引用。复用现有 catalog,不另建全局 cache service。 +- 验收:重复打开不重建静态结构;新增/卸载扩展正确更新;上下文变化立刻生效;冲突仍明确失败;对菜单打开至少五次采样 CPU/分配,包含旧 ALC 退休。 + +### U02 — 大数据视图与 Stats 尚未满足成本要求 + +- `FileBrowserPanel.List.cs:73` 遍历全部 entry,Grid 同样逐项提交;Hierarchy 每次绘制重新构造 visible-selection 列表。Native clipping 不等于避免 managed 每项处理。 +- `ShaderEditorCanvas.Node` **已有离屏矩形提前返回**(约 622 行),不能声称 Graph 完全不裁剪。但 `PortPositions()` 仍为全图创建 Dictionary 并计算端口,绘制/命中仍遍历图。是否构成严重瓶颈必须通过 2,000 节点实测判断。 +- `EditorStatistics.GetSnapshot():230` 每次合并 Dictionary、排序和 ToArray;`StatsPanel.OnDraw():35` 每次 GroupBy/Select,并重新构建字符串。它有 0.5 秒帧时长采样器,但不能据此宣称展示只读取复用采样快照;趋势显示尚不存在。 +- 完成方式:列表按可见范围提交,保留编辑/拖动/展开状态;显示数据按实际 revision、过滤、排序和尺度更新。Stats 的采样与不可变展示快照由明确 owner 管理;Graph 先测全图成本,再决定必要的增量几何复用。不能用复杂池掩盖重复工作。 +- 验收:10,000 项列表/树、2,000 节点图,空闲/滚动/搜索/拖动至少五次重复,CPU、分配、GC、median/P95;不得丢失多选或拖动状态,不得降低刷新正确性。 + +### U03 — 通用 Graph 呈现与领域样式边界仅部分完成 + +- `presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs` 提供坐标转换、网格、连线绘制和连线命中。它保持无 Shader 依赖,是实际有效的抽取。 +- 原计划还要求通用节点/命中/框选;这些行为仍与 Shader 的端口、默认参数、文档 draft 交织在 `ShaderEditorCanvas`。partial 文件分开并不自动解除共享状态依赖。 +- `EditorStyleMetrics.cs:230–562` 集中 Browser pane、breadcrumb、列分隔比例及图标布局等策略。它们不是平台耦合,也不等同于“包含资产类型白名单”,但三层样式中领域 owner 的责任仍不够清楚。 +- 完成方式:由真实第二种图消费者验证最小可复用节点、命中和框选输入/结果;Shader 只贡献自身端口/参数/颜色语义。Browser 专属布局值由 Assets presentation owner 组合通用尺度,通用层不反向认识 Browser。 +- 验收:非 Shader 测试图可使用同一呈现机制;领域专属类型不泄漏进通用层;节点选择/框选/缩放/连线与 History 语义保持;样式同帧一致、API/脚本/Wiki 同步。 + +### U04 — Console 搜索与键盘操作验收未闭合 + +- `ConsolePanel.DrawToolbar():102` 只有 Collapse、级别 Filter、Clear;DrawEntries 只有级别筛选。原计划明确要求的文本搜索没有实现。 +- Console 没有统一的条目键盘选择模型。Hierarchy 的共享 Tree 返回鼠标 clicked/doubleClicked;原生 Tree 可以处理部分导航,但这不足以证明它与引擎 ordered selection、范围选择、焦点和批量命令一致。 +- File Browser 使用原生 Selectable,不能因此断言完全没有键盘激活;缺少的是原计划统一键盘选择/焦点行为的完整证明。 +- 完成方式:Console 搜索使用现有 Diagnostics snapshot 与 revision;键盘动作接入既有 Action/Selection,不能新造列表专用事件系统。明确焦点、上下移动、范围扩展、激活、取消和只读行为。 +- 验收:真实公开输入驱动 Console/Hierarchy/File Browser,覆盖过滤后导航、Shift 范围、Ctrl 切换、输入框与 Popup 遮挡;结果与鼠标操作一致,批量修改仍原子化。 + +### U05 — 异常栈平衡与长期退休仍有证据缺口 + +- Shader 节点端口标签局部 `PushClip`/`Text`/`PopClip` 没有局部 finally;Console 也有部分 Child、style 和 ID push/pop 直接顺序配对。 +- 这是源码审查得到的异常安全风险,不是本次已复现的崩溃。正常产品跑通不能证明中途异常一定平衡,也不能在没有可触发路径时武断判为 P0。 +- 完成方式:在已有 owner/borrow 边界补齐成对资源的异常安全;通过实际可替换 public boundary 注入失败,确认原生栈和后续绘制可继续。没有必要引入一个覆盖所有 UI 的新异常框架。 +- 再执行 100 次 Panel/Document 开关、10 次脚本/plugin reload、Host 退出资源检查;记录 UI owner、订阅、gesture、旧 ALC weak monitor 和 GPU 资源。保持严格 unload barrier,不放宽超时。 + +## 原计划验收对照 + +| 原计划条目 | 当前有效证据 | 本次判定 | +| --- | --- | --- | +| 无 Panel→Panel、feature→UI/native 依赖 | 架构验证与当前项目引用 | 已通过该边界检查;不代替全部内部职责审查 | +| History LIFO、手势、失败/Missing、批量 | 当前源码、121 项 Interactions 本次复跑;Windows/Mac CI 对应回归 | 核心契约通过 | +| 无 native borrow 越界、Session owner 退休 | UiSession/native widget tests,关闭 fixture | 有针对性证据;不等于全 UI 长时认证 | +| Static menu/shortcut generation index | 仅 Action ID 索引存在 | 实现未完成,见 U01 | +| 通用节点/命中/框选 presentation | 通用坐标/网格/曲线/连线命中存在 | 部分完成,见 U03 | +| Console 搜索、键盘选择;Stats 趋势 | 搜索/趋势未实现,键盘矩阵不足 | 未完成,见 U02/U04 | +| 7 档 Zoom × 可执行 DPI 场景、长标签/遮挡/焦点/中文输入 | 有若干真实 native layout/输入测试 | 不能推断全矩阵已通过 | +| 同布局、数据、尺度和尺寸的前后截图 | 有 FlappyBird 截图 | 没有完整成对视觉证据 | +| 当前 UI 操作、菜单与三层样式规范 | 有 `EDITOR_UI_ARCHITECTURE.md` 架构说明;原计划的 `EDITOR_UI_DESIGN_STANDARD.md` 未落地 | 架构说明不能直接代替操作/样式设计规范;应补足实质内容,文件名本身不是完成标准 | +| 10,000 项树/资源列表、2,000 节点图;至少五次 UI CPU/alloc/GC/median/P95 | 只有独立 UTF-8 编码 microbenchmark 和产品帧运行记录 | 未执行完整规模矩阵,不能宣称整体提速 | +| 100 次 Panel/Document 开关 | 有单次/定向生命周期测试 | 没有计划要求的 100-cycle 结果 | +| 连续 10 次脚本/plugin reload、严格旧 ALC 回收 | 有 reload/Missing/GC 定向测试 | 没有整个 UI 场景的 10-cycle 结果 | +| Windows Debug/Release、D3D11/12/Vulkan、Play→Edit | 本地 FlappyBird 和远端产品 smoke | 有界产品路径通过;不是完整人工操作旅程 | +| Canvas/Rendering2D 当前脚本及 SDK/API 投影 | 新 SDK probe、Rendering2D 42 项、正式包导出 | 已有证据按前次记录范围成立;不能扩大为所有插件操作已验收 | +| Mac CI | 最终 ARM64 job 通过 | 自动验收通过;本地/人工 Mac 交互未执行 | + +原计划明确允许通过真实 ImGui Context 和公开输入完成测试,**不依赖 computer use**。因此此前窗口激活失败只能说明人工操作没执行,不能解释或豁免完整自动几何/输入矩阵的缺失。 + +## Issue 核对 + +| Issue | 源码与证据结论 | 处理 | +| --- | --- | --- | +| [#13](https://github.com/Wolfiora/InnoEngine/issues/13) History 非 LIFO 拒绝破坏栈 | `EditorHistory.PopTransaction` 先 Peek/校验,再 Pop;Commit/Rollback/Dispose 三种拒绝后仍可正常回滚/Undo。最终 Windows/Mac TRX 各三项通过;本次 Interactions 复跑通过 | 已在本分支修复,PR 添加 `Fixes #13`;合并前不把 master 标为已修复 | +| [#16](https://github.com/Wolfiora/InnoEngine/issues/16) 固定默认 assets 路径 | 当前及 master 测试都由 SDK Restore 返回实际 `ProjectAssetsFile`,使用独立 ArtifactsPath;两平台最终 TRX 通过。旧描述的固定路径已不存在 | 根因已修正;原 issue 还要求真实 package/load mismatch 拒绝等完整矩阵。本次没有新的 mismatch 反例证据,不直接宣布全部关闭 | +| [#18](https://github.com/Wolfiora/InnoEngine/issues/18) API XML/Wiki 语义 | UI 新边界已补文档,但 Stats 等仍有泛化描述,全仓语义审查不因 XML/Wiki gate 通过而结束 | 保持开放;与本次文档质量有关 | +| [#19](https://github.com/Wolfiora/InnoEngine/issues/19) 多 SDK reference 一致性 | SDK 直接引用 API owner 修复了另一种依赖问题;没有证明多 pack/root 输入一致 | 保持开放,不混淆两类问题 | +| #14 / #15 | 热 Native 准备成本、自动维护预算收敛 | 0 native processes、显式清理成功都不是成本/自动预算验收;保持开放 | +| #17 / #20 / #21 / #22 | 文件系统语义、原生编码、Runtime coroutine、Browser 诊断 | UI 改动和本次审查没有关闭这些问题的证据 | +| #23 | NuGet 锁文件治理 | 已关闭且被标准 PackageReference 流程接替;不重新引入锁清单 | + +没有因为 issue 标题看起来相近就直接关闭,也没有将旧问题重新包装成此次 UI 回归。U01–U05 在本报告给出 owner 和验收条件,作为 PR 尚未完成的计划清单;本次没有批量新建重复 issue。 + +## 测试与证据 + +本次重新运行: + +```powershell +dotnet test tests/editor/Inno.Editor.Interactions.Tests/Inno.Editor.Interactions.Tests.csproj --no-restore -c SourceDebug --logger "trx;LogFileName=interactions.trx" --results-directory D:/InnoEngine-Verification/editor-ui-2026-10-27/plan-review-tests -v quiet +``` + +结果:**121 passed、0 failed、0 skipped**。此命令重新构建当前源代码,不以 `--no-build` 的旧程序集替代。日志 `plan-review-interactions.log`;TRX `plan-review-tests/interactions.trx`。 + +本次重新解析最终 CI 原始归档(不是重新运行 CI): + +| 来源 | 结果 | 适用限制 | +| --- | --- | --- | +| [Engine CI 40444183857](https://github.com/Wolfiora/InnoEngine/actions/runs/40444183857),head `2dc37114` | Windows 2,230 pass / 18 skip;macOS ARM64 2,247 pass / 1 skip;均 0 fail | 环境相关 skip 保留;自动产品 smoke 不替代完整人工旅程 | +| 两个 managed job | 各 1,185 pass | 与完整 suite 重叠,不相加 | +| [NuGet graph 40444183878](https://github.com/Wolfiora/InnoEngine/actions/runs/40444183878) 与 submission | 通过;该 head 共 6/6 checks success | 不代表原计划缺失条目已经被 CI 覆盖 | +| 本次从两平台 TRX 定向抽取 History/SceneHistory/UiSession/GraphCanvas/包身份 | 每平台 54 条通过,包含 #13 的三种拒绝和 #16 还原身份 | 子集证据,不与上列累加 | + +证据 owner 为 `D:/InnoEngine-Verification/editor-ui-2026-10-27`,新增 `plan-review-evidence.json` 保存原计划哈希、归档哈希、具体 test ID/名称/结果及 TRX 路径。既有 `remote-ci-final.json`、两平台 `remote-ci-*-contract-analysis.json` 和实际下载归档保留。 + +前次微基准只证明编码缓冲实现减少分配,不能推出整个 Editor 更快。Mac Metal 的八条 BGFX refcount warning 在 master 基线也存在,device count 有差异、根因未知;本次不将其当作新 UI 缺陷,也不宣称已经无 GPU 泄漏。GitHub Action runtime 弃用告警同样不是此次 UI 完成标准的替代项。 + +## 后续闭合顺序与 PR 决定 + +1. **先修正文档和状态**:撤回“整个实现完成”的表述,保持原通过日志,区分缺功能、缺抽象、缺测量。当前问题页修正仍把 #13 描述为未修代码的旧结论。 +2. **完成 U01/U04 的用户可见闭环**:generation 索引、Console 搜索、统一键盘选择;保留 fresh Query、既有 Core Events/Selection。 +3. **完成 U03 的职责收拢**:用实际非 Shader 消费者验证通用 Graph 机制;领域布局回到领域 presentation,公开 API 和 SDK 同步。 +4. **完成 U02 的测量与针对性实现**:先固定相同基线与输入,记录 10k/2k 成本,再实现必要可见范围/增量快照;不凭猜测建池。 +5. **闭合 U05 和完整验收矩阵**:异常路径、100-cycle/10-reload、Zoom/DPI、同条件截图、完整 FlappyBird 编辑旅程及插件安装态。真实 Mac 人工项单列。 +6. **最终同一源码再判定**:适用实现与验收全部通过后,将 PR 从 Draft 转 Ready。同步 Rendering2D/Samples 的调用方交付状态;它们此前仅本地提交,不把引擎 PR 当作四仓库都已远端发布。 + +本次不为给出漂亮评价而临时大改未测的菜单缓存或图形算法。已存在的 **PR #26 继续面向 master 并保持 Draft**,无需再创建一个同分支重复 PR。标题和正文继续英文、`type(scope): description`;`Fixes #13` 只在合并时关闭已修复 issue。 + +自查答案:**基础分层和 Core 复用有真实代码支撑,但“超干净、全部达标”的说法目前不成立。** 上述缺口属于明确的后续工程工作,不能用改名、拆文件、增加接口或更多正常路径 smoke 掩盖。Windows 无 `/System/Library/Sounds/Glass.aiff`,本次未播放指定提示音。 diff --git a/records/architecture/acceptance/EDITOR_UI_SCRIPT_PUBLICATION_ACCEPTANCE_2026_10_30.md b/records/architecture/acceptance/EDITOR_UI_SCRIPT_PUBLICATION_ACCEPTANCE_2026_10_30.md new file mode 100644 index 000000000..c2bc909e0 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_SCRIPT_PUBLICATION_ACCEPTANCE_2026_10_30.md @@ -0,0 +1,43 @@ +# Editor UI script-cache publication correction + +Issue: [#55](https://github.com/Wolfiora/InnoEngine/issues/55), parent [#27](https://github.com/Wolfiora/InnoEngine/issues/27). This is focused correction evidence; complete corrected-source product, quantitative and CI acceptance remains a separate final gate. + +Subsequent complete local acceptance at `dd5979c6`, including five cold/warm publications, companions, six products, quantitative review and full Wiki, is recorded in the [final report](EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md). Its exact delivery-head PR attachment separately records CI and Ready; this focused report retains the original diagnostic chronology. + +## Confirmed failure and diagnostic limits + +At `5e329bbe96e4812fcec1207151dd98c520237bbd`, eleven preparation stages and Contracts, Workflows, Visual and Lifecycle passed. The subsequent formal companion script/IDE command failed in `ScriptCompilerEngine.CommitGenerationCache`: raw `Directory.Move` reported Windows access denied for an owned staging directory. The failed command is preserved; a successful explicit repeat does not turn it into a passing execution. + +Evidence owner: `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. Original log: `candidate-5e329bbe/companions/Canvas/generation.log`. Independent diagnosis and focused regression evidence belong to `script-publication-investigation/`. + +Public Roslyn 5.9.0 probes used a small emitted assembly and a copy of the actual companion assembly. File-backed references, owned metadata images, explicit metadata disposal and `AssemblyName.GetAssemblyName` did not reproduce the rename failure. A public BCL control holding a readable file with delete sharing reproduced directory rename denial in three of three trials. These observations establish a real filesystem condition; they do not identify the original reader. No antivirus, Roslyn or BGCS cause is asserted. + +## Actual ownership correction + +- Core IO already owns atomic directory publication, bounded filesystem rename handling and rollback-safe replacement. Its existing two-second/25-millisecond Windows policy is unchanged. +- Compiler now uses `AtomicDirectory.Publish` for an absent destination and `AtomicDirectory.Install` for an existing invalid cache tree. A complete valid cache is still reused. Best-effort private staging cleanup no longer serves as destructive replacement of a published cache tree. +- The existing compilation token reaches both assembly and generation commits. `Install` has one optional cancellation parameter, with no compatibility overload. Cancellation can interrupt preparation and precommit rename waits. Restoration deliberately does not use the canceled request token. A committed cache is not canceled afterward. +- Postcommit backup cleanup failure remains `DirectoryCleanupException`; installation plus restoration failure remains `AggregateException` with both failures and the preserved old-tree location. Cache completion is distinct from runtime generation activation. +- `Inno.Scripting.Compiler` references Core IO as a private implementation dependency. It gains no dependency on Build, MSBuild, named platforms, BGCS or Editor Panels. No new public Compiler API, cache manager, retry loop, event system or identity table is introduced. + +## Public-boundary regression evidence + +| Evidence | Actual result | +| --- | --- | +| `compiler-publication-before-exact.log` / `.trx` | The public compiler regression failed before correction with the same raw `Directory.Move` denial and preserved original stack. | +| `compiler-publication-after.log` / `.trx` | The identical temporary-reader regression passed after Core publication reuse. | +| `compiler-publication-final.log` / `.trx` | All 17 Compiler tests passed, including temporary staging observation, canceled publication, persistent blockage and invalid-tree replacement failure followed by explicit repair. | +| `core-publication-suite.log` / `.trx` | All 41 Core IO tests passed, including prior cancellation with/without an existing destination and cancellation after the old tree is evacuated. Rollback restores the previous bytes while the request token remains canceled. | +| `roslyn-directory-probe.log`, `roslyn-actual-payload.log`, `assembly-name-probe.log` | Independent public API probes and the reproducible BCL control; retain all observations rather than attributing an unproven cause. | + +The compiler tests discover artifacts through public results/progress and ordinary filesystem observation inside their own fixture. The replacement test compares the entire existing tree after rejection, then compares repaired output against the original complete bytes. Cancellation and persistent-block tests preserve another completed generation and require a subsequent explicit request to succeed. All owned compiler work drains before fixture retirement. No IVT, private reflection, production test hook, forced GC or process termination is used to make publication succeed. + +The Windows sharing fixtures are explicitly unexecuted on other hosts; they do not silently return a passing result. Portable Core replacement behavior and ordinary Compiler suites remain applicable across hosts. + +## Remaining final gate + +Five independent cold companion copies and fixed-input warm publication, formal companion projections, complete UI/product/lifecycle/visual acceptance, controlled paired performance, architecture/XML/Wiki and exact delivery-head CI must pass on the corrected candidate. Their results and source identity belong in the final acceptance report. This focused report does not mark #55 Verified or make PR #26 Ready by itself. + +## Deep self-review + +The change removes a duplicate, weaker filesystem transaction from a real execution chain and reuses Core IO at the actual publication boundary. It preserves Compiler ownership of validation/staging/cancellation, Core ownership of rename/rollback, and Host ownership of activation. There is no platform policy in Compiler and no BGCS, package, vendor or generated-binding modification. Cancellation does not disable restoration; cleanup failure is not confused with failure to commit. Complete final acceptance remains required before claiming plan closure. diff --git a/records/architecture/acceptance/EDITOR_UI_SNAPSHOT_ACCEPTANCE_2026_10_29.md b/records/architecture/acceptance/EDITOR_UI_SNAPSHOT_ACCEPTANCE_2026_10_29.md new file mode 100644 index 000000000..abe61f2c7 --- /dev/null +++ b/records/architecture/acceptance/EDITOR_UI_SNAPSHOT_ACCEPTANCE_2026_10_29.md @@ -0,0 +1,71 @@ +# Editor UI snapshot and product workflow verification + +This is an intermediate acceptance record for #27, #35, #38 and #46. It does not close the complete approved UI plan or make PR #26 Ready. + +## Implementation and ownership + +- Core Serialization owns immutable, neutral `SerializationSnapshot` values. Repeated capture still executes all getters and converters. Copy-on-write only reuses already captured neutral nodes; it is not a change notification system. +- Snapshot values contain ordinary scalar/string/copied binary data and neutral structural metadata. They do not retain source instances, plugin types, converters, delegates, context services or generation leases. +- Scene documents own their latest observed snapshot/hash. The saved hash remains independent. Encoding and SHA256 use the ordinary binary format; failed capture/hash does not replace the previous valid observation. +- The writer borrows the converter generation's actual type snapshot for consistent Stable Type IDs. Nested state capture uses the same converter lease and complete owner context. +- Context storage uses a private immutable array of exact contract/value pairs. Replacement copies that small owned array without retaining the replaced service. Object nodes use the BCL generic OrderedDictionary for one ordered storage authority rather than a dictionary plus a duplicated entry cache. Array enumeration and component callbacks reuse operation-local mechanics. Graph membership comes from the actual capture owner; subtree boundaries remain distinct from the complete reference map. +- No additional serializer format, History stack, event bus, identity registry, mutation tracker, platform branch or public test hook was introduced. BGCS, vendor sources and generated bindings were not modified. + +## Required public surface + +`CaptureSnapshot` and `SerializationSnapshot` allow another assembly to retain the exact neutral capture without retaining its source. `ToArray` and `WriteTo` provide owned bytes and caller-owned streaming respectively. `SerializationWriter.WritePropertiesData` captures nested independently restorable state with the current converter lease. Its borrowed `typeSnapshot` avoids an independent live type-generation decision. Node representation and reuse remain internal; snapshot/type-generation APIs are not added to the script surface. + +## Recorded evidence + +Evidence owner: `D:/InnoEngine-Verification/editor-ui-closure-2026-10-27`. + +| Check | Result | Evidence / scope | +| --- | --- | --- | +| Core serialization | Passed: 74 | `e46/snapshot/core-bcl-ordered.trx`; includes ordinary byte-array copy semantics, structural changes, floating point bits, wide objects, failure, parallel readers, source retirement and context replacement lifetime | +| Scene contracts | Passed: 53 | `e46/snapshot/scene-stack-final.trx`; capture references, subtree, Prefab and current serialization contracts | +| Real workspace changes | Passed: 3 | `stack-controlled-final.trx`; independent ordinary serialization/hash oracle, direct mutation, converter state, failure recovery and component ordering | +| Snapshot across collectible retirement | Passed: 1 | `snapshot-generation.trx`; retained neutral bytes survive the existing strict unload barrier while the old script type becomes unreachable; later final-source rerun required | +| Independent Editor output | Passed | `product-independent-final.log/binlog`; official SDK graph, zero warnings/errors; native `processes=0`, `bindingGenerations=0`; later comment/context changes require final rebuild | +| FlappyBird drawn workflows | Passed: 8 stages | `e08/product-journey-d3d12-complete/result.json`; actual hidden D3D12 product, gestures, independent Undo/Redo, references, mixed axes, duplicate components, batch recovery, failed mutation rollback, Save/reopen and advancing Play behavior with Edit isolation | +| Whole source build | Passed on successive implementation candidates | `source-wide-final.log`, `source-documented-final.log`; zero errors; final-source gate remains separate | +| Full Contracts / Wiki | Pending rerun | The first operation was aborted after confirmed XML formatting violations. A diagnostic trace identifies its existing whole-repository Wiki test. The direct diagnostic Wiki invocation also lacked logical example references; neither invocation is counted as passed. New XML is expanded and API markers are synchronized. | + +The product journey uses a fresh owned project copy and ordinary source extensions. It never edits the original FlappyBird project. It drives public feature commands while real panels draw intermediate state; native pointer/keyboard tests and physical-device experience are separate evidence. Short smoke runs that exit before script activation do not prove these workflows. + +The initial journey used host-only Scene lookup APIs that are deliberately absent from script references. The corrected fixture uses the public current scene/component collections and Core persistent identities; no API was exposed for testing. Another initial assertion incorrectly required Selection count zero after closure. The actual contract retains temporarily unavailable identities, so the corrected assertion rejects a resolved stale target while preserving Missing semantics. These attempts remain failed records. + +## Quantitative gate still open + +`e46/snapshot/absolute-array-processes/measurements.json` records five independent Release VSTest processes, each with five warmups and ten deliberately due complete inspections. The 110 ms preparation delay is excluded from frame timing; it ensures every sample runs the unchanged 100 ms policy. These measurements precede the OrderedDictionary storage cleanup and are not final-candidate measurements. + +| Process | Median ms | P95 ms | Allocated bytes / inspection | +| --- | ---: | ---: | ---: | +| 0 | 40.98200 | 42.3213 | 8,370,499.2 | +| 1 | 42.83055 | 44.2989 | 8,370,499.2 | +| 2 | 41.33805 | 61.8373 | 8,370,524.8 | +| 3 | 43.39640 | 50.5440 | 8,370,499.2 | +| 4 | 42.02650 | 43.2985 | 8,370,499.2 | + +Allocation is below the announced 8 MiB limit; latency does **not** meet the 16 ms median / 25 ms P95 target. #46 remains Open and must not appear as `Fixes` yet. Earlier stack-context results remain in `stack-controlled-final.json` (49–51 ms, approximately 10.45 MB per inspection). Do not compare due inspections with ordinary frames as though they were identical operations. + +Diagnostic decomposition measures the public canonical Asset load and current source freshness separately from complete neutral capture. The public metadata-getter workload costs approximately 9 ms for 10,000 objects. These diagnostic runs locate work; they do not authorize skipping liveness or source verification and are not controlled final comparisons. An experimental scalar converter cache was rejected rather than added to production without sufficient benefit. + +## Additional applicable checks + +- `e09/native-current-matrix.trx`: 236 real-context checks passed, including the hierarchy and Browser keyboard workflows at both logical sizes and all 28 zoom/framebuffer-scale combinations. These are native input/layout simulations, not physical-monitor or human visual approval. +- `core-bcl-ordered.trx`: 74 Core serialization cases pass after replacing duplicated ordered-entry storage with the existing .NET collection. +- CI at `a38437f7d27307aadcf964f76e9228256d26185f`: Windows and macOS managed jobs passed. The macOS log explicitly reports all 31 Core IO tests passed, including the atomic publication regressions. Native job results are tracked separately; this does not certify subsequent uncommitted snapshot work. + +## Original product baseline + +The original engine `b303d9f1`, Rendering2D `3e8fafe`, Samples `6eb52f3` and unchanged Canvas restore a consistent historical producer combination. The Samples source differs only in the Rendering2D package; package inspection shows identical runtime/assets and changed Editor code/manifest identity. Both producers share the explicit neutral layout and test source. This is a multi-repository before/after comparison, not an assertion that the original engine can load the current package unchanged. + +The unchanged original platform builds and runs 300 D3D12 frames. A diagnostic SDK host links its unmodified entry program and complete official platform output. The cancelled historical entry-graph operation is preserved separately. Complete paired product measurements are still pending. + +## Self-review and continuation + +The correction preserves domain direction and reuses Core serialization, identity and IO protocols. Snapshot publication owns only neutral data; contexts and converter leases remain operation-local. Failure does not replace valid data, and replacement-context tests verify that discarded services are collectible. These concrete checks support the ownership design; they do not certify the whole engine as flawless. + +Still required: #46 quantitative closure, final frozen-source profiles and Wiki, final product/paired performance, remaining per-view and lifetime evidence, exact-head CI, issue-by-issue review and the eleven-dimension report. Mac physical checks remain unexecuted. PR #26 stays Draft until its applicable completion conditions are met. + +Automatic approval review rejected deletion of four completed diagnostic runtime copies with `blocked by policy` and no more specific reason. They remain intact. No user, SDK or NuGet global content was removed. The Windows environment has no specified `Glass.aiff`; no completion sound has been played. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/.gitattributes b/records/architecture/acceptance/editor-ui-2026-10-30/.gitattributes new file mode 100644 index 000000000..4973fa637 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/.gitattributes @@ -0,0 +1,2 @@ +# Preserve these recorded evidence bytes and their manifest hashes across checkout hosts. +* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/README.md b/records/architecture/acceptance/editor-ui-2026-10-30/README.md new file mode 100644 index 000000000..4debee208 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/README.md @@ -0,0 +1,11 @@ +# Editor UI production-candidate evidence + +Production revision: `dd5979c613b0fe8ebd9f7026f25001ceee9f8cee`. + +Read the [final report](../EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md) first. `manifest.json` records exact content hashes for this portable subset. The directory-local Git attribute preserves evidence bytes on every host, including the original CSV/JSON line endings; it does not alter source/build configuration. + +`test-cases.csv` contains overlapping profile executions, not a summed distinct-test claim. `raw-*-measurements.json` preserves every initial producer measurement and the predeclared graph-tail repeat. Initial flags and failed historical attempts are not reclassified as passing trials. Complete TRX/binlogs/runtime manifests and failed-attempt logs remain in the recorded external evidence owner. + +The 307-row plan matrix includes ten interface journeys, seven status-vocabulary definitions and two future post-merge audit rules. At report authoring, fifteen delivery/CI conditions remain pending; their exact final disposition is signed in the [stable PR verification attachment](https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338), together with the containing commit SHA and exact-head CI. This prevents a self-referential report-commit loop without using historical CI for a new head. + +Physical/manual limits are explicit in the report. These files are acceptance evidence, never a production registry, resolver, configuration source or cache identity input. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/absolute-inspection-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/absolute-inspection-measurements.json new file mode 100644 index 000000000..cc90cac8c --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/absolute-inspection-measurements.json @@ -0,0 +1,1392 @@ +[ + { + "p95": 12.3807, + "maximum": 22.1401, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\0-idle", + "median": 8.9222, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 9.6019, + 9.5651, + 9.551, + 9.4154, + 8.9608, + 8.6895, + 8.8458, + 21.0278, + 9.2889, + 10.043 + ], + "median": 9.4832, + "p95": 21.0278, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 9.6608, + 8.9665, + 8.7771, + 9.8036, + 8.6855, + 22.1401, + 9.9554, + 9.1504, + 8.7155, + 8.8836 + ], + "median": 9.05845, + "p95": 22.1401, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.6657, + 8.5388, + 9.2778, + 9.262, + 10.6916, + 12.3807, + 12.1319, + 11.3081, + 9.0471, + 8.8326 + ], + "median": 9.2699, + "p95": 12.3807, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 187.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 9.2851, + 8.5787, + 8.4439, + 9.5828, + 8.44, + 8.5284, + 10.0074, + 8.6177, + 8.4987, + 8.2943 + ], + "median": 8.55355, + "p95": 10.0074, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.4635, + 9.4812, + 7.8818, + 8.8301, + 8.5445, + 8.1611, + 8.603, + 8.5867, + 7.9488, + 7.9857 + ], + "median": 8.504, + "p95": 9.4812, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 0, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 17.7009, + "maximum": 23.4513, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\0-edit", + "median": 12.3006, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.9998, + 14.9489, + 15.4932, + 14.1631, + 11.9199, + 12.2311, + 23.4513, + 12.3617, + 12.3436, + 12.7973 + ], + "median": 13.4802, + "p95": 23.4513, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 265.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.4663, + 12.5163, + 11.6394, + 23.1995, + 12.2105, + 11.7747, + 11.8486, + 11.6241, + 11.4952, + 11.5496 + ], + "median": 11.81165, + "p95": 23.1995, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 11.5752, + 13.0555, + 11.8782, + 12.0658, + 12.5633, + 11.6064, + 12.4525, + 13.5673, + 14.6442, + 12.2771 + ], + "median": 12.3648, + "p95": 14.6442, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 218.75, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.5485, + 11.7938, + 11.9887, + 12.1073, + 13.1001, + 11.9378, + 12.8684, + 17.7009, + 12.5765, + 12.0066 + ], + "median": 12.3279, + "p95": 17.7009, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.1162, + 12.5564, + 12.2221, + 13.4516, + 12.1862, + 12.0622, + 12.0893, + 12.05, + 13.6223, + 12.3241 + ], + "median": 12.204149999999998, + "p95": 13.6223, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 0, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 15.8753, + "maximum": 24.4228, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\1-edit", + "median": 12.2998, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.4602, + 13.9571, + 15.8753, + 11.9225, + 12.6494, + 12.5547, + 24.4228, + 11.9308, + 12.06, + 11.934 + ], + "median": 12.60205, + "p95": 24.4228, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 234.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.0769, + 12.3585, + 11.8625, + 23.7502, + 12.1167, + 12.445, + 12.9273, + 13.484, + 12.0659, + 11.6213 + ], + "median": 12.2376, + "p95": 23.7502, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 203.125, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 11.6032, + 13.2885, + 12.0036, + 12.1864, + 13.6702, + 11.7924, + 12.1898, + 13.8006, + 13.4968, + 12.0085 + ], + "median": 12.1881, + "p95": 13.8006, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 171.875, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 11.8119, + 12.1729, + 12.5391, + 12.3134, + 12.2831, + 12.6453, + 13.4729, + 11.8185, + 12.2862, + 12.515 + ], + "median": 12.2998, + "p95": 13.4729, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.5247, + 12.0979, + 12.1579, + 15.2212, + 11.9826, + 12.1325, + 12.3608, + 12.749, + 13.6585, + 11.7756 + ], + "median": 12.25935, + "p95": 15.2212, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 1, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 10.1327, + "maximum": 19.9138, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\1-idle", + "median": 8.53035, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.3233, + 8.9338, + 8.7193, + 9.1145, + 8.2816, + 8.9857, + 8.3798, + 19.9138, + 8.5373, + 8.9224 + ], + "median": 8.82085, + "p95": 19.9138, + "allocatedBytes": 9647808, + "totalAllocatedBytes": 9648464, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.0052, + 8.5423, + 7.9935, + 8.3199, + 8.3132, + 7.8977, + 19.8211, + 8.2266, + 8.339, + 8.6044 + ], + "median": 8.31655, + "p95": 19.8211, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.4134, + 8.2402, + 8.4949, + 9.1222, + 9.6246, + 10.1327, + 9.5116, + 8.4893, + 9.144, + 8.283 + ], + "median": 8.80855, + "p95": 10.1327, + "allocatedBytes": 9647064, + "totalAllocatedBytes": 9647544, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.1912, + 8.2234, + 8.4287, + 9.6206, + 8.785, + 8.6567, + 8.5234, + 8.9007, + 8.7537, + 8.4888 + ], + "median": 8.590050000000002, + "p95": 9.6206, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 93.75, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.2555, + 8.0363, + 9.6566, + 9.3987, + 8.2011, + 8.0976, + 8.5726, + 8.7949, + 8.6666, + 8.2265 + ], + "median": 8.41405, + "p95": 9.6566, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 62.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 1, + "allocatedBytesPerOperation": 964714.88, + "scenario": "idle" + }, + { + "p95": 10.051, + "maximum": 23.1796, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\2-idle", + "median": 8.54255, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.8773, + 8.4701, + 8.4841, + 8.6071, + 8.9514, + 8.5858, + 8.5847, + 20.1636, + 8.4375, + 8.6228 + ], + "median": 8.59645, + "p95": 20.1636, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.5586, + 8.1269, + 8.4946, + 8.3018, + 8.9074, + 23.1796, + 8.4899, + 7.92, + 7.8139, + 9.9828 + ], + "median": 8.49225, + "p95": 23.1796, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.3356, + 8.473, + 8.4143, + 8.7906, + 10.051, + 9.7758, + 9.583, + 8.6287, + 8.9725, + 8.1879 + ], + "median": 8.70965, + "p95": 10.051, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.0464, + 8.4645, + 8.5879, + 8.8999, + 9.1305, + 8.0816, + 8.5265, + 8.2143, + 8.6434, + 8.4977 + ], + "median": 8.5121, + "p95": 9.1305, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 9.1992, + 9.5064, + 9.276, + 8.1418, + 8.7612, + 8.0167, + 8.4035, + 8.1182, + 8.0503, + 8.1969 + ], + "median": 8.3002, + "p95": 9.5064, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 2, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 19.1123, + "maximum": 25.045, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\2-edit", + "median": 12.69685, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.1411, + 12.8804, + 19.1123, + 13.8906, + 11.9989, + 24.324, + 12.0374, + 11.8256, + 12.2041, + 12.5018 + ], + "median": 12.691099999999999, + "p95": 24.324, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179944, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.2034, + 12.6992, + 11.4427, + 25.045, + 12.5896, + 12.1224, + 12.6114, + 15.2748, + 14.2026, + 14.2415 + ], + "median": 12.6553, + "p95": 25.045, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 14.3339, + 12.8216, + 13.6663, + 13.5159, + 13.5648, + 13.01, + 13.2676, + 13.3107, + 15.2481, + 11.8418 + ], + "median": 13.4133, + "p95": 15.2481, + "allocatedBytes": 11179744, + "totalAllocatedBytes": 11180224, + "cpuMilliseconds": 265.625, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.1586, + 11.9246, + 15.9201, + 11.834, + 12.1561, + 13.4209, + 12.3512, + 11.7068, + 12.8034, + 13.22 + ], + "median": 12.2549, + "p95": 15.9201, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.3788, + 11.6936, + 12.6945, + 13.2038, + 12.024, + 11.947, + 12.0669, + 12.2406, + 12.0875, + 12.7541 + ], + "median": 12.16405, + "p95": 13.2038, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 2, + "allocatedBytesPerOperation": 1117912.32, + "scenario": "edit" + }, + { + "p95": 17.2107, + "maximum": 24.2276, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\3-edit", + "median": 13.011299999999999, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 12.8744, + 12.2026, + 13.354, + 14.1843, + 12.9206, + 12.7451, + 24.2276, + 12.5768, + 12.4068, + 12.8823 + ], + "median": 12.878350000000001, + "p95": 24.2276, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 13.3007, + 12.5622, + 11.9551, + 24.0171, + 12.5052, + 12.6458, + 12.3361, + 13.0841, + 12.4501, + 12.533 + ], + "median": 12.5476, + "p95": 24.0171, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 187.5, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 12.9385, + 13.6924, + 12.8055, + 14.0937, + 13.9949, + 13.2718, + 15.3084, + 17.2107, + 15.6573, + 13.7986 + ], + "median": 13.89675, + "p95": 17.2107, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 218.75, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 14.6498, + 13.8284, + 14.1835, + 13.9772, + 14.0462, + 13.9758, + 15.6751, + 16.3196, + 13.8047, + 12.4162 + ], + "median": 14.011700000000001, + "p95": 16.3196, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 13.7084, + 12.1913, + 12.161, + 13.557, + 12.4667, + 12.2528, + 12.4945, + 12.0847, + 12.3153, + 12.5764 + ], + "median": 12.391, + "p95": 13.7084, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 3, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 10.351, + "maximum": 20.6685, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\3-idle", + "median": 8.44505, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.3865, + 8.2209, + 8.4115, + 8.5456, + 8.2323, + 8.3231, + 9.1648, + 20.6685, + 8.5411, + 8.4786 + ], + "median": 8.44505, + "p95": 20.6685, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 7.9456, + 8.5688, + 8.3446, + 8.1275, + 7.6609, + 20.4989, + 8.3169, + 8.6972, + 8.3068, + 7.7247 + ], + "median": 8.31185, + "p95": 20.4989, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 7.8035, + 8.5878, + 8.3781, + 8.0937, + 10.2531, + 9.2406, + 9.8069, + 8.3478, + 8.5085, + 8.5664 + ], + "median": 8.53745, + "p95": 10.2531, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.3166, + 8.5059, + 8.8209, + 9.0184, + 8.0193, + 8.7592, + 8.0322, + 8.7098, + 8.0016, + 7.9664 + ], + "median": 8.411249999999999, + "p95": 9.0184, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 62.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 7.9625, + 9.545, + 8.1059, + 8.5349, + 8.238, + 8.897, + 8.5141, + 7.8492, + 10.351, + 9.632 + ], + "median": 8.5245, + "p95": 10.351, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 3, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 10.1595, + "maximum": 20.0675, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\4-idle", + "median": 8.76125, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 9.5737, + 8.5151, + 9.0708, + 8.7022, + 8.3397, + 9.6961, + 9.1659, + 8.7308, + 19.7437, + 8.9403 + ], + "median": 9.00555, + "p95": 19.7437, + "allocatedBytes": 9647808, + "totalAllocatedBytes": 9648464, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.5027, + 8.6419, + 9.027, + 7.8713, + 8.9598, + 8.6629, + 20.0675, + 8.4933, + 8.6182, + 9.2698 + ], + "median": 8.6524, + "p95": 20.0675, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.3681, + 8.3748, + 9.093, + 8.7078, + 8.5405, + 10.0968, + 10.1595, + 8.9771, + 8.7277, + 8.7153 + ], + "median": 8.721499999999999, + "p95": 10.1595, + "allocatedBytes": 9647064, + "totalAllocatedBytes": 9647544, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.3283, + 9.1009, + 8.6986, + 8.7188, + 9.3451, + 8.8962, + 9.0283, + 9.0802, + 8.5968, + 8.5217 + ], + "median": 8.807500000000001, + "p95": 9.3451, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 93.75, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.8543, + 8.6611, + 9.5965, + 8.8972, + 8.7917, + 8.71, + 8.5038, + 9.2104, + 8.5303, + 8.8305 + ], + "median": 8.8111, + "p95": 9.5965, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 4, + "allocatedBytesPerOperation": 964714.88, + "scenario": "idle" + }, + { + "p95": 24.5695, + "maximum": 42.0022, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\4-edit", + "median": 12.1435, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 42.0022, + 40.6075, + 12.9553, + 13.1752, + 12.6073, + 24.1638, + 11.7008, + 12.0114, + 12.389, + 12.5688 + ], + "median": 12.7813, + "p95": 42.0022, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 421.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 11.9311, + 12.3971, + 13.1053, + 24.5695, + 11.9685, + 12.0172, + 11.8971, + 12.1158, + 12.0578, + 12.1108 + ], + "median": 12.084299999999999, + "p95": 24.5695, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 12.6066, + 11.7774, + 11.5886, + 12.5307, + 12.8895, + 11.9035, + 12.567, + 12.9019, + 15.1707, + 12.0228 + ], + "median": 12.54885, + "p95": 15.1707, + "allocatedBytes": 11179744, + "totalAllocatedBytes": 11180224, + "cpuMilliseconds": 265.625, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.0508, + 12.3723, + 12.0221, + 12.1205, + 11.5312, + 12.7472, + 11.6468, + 12.2152, + 12.1725, + 11.812 + ], + "median": 12.085650000000001, + "p95": 12.7472, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 11.9903, + 12.6222, + 12.0284, + 12.7907, + 12.0501, + 12.1665, + 11.8014, + 12.1062, + 12.2728, + 11.846 + ], + "median": 12.07815, + "p95": 12.7907, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 4, + "allocatedBytesPerOperation": 1117913.28, + "scenario": "edit" + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/bgcs-package-integrity.json b/records/architecture/acceptance/editor-ui-2026-10-30/bgcs-package-integrity.json new file mode 100644 index 000000000..4d29b6958 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/bgcs-package-integrity.json @@ -0,0 +1,37 @@ +{ + "packages": [ + { + "package": "bgcs", + "version": "4.1.2", + "matched": 100, + "archiveSha256": "e45ae62dbb7cfa879999a4d91d58d6d17a0b26ac60d2044be47a7abbddab40f2", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.core", + "version": "4.1.2", + "matched": 5, + "archiveSha256": "25d3f0db2306792a08632278b02c3a08ef37c12f63330d6ca0b1eff55a971309", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.parserruntime.osx-x64", + "version": "4.1.2", + "matched": 15, + "archiveSha256": "da1b3ede4ffb6e0b370a36ff66be6a1c2610ccb5fea08d2e854b36a5accf10b5", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.runtime", + "version": "4.1.2", + "matched": 6, + "archiveSha256": "5ab737cc30577290a0c049ba2254cc70642c30e7fb95c0a3188de6fd4643eb3e", + "matchesPreImplementationArchive": true, + "mismatches": [] + } + ], + "passed": true +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance-dry-run.log b/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance-dry-run.log new file mode 100644 index 000000000..b24b35e7d --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance-dry-run.log @@ -0,0 +1,1809 @@ +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\20767192A8FFABF94B17ED3B0D1EB6F3CB698CD2B7D349DCAEF15C83A4CEA0DB (154715338 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\C9C64A4ADA1ECF2BD9A204FDF52120F038213D955C1817909AEC1C35FCC14130 (154908362 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\BA518897418F0B2EAEE8DE439775AFDA20CB7E1A02FF15851E2B27613F539CF6 (154715338 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\913B10F8B6CC8F30FC656B92A16A090CA6262B8DE529D12A92B5E7F08FAA4CE8 (154908362 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\61932802E098F95DA122A1DE4501789F7F8E220A0C8F82FE521BBF472525CD7A (154908362 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\5DFF94163173DD2D29401EE78C78EBC0BD6478B3730A399ABA2979DC050DD5A9 (154715338 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\BAD804654D6EE834AA7673C0486FE4EC224D8C519154740C9C79F4B0F537FCCA (154913482 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\0F73FA4356EE9C90328279E3C7FC9723AD2DAC4BD88C243DD15127F367D7CFC0 (154916042 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\203B2F57B061E4D5D52906101417C94FB927BC0F7B964F31AFCAC58CDE3E6BB8 (154916554 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\6B73E757E27D7C8827908CB4D3EF3A837DE247EAEEC579ED48F850B24EBFAA30 (154917066 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\67669CE8674ACDA534D7BD964ED4FEEBA09E14EF11FE6A91F737BE96E696ABA6 (154723018 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\3cd1b7a1040aaa4e6ef3d550d35221b8e0c11a10190f29f329a4c69bf1120e13 (1168765 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\abc5bfda492cf202d89c397411c323c9f357ee80fcd022c4ad653d1c1c733894 (2798940 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\87004d295a3092a3beb9ba1bd3017f6ebc2928c3806c9f4a840d0c29fd08f99b (770923 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\9dcb0fa9d13a97e1da2e8d365c6ab299120c52a5e5530d9bbbe91d42dae0acfd (1571179 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\bd8c29fddc850f284ad877b81e069ebc22f7dd64218b3915a3f052575da29f1c (3023717 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\9ce2e574c484e62db564cc65a9c23533d17c31975e6303a2d81ed098f6d31152 (2886924 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\b108a6aa66f5553116c07536b7a38a1ec7ac5c72f2706d7baa2075d463e27d5d (93044 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\BD5D0ABAB5D14D7658DCF169D6145960BF0DF644D737C7681AACA528C95A5698 (154917578 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cab1a2ddcb1e4941aac2363f4cdd32da (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9c188edcda0d4522bf49f2fececa04c7 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9c188edcda0d4522bf49f2fececa04c7 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3f9037abfb244a03ad024c04b4089052 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3f9037abfb244a03ad024c04b4089052 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08dffcda582843a3bce3aeecee9c4608 (440643 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\08dffcda582843a3bce3aeecee9c4608 (154917794 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c4370158649545e2882b7dc10094e394 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c4370158649545e2882b7dc10094e394 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\79f05f014cda4cf082b8700c4adadb99 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\79f05f014cda4cf082b8700c4adadb99 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\47caafdf4f8b4cb3a8717841ab83ee76 (3889363 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8b5b8608d49e47eda397a209e2ff4ef7 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8b5b8608d49e47eda397a209e2ff4ef7 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\18c6b71395c4421b838b317789a10e18 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ae66a34a7ef340f6934f6ec822e0d232 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ae66a34a7ef340f6934f6ec822e0d232 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7c2eb82f13244a15a05c478d5716dbd1 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\86261c4a2ec4484cba812763efa9d0b5 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\86261c4a2ec4484cba812763efa9d0b5 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b6d46b16ca2b437dba5e9de35e3abbc8 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9a0bded3b7134afbbd7ac0bdb35a5409 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b4d83b46b5e5400b965d34ff4e8784a7 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b4d83b46b5e5400b965d34ff4e8784a7 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3dd20b362bdb4cbfa50c67e1824e15c8 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ecd9f59e85c8404ea730a4fba0d40127 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8041dbbae96a4fd3914f00dacfcf134e (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f737b2eb0c0544ad95116acd0d8a0949 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c5f8493b07114a30810126b6ccb34170 (3889084 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4c384410870b4659bdb5c94fcb3dc112 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\9A13928F6BA07F11992DF2CE93F48C6B6AF837BFD76CCCEE580AC8A18FE96253 (154917578 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4c384410870b4659bdb5c94fcb3dc112 (154917795 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a3609a02c3314a57bb03915314045538 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\322e163115a44cd6a93feb055db58831 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\989d3b7ac7134489b4398506853e4dd8 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2b1d1b23cc3e46f1a38b7613d0add188 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b961344885dd48318e0367ecb63f03f3 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d479672430c14e2f899dca366ab37a47 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dd5982b3ef594e46b50e31a266befbd7 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ffd0916d29b463088419d6235e6c53e (3889084 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\58bf1aecd90e4e5fa1ee907959ef3edc (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a93757c4facd4fa2bae65a256b01fb5f (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4dc27909ba2a4786976667d42703cd1e (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2cdf840d089e465f8b83430ae170f955 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8c1305a72659484c954ac865f53d19fe (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3e46f79b822e4b7ea4196a624a9c2e38 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\05da108960b64397b297f9bc8cef2be4 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2b429b396a1948e696ec41fb0acac137 (3889084 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f999d50020fc402d980d583d2cf69e83 (415556 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fcd083ec175343a1bc8c3d269612525c (415556 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\90187f525ac14273a3f3ced5b3458986 (415556 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\21d6994f60ca4090b5ad76d7446269f6 (3748716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5d7e109eddf74e6d9f5510568e67e280 (440643 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5305e08b094c41beaa5787f920d2c30b (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5305e08b094c41beaa5787f920d2c30b (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fd0905a853b54974be57eedc79d71770 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fd0905a853b54974be57eedc79d71770 (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\14c8c88c0170416cab7cc03470367f77 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\14c8c88c0170416cab7cc03470367f77 (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c4f976b635764233b50a58fb5217187b (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c4f976b635764233b50a58fb5217187b (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\41ccde079ef247c493422b6729b82e2c (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\41ccde079ef247c493422b6729b82e2c (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f275752504bb4f2e9af77d06bbec3b96 (3889363 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\35cfc723be4448e79323cece9a758aa0 (440644 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\97C02D137A7AD959D4D4B93E72C861169C7FD736D5EF8FF95A3A55D6DC8C59AE (154918090 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\35cfc723be4448e79323cece9a758aa0 (154918307 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\a02cab90e786e7478d9d1a7fbfc6bee80dcdaf9ea9c503cfa010437afd340f67 (7058807 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\2ee2d53a9cf3d51da57102389efcff041e2007fc2fb3ff6763abbad57f2baf8b (7072086 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\47d25d1140f12acb4dfd3d884f3666e25c8a8824c2ebf54e09180887eec443c3 (1719141 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\7c0da2eb2e9641e7494f10abc64a4f95e85bc4e3467e10bdf87167f4c0a39376 (5496165 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\bb8a23aab78957710007dfad0d294a22553f3287017f94d3d1c2eed5db9887be (12201823 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx-tools\windows-x64\029e31f2530fa7181367f90ee53aff2ff9a33e78a57b06a6246834576accbe79 (16494228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\19b50188d21d1eb11ce28aa7d7ee45540f03a44b2899b66047d1a16c3461ff41 (4519482 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\1f26fe94c7c29af5963363a7a51931ea6fa8b2491ef5da1cdaff69f343b9df3c (221038 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2ee9bba7a07644d7903d239e9bc06d73 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1d78db0748504f3aa1fd4b6f20781606 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\923f8b1034f74d64b72f6cf8c73525e2 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1f89b0859ac24501a2fab0c9b23582a4 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1f89b0859ac24501a2fab0c9b23582a4 (56 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\932a6b28d6c047eda59e7bb767e187b7 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1aa274d6aaa342d39773c2b2740eb93a (3770960 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ee35be6df39249759a197dfa8089d28b (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ee35be6df39249759a197dfa8089d28b (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a2c79d239284455c8394790796b363d3 (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3aea44fb3de443f88dc83378ad3fbd88 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3aea44fb3de443f88dc83378ad3fbd88 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\30f5da0c510f45a596402af58e032460 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\21b90cf6657f40dc8d291cc352b72033 (3773183 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f8d23ce047e04887b1cc52abc36f49f7 (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\785710cb804e480b9f420d8b18be389f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e156a287081d4dd1bf3032c325588afb (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5deff5efc58141228e3131be085043f9 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e3e9ec59cadb4d958af55bd6ba28cf1a (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5accb51763154e9c909b00f24f6c8bc8 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\80f6770b8ee6450c9acc5bb77b3699d3 (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a3eafa3531404c4ea65709c4f366639c (418116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b8a42eebb23644c9ba8773bbe8c660ca (3770960 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\93192f07269d4c948184bc404bf46f5b (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\93192f07269d4c948184bc404bf46f5b (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2deb1fcf35cb43868701b7598a691f61 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2deb1fcf35cb43868701b7598a691f61 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\334efe5fbcfc4ddb9c38b9c70cfd7c86 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\334efe5fbcfc4ddb9c38b9c70cfd7c86 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7e6267b8acd4de89145550db735cf9d (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b99eeef8f3a84dfda070a4e521c768f8 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\CC940F55964A87A0BECA8DFAC31400BDE7E2A3BC99F3038ABF9C3EED41E949DB (154727114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b99eeef8f3a84dfda070a4e521c768f8 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\548da871d54c47e39e6856e826b46a5b (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df6cec236de7417594b5d3d7f8481edc (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7938b2b9ce75491b8b9987eddf1f64df (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cf40a77871064c6cb31c1058ddd03770 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\17b0ef420c714ebaa6f55291be3b077c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\225921fd8cb24c90bd93bf84da7c1244 (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\56d70da24ae74bd8baa4694c276fd256 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\72508587ad84427098da3ad60bf856ae (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c081329a08c04bcf8316bab422b5a3db (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\93cc0805a33743a897bb423d71af2269 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\66dd6652bd7645ee8c96655c111654bd (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e732efd59bb341f68e09af89ff022a2a (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fe251826a66d4655bbfa85e6cd419043 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3ef53cb14a0743619bad61fba88e672b (3915256 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8c16b829e7214d3dacd6f8313a2fd0dc (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ecb60747aa794797b48b6e575b5439ba (3915256 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cedf733ddd6a415e8740280c3238b1e4 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\20cd89b35beb43bcb17c2cfddf4bbd3f (3915256 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\e1ca213a7d2074eb624703fa30018813dfd2686377818e4cdeb396d07702e90b (96366910 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\39a67f93b0bc4d5a9a44d8954ad80eb7 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8683df6ed7df46869172e0c87ff5d9e5 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8683df6ed7df46869172e0c87ff5d9e5 (154727843 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\80912947a8eb407e8eabafdbba136265 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\80912947a8eb407e8eabafdbba136265 (154727843 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a23e06ec143e4b1a973f11e931105822 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a23e06ec143e4b1a973f11e931105822 (154727843 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1a8c898b20d144bbb648a269ac5482be (3773220 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5a239a12af024d63821a37970db0bb8a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\90382BB77C45724AE5F6213C08D35BD333ED009D16F9D24EB3DE2F47F4DF286F (154727626 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5a239a12af024d63821a37970db0bb8a (154727843 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d331ded58aab40be907dc0d6c6e4829f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\91e27818eb8449b68118ad502daccd83 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\91e27818eb8449b68118ad502daccd83 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\21da4a81312641ff8412d3e7d7712c8c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\21da4a81312641ff8412d3e7d7712c8c (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\21c8dc17a29743e9b2389be03b86f90d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\21c8dc17a29743e9b2389be03b86f90d (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bd8300bbf93f44b7838dccda567af647 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bd8300bbf93f44b7838dccda567af647 (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\32577200edd846fc99d1071c9c5abac1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\32577200edd846fc99d1071c9c5abac1 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b8795bc54a5346fd8a8e4d31a4ac9469 (3773527 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38cb615715c240c8842be83e98db145d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\38cb615715c240c8842be83e98db145d (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f16fca4cbcfa4d5dbec0a2b5d6644e5f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2ff2de2c7a7544f7917233550a77bcde (3773192 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8db1041f091640bfaebb95c2323592b4 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8db1041f091640bfaebb95c2323592b4 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\16a312f9a1d34daab9d18635830c65a4 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bba7816e80ff4dd7afd2c5a5d3e5025d (3773191 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ae7001e2d254d22bc4f047043875447 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bb3f934958e04194ba2eef339c047e6d (3773191 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\01b5cf2987cf48bbb0cb5364059db16c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0513bd0de3544c6dbf1dbf4f75134ba9 (3773192 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\01daa260edfe47fa8f630932df240c1a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6f1fe6ecd1b5429885081f3a6519c3cf (3773192 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\49ddf9f13aff4df2a2e822cc3689a4e3 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\545e0db8d1b14852a98ccb501ad495b8 (3915248 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\17ae2ad60e454d30af9403dda6cd9148 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\253b9af7649c4fefaa8fef2fda4655bb (3915248 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d4ac5c6d5c4f4003826b6ec2d46abe40 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a873d8414b9b454b8c317a71f96be03a (3915248 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d65b9d12f783402cb93a7d9b1675bc51 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b09a324451e4477fa8aae32af1c68643 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\adb463e4892c447d864cc63b89af7543 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d05c9515325a4b2d9346563e6e17d9d4 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c4ad00ad750a4953b0f931a878271d2a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8c3ef2f7d1bd4854a9d5386e6bec0aed (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b09a324451e4477fa8aae32af1c68643 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d05c9515325a4b2d9346563e6e17d9d4 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\adb463e4892c447d864cc63b89af7543 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d65b9d12f783402cb93a7d9b1675bc51 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c4ad00ad750a4953b0f931a878271d2a (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8c3ef2f7d1bd4854a9d5386e6bec0aed (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\68aaf388064246449d898512a7af450e (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\68aaf388064246449d898512a7af450e (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5ea47ab108f04c68959d05a2aee4b0c1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5ea47ab108f04c68959d05a2aee4b0c1 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7717db4a959d42589a051a552e119632 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7717db4a959d42589a051a552e119632 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b190ec6238f4defa7913a38f6ef21f6 (3773528 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\61480add51324470aee0468c682274ce (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bfde47d865704b96b4752bc5ea45a024 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\61480add51324470aee0468c682274ce (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bfde47d865704b96b4752bc5ea45a024 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3f0e30e96c7140829f34666804b88f3f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6699d7b07149447bbc8cc93d86ca0c11 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bcdd0ddf9d69431c9a32f76524fa71b0 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3f0e30e96c7140829f34666804b88f3f (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bcdd0ddf9d69431c9a32f76524fa71b0 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6699d7b07149447bbc8cc93d86ca0c11 (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3d62f7f4b21a4f18a8cba35cd5ae1e7b (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3d62f7f4b21a4f18a8cba35cd5ae1e7b (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\05a0761cc1bb41c5bdde4178e8c20dce (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\05a0761cc1bb41c5bdde4178e8c20dce (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dce3900550a44d1ca0ef9fe49c5046c4 (3773359 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7eace4d27b64471380086ea3f98247fb (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\91950c2e341946d0bed64d72db533863 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7eace4d27b64471380086ea3f98247fb (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\91950c2e341946d0bed64d72db533863 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\568629832ade4f1c94a9358a32873e2a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e7c374d848b2457b8048f89e520d877f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\224ff876e1374d67b4dce16d9e9efa19 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\568629832ade4f1c94a9358a32873e2a (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\224ff876e1374d67b4dce16d9e9efa19 (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e7c374d848b2457b8048f89e520d877f (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d8c6dc10ab7a48e4839fd04e5e25948d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d8c6dc10ab7a48e4839fd04e5e25948d (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\47049d8b581844d3973f0a0065751726 (3773359 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7f0b6ef141af4d81982953e26e07f145 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7f0b6ef141af4d81982953e26e07f145 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6685c3ac7e214331a9923782cb424acb (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\501D5BF32BC476C647CB08A9C7034850C36F17BC8EF6720E434051A71D1430EE (154727114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6685c3ac7e214331a9923782cb424acb (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\447a6648bf834a02962a14a90e10d825 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f9d642bd74184ee4beb79a6cda98c9d4 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\508d5a797f3c422ebfa496b8ece5e16d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\769bae2bed0c42238fcda47b357146a5 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f452a917653e494dba80aeae1cb89049 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2d7eb2ed63b14642866c45cf94f35a20 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b0380b767a594af18ffa018830c68b33 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d1403a0f654d4499931758687f8cd38e (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8e4883bd218b4bf69c3dc7123a5dd1c1 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8e4883bd218b4bf69c3dc7123a5dd1c1 (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\928d1412c97a448e9c3a2e1e0991ab9f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ebe4a9a8f9b9467fb695b8c1537e8429 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f16cf765641849c094dda3376219dc81 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6a371276422b44d893a5b218c0570095 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e8a4e9fc1f024a6292cdab663b88467d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e27a3b8ade1a42a9af8d624220a468e1 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\086d4d195f6a4b788cf0c2964c218677 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\07a90df8310840a5ab632be450398b7a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\07a90df8310840a5ab632be450398b7a (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3795168e44414f569d3c7d45a0b288da (3773859 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2b6fe42af5d24cfbbd2cceec76f729cc (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2b6fe42af5d24cfbbd2cceec76f729cc (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\36aab91e2e254b6494f5ac2218bfa3bf (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\36aab91e2e254b6494f5ac2218bfa3bf (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d38d1a4f047f4f2cae743b008c9efc48 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\73f6fdb152014465b7e53296ef42e08b (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5121157606b7461bbfa5168284057358 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\01914cc967fd44bb990810d849b7d7d1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d38d1a4f047f4f2cae743b008c9efc48 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\01914cc967fd44bb990810d849b7d7d1 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\73f6fdb152014465b7e53296ef42e08b (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5121157606b7461bbfa5168284057358 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2be09b2db8cb45369e420ec178e85d58 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2be09b2db8cb45369e420ec178e85d58 (154727331 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d12cc1437c5c44308cbac383adf5278a (3773471 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\33ade79dcfcc4dc1ab3600e35d1c2920 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\C7F1E2813C710C93F1873DCABE87CC1F818281F59B64DC64BFF387D728B224FE (154727114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\33ade79dcfcc4dc1ab3600e35d1c2920 (154727330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f1216fec854946938cbb92f240409e87 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\730e7aeaca454a55b31377431067b1b8 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\48612937b2f8456ebfcd9dcb08adebd4 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\faf481680aba42c3b82ce081d3ffcd50 (3773304 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ddd7726e71264e4cb0fc60b4fbdb6abe (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ad1d210cbe9946908d4d1bb47f9dfa07 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\917c3a856c5e4541867fd99dd9c51f2a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\77805e455b6149c3b841b212d87172db (3773304 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1af6a7ac73744e8c8f351c055d3253e7 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c2d60abdf77c40a686ddacf38295c1ce (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c2d60abdf77c40a686ddacf38295c1ce (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\88ecea967e3c4a82a5e568e4941a25a5 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\88ecea967e3c4a82a5e568e4941a25a5 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\74646d8594ae46bc9f01bc5fabfaa899 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\74646d8594ae46bc9f01bc5fabfaa899 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b20560038c514eebbcf48532b7a778e4 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b20560038c514eebbcf48532b7a778e4 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a538f236c58e4bdf8f3c388862608b52 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a538f236c58e4bdf8f3c388862608b52 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\55452e5a73e145eab2a960be7b142d33 (3773556 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\82f24492c63f4ec380989660f44ef5fc (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\53c3688aa12b409ead72ca19ec1fa31d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\82f24492c63f4ec380989660f44ef5fc (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\53c3688aa12b409ead72ca19ec1fa31d (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d281acbc6f5646ed9ee9cf99a0797624 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f81f0919651e4f058fd710724d6ba7c1 (3773183 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\14f83247e7e04b8884bd3d6ba2af54c5 (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\14f83247e7e04b8884bd3d6ba2af54c5 (154728354 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\67b6f687b4e04d5a9e886667cd55cbf9 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0eebc2612db540ecb64a9e1551f1e24e (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\85c7c2e84a904ba68251b303090cc8e0 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\557d9c3e632541c1a505be886c54f0a3 (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e955e514438c4653a66ec2cd58b42d63 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\593b10423cc542acb43c0111c41a009f (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a7fd1f0e7ec454daf742eb64a4c8c9c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e78e48d3c1804ba98174edc41bd7e11b (3773184 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8e0324d6a6644a0d91dad5cee101f99f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f736032cb8104d2891422fa4428668cb (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f736032cb8104d2891422fa4428668cb (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ca80d40e160a48e4a22c3894dc761936 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7ef915035d004fe09cc682a4c7acd60b (3915220 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b9f6df7987b4405a28b63a836a749f8 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3bdca9f82c0b4fa196f2bff73dcb091f (3915220 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\807ad769a75f4e018c748ad0a1fb7477 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ab09c559a8a34f70952cd8220490d6ad (3915220 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7c807c63b91d4729871af06772065ffc (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c3c3c57c45a54f6f958eeaec532cb6cc (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\697c2b5c51864bd893aa8f73383eedbe (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0716197646f1472eaca4f7f1bed68b8c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ce1a735714f2446fa9454842b754aa15 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2c696d6b86ad4556a5108654e8fb7228 (3773352 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\12c636735fea4f53b16bc840c0986953 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9f37e11b907c4b708c5787fe87e29349 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\223ed847cfa24c8880d4bde34798c8c8 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b4bac4a6015446c6b0071a4c06c74412 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\41156ea451fd408e85019c7452e76813 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d2c931d35c434dbfb101ed08ecd9e2f2 (3773520 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3730ce1157a743e5b226bd09fec9593f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3730ce1157a743e5b226bd09fec9593f (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1339c9ca88414f0fa6d68bf632bdc235 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d3e61c0b36fe4e96bf9f79f355ff7e45 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fcdcbbe41371485daa2c9bc73a99c56e (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b30bbbfe57c42f189bc0b46328386d1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a6c67ba4fd0845d88262dd227604353c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3bcdaee1bc444905a3a04c6392d0bffb (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c56c4c269f36461fabf1c7c092eddb20 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e39baa602393471396cfdc7625ed84d2 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb25fde0b85a41aeba9359a511f84e43 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fb25fde0b85a41aeba9359a511f84e43 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b981859afe8b4ccc88a340884ce655c7 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\13a73a4d64824f77befaeeabcbd6f3e3 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\13a73a4d64824f77befaeeabcbd6f3e3 (154921891 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a6ed22866c524b27be499b4bf95d58b2 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e9922745c6c347cc805dd6ec8112df5c (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2ed0487332044803b85537ff89455abd (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4179c2f6e45942549136308a461af4d8 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\97c025c91cc6478f93d38d15c3a4e825 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9ee8c2428def430ea74fd1eff9c831b1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38f6a305fb474198b781010438225827 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\38f6a305fb474198b781010438225827 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d27343382aab474f83356c8d22b196b1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bc8a814bd5c642caa3990d227a2f8805 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b8bdb350e874db7845306a87b9cfcbd (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7e72a6772b9432ebccd3335c57931a5 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a7e72a6772b9432ebccd3335c57931a5 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a0610e376ba04573bf4bcb3c1401c542 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bc8a814bd5c642caa3990d227a2f8805 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a0610e376ba04573bf4bcb3c1401c542 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d27343382aab474f83356c8d22b196b1 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5b8bdb350e874db7845306a87b9cfcbd (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\866631fd6f714e60a906dbee51f3d165 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\866631fd6f714e60a906dbee51f3d165 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e0f097271dd74d299cde2aad96364014 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e0f097271dd74d299cde2aad96364014 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6140a0689331416fa7d8d8658a2733ed (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6140a0689331416fa7d8d8658a2733ed (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\5812cda743cd7aab73329e522f3fb11f2b0eed5d774252a95d25b7e5ca24c335 (6971767 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9d48a69026504865a54f7b4c79964e48 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f8ac8e6237274e5087a1010bf073ff19 (3773668 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\489036a0d4879189de5e2283c531066a78e2e34787712bec29b464f446f6ccbb (7055190 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ef35a27e8a1946d89c47e65e332212e3 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ef35a27e8a1946d89c47e65e332212e3 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\f1a35d0da07a80791aff1165768648c5d7ef02d8ef56ad69fed4d37460542662 (1680741 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b83e8593c0a4d9f849f9790897fa93c (3773556 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3dcbe4fc031b42fe92f508718762c6ac (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\67ebb323606f40f197a12419e9e8d3f4 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3dcbe4fc031b42fe92f508718762c6ac (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\67ebb323606f40f197a12419e9e8d3f4 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\9654083beb9109523ca3983e86f26c39b1631b7a9c8cbeed0fbef9d8e316449b (5491557 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\224d243a5e49460a5f14183f7ed0d91ea0ab4bf2e9a64f28956c841c7a46d9c2 (12103519 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\aaf4ddb920bbbdb7a7282d8ab7c854cd0fc8b59ce94925cba09415aec2edb1ee (4477498 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\981a08ceee61ae3b979a34c3ba13ec965e0a0d183f64e560501e182ce8405d29 (99811426 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\2cd66db9bacd0184f456d9db626be775a5067a3d8456dd0cd9094c839b8177ce (220526 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6719756b62c04206903978818a55760f (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\281e39c4213b46488a5ebeb26c711656 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\55e751d947d0444e95dab899b2bd8550 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\342c9f906f474068a1f2c89870b20f13 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\67e2d846f4c6498abd30dd4ebcf06be3 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c45a4ba32f5e483f8cfef9d5a8bbb4ad (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0451695c32b64b379755afbe35df19cf (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4b1c2cbdad5241a69e993f403b426b65 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4b1c2cbdad5241a69e993f403b426b65 (154921891 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0cdeacc17b734d73954a534c2f59401c (3915600 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8282d9f30df44a6c9ebd4683a12d4744 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8282d9f30df44a6c9ebd4683a12d4744 (154921891 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e4999de32a4c455b9b8d529ffa586c59 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\DA0224C9196AF2966F03CFFF98D242B44EB4DD0888349673769A60331E82CA48 (154921674 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e4999de32a4c455b9b8d529ffa586c59 (154921891 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f68ed9cf68644944bf2d2d6da996777f (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f68ed9cf68644944bf2d2d6da996777f (154728354 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\205f9da7332d486f96dc635948a7436b (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6ab327a133e74afb99bf502d2fbea019 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc1445c44c83466b9c1ad3136e0954e1 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d5fb6a8696254e23b29aafb23039089e (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\adb3bda20bee4b1490e774907f05585d (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5bc78bc95e7940958d8437e6f4799289 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2a2fa46a1acf4d4e8c74973b884cd455 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d9eb6a99bed841849314ffdb6a4db33a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d9eb6a99bed841849314ffdb6a4db33a (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\865950a1045e429d9eb19f432630e375 (3773666 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c400c1e385b344cdb1541b78df75a0f9 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c400c1e385b344cdb1541b78df75a0f9 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1275e098b780488797c6f0af2178436f (418627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\DEAEA0BE8974327A33A45887DF6FCF4956310AB1A1EF9B992CF6B5662EC5FCE6 (154728138 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1275e098b780488797c6f0af2178436f (154728354 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\887a433ac00c4ee2be4e0732d8d2d4aa (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ae8834db49594874979a2b6a165073c0 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3e86d3acb55e43609c0a9b8f25393eb5 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ae8834db49594874979a2b6a165073c0 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\887a433ac00c4ee2be4e0732d8d2d4aa (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3e86d3acb55e43609c0a9b8f25393eb5 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\419f2d909a764316aa11d480676dcedb (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\419f2d909a764316aa11d480676dcedb (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dc0ba415751640509b04e33f7c804e1f (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\dc0ba415751640509b04e33f7c804e1f (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\61f3b33383204ebfa539b8b08c9ce0d2 (3773500 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\643cc44e926644f29c884163e0b0e95a (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\54502698abfb43c19383204aecdaafa3 (418628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\643cc44e926644f29c884163e0b0e95a (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\5467F8999546F87BF676099FD063F3724298D5290CE0E43E7FAA89FA933E5884 (154728138 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\54502698abfb43c19383204aecdaafa3 (154728355 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\e0fb0191d6297fadc4e311c0c06416ed307ef137a0e6eee5f387f76d9be31c28 (1168253 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\3c1059ffacb88f3861fbab7c69798d1141a1ffefea7f441b258f470a90866f18 (2798940 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\feaba5c4ba50d77c7c035e9d04cac790696ac96ac0885e3635addb0dfd41dd3c (770923 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\e5a65b2c0935f328b0dc1c0b88baf275c83e141f7783ca8d3215975c8fb57f4f (1571179 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\df6e4b4176cbef0389bd345ae9769884f01afc4a225831bce7a51a198d53f0eb (3023717 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx-tools\windows-x64\9c949ca08bdb21f7923c3e3471cb15139c4cab515f82043c2177a323d26c5113 (16489108 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\61d03c8205fb45b7ba1a73457e19cd6179527f499c0eb7b0f8884f932aaa0478 (2886412 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\0e9c829b1f8a1dcedf568bd742ec1b8f7e2e77cb94e62a3c00fb7c64428b9a4b (93044 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d07a39bec7cd4d3eb039c6c67c1150a8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1a67909526af45d2abba426bd450c929 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1a67909526af45d2abba426bd450c929 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\de0d482b5cf44b679e9b2f8eb043261e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\de0d482b5cf44b679e9b2f8eb043261e (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\76f3e5fbcf7e475180369c361fcfebec (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\76f3e5fbcf7e475180369c361fcfebec (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8e2afd38bf404d049bfff89805b3d8bf (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8e2afd38bf404d049bfff89805b3d8bf (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\24015e5d946a48d7bbc82d1f71b6255e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\24015e5d946a48d7bbc82d1f71b6255e (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\164e8004ef1e4509aaf6aae37533f481 (3779878 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3d30df9199184a1ba36de3e649ded002 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\64FF81F912A62EAD8282C64CEFE2A94E24EA2BC1C683B069D42BC65695653926 (154732791 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3d30df9199184a1ba36de3e649ded002 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\8181c686b4b71030a5fc583c80a1cc44de1e3b9d4041a447c8266212c0377c7b (96368035 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5d896f3082fa41258b4d5dc93e84720d (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df87c77b8c944e77849bcbef7bc803b4 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1a94be6da4e54d928d8f80acfdb63fa6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8747fc67008f4cf29bd2799457a6a28b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d8edea2fa0204584aa3423fb59a3c13f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1b6004d551774592a6747446dd4fa593 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f01e97dca39a4bc5a14c530009cc294e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cc990dab14a846cbb3efd2149fd06875 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\836525e3deba4a58b1e517e764d8d715 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3c9bcda2e77b4e7d89b1059e07fe9cb1 (3918792 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b7bd54af11934850b8903fd26c61ad5e (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\D70406D81BBDC1C13FEA20233776365DC148C52082D29B6E13D5EC3DD0047188 (154925815 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b7bd54af11934850b8903fd26c61ad5e (154926031 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\84c1b528209c48369bee626a5278df91 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a72ccd4fb2004513913d8bd59e4d5139 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a72ccd4fb2004513913d8bd59e4d5139 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\14ebb7ba7796d8b5849d819adc899207dd2b4bb046ff17acca7232e1896bba6d (6971255 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\774990ad3dd964a076b824ceacf3fb8a694b09fe226a21ffe3ca5ffc1ae31b61 (1168253 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\09176cd0a118effa968ec533adc405bc4aea4b2e0cb27976380b9d82eb52e481 (2798940 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\c2fe8fe5e43b71c07e7ba8618f50b55c68bd7821e0a74c380b9c754cb44e6c82 (770923 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\5ee3a69da74370766007b2a5c842445088aa77af25edd838c17a790458c30db0 (1571179 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\d1638ce9c310d7a725cf4c8b68fe48b4408a689cd76d2207d50246a79e9f7d50 (3023717 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\a0738914776b5f0e627492a61c16b8911b14d16f3bcdc3fe43c6fefb539db7f1 (2886412 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\1e93072e175ebd18bd8ff070802c395b10c17f4ce95e94685dab3a5c64bd591f (93044 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\501e13d33a1f4bdf9cd9d71f57bec571 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\95ac905a545547c6b109158d9fbe079e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c587b27853e94585bedf73da9659d19b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df6170d8d6aa4d5bbf458e3b40085255 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8d023cf90adb4686baa2c3f55f0f5e31 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b48d73557dac481a9f22ce5f926d42e7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44ce657c6fe34cc28c583756938afa48 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1ded9a9ad1e24d8ab0d82c4e1d163df3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1ded9a9ad1e24d8ab0d82c4e1d163df3 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\4294f2d7b213bdb21659cf829ad629a20b99eaa1a665f057c7ce984b42e829bb (7055190 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\e27d461178508774f0ccbfd2f3e72f282ca0cc5cf3d54487428280a282e6189c (1680741 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\48d650a93cff4584b7fbb947796343cc (3780102 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\07737502105b494689e4f89225720cc4 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\07737502105b494689e4f89225720cc4 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\dcafed18fd5e27c7942ce3e391a767a50dee199502678fff182588c178e8e28f (5491557 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx-tools\windows-x64\51e8266c89eca806f576f3f3a35ab8b882062883cc78d2ac8984b2c491e9466e (16489108 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ae2ebab898ae44c6a1c1629c6d753ad9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\CE2AD2098EAF2633572C9549C5C6F95AAF91B627D618DB0D6D516B1EB44C35F6 (154732791 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ae2ebab898ae44c6a1c1629c6d753ad9 (154733008 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\6585e90f944bbabf7195aa2ca9255bd84bf937cdf1dd5aacd95cc075c516de39 (12103519 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\655578fb9ac14a0f9c26757dec2941f6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a55f421967114818b0c939c299e08f4b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a55f421967114818b0c939c299e08f4b (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ddab69ca7732461fae63c88cff4f7946 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ddab69ca7732461fae63c88cff4f7946 (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\29d8f2bf345c40728a60208d46e30124 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\29d8f2bf345c40728a60208d46e30124 (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b36603d491f4c2cb642ed38510ac868 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5b36603d491f4c2cb642ed38510ac868 (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0efb49c6aed1480d875e12b65e6c3fe7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0efb49c6aed1480d875e12b65e6c3fe7 (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c6ff0845441948edbe7e2c171e4a0692 (3780342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\624974d5efdc48988ecd1d3710bab931 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\C3DBF1E67AF6FD49A219164FFA22530529ED96CE51CCC947402A7C9DF52CF4F5 (154733851 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\624974d5efdc48988ecd1d3710bab931 (154734068 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\49ebb4bcdca34bc282873c72fe85cfaa (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b687adf607a745b2942f127912a019fe (3780061 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\512356a08d944ac494b4857431f4d156 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7d5f8b9c851432e962c5651661069ff (3780062 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\8eaa72542a3ed4d1c4c2bc452446d7efa60d327666f29e46aaa50286d606999a (96368115 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ca9d4741cd5f4116b5255084d4888770 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\19ba4c33cf7e4bf6b5510ade32a1bf03 (3780116 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1d5781f6121e4db9b9a596f20f411ec0 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1d5781f6121e4db9b9a596f20f411ec0 (154736627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9dbeca550b2c4ff5b6153e993ac65d07 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\763c87afe9214f2bb39ebdd47958ec8b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\443bcd7ac30c49b6bfc5972b0552d40e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1177940b295e4889b62604ebbebfdcfd (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c4c71b457f3242ce9702b6db77efe197 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\15e9b0e8e13a4bcfaeb09dc04d7adece (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bb76a9b8ea9e4cb5bd8c9e9b683ad025 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e68b11c2a41544b795f150e431329c45 (3780328 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d099a256abdc4a9fbbf9133f69abdef3 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9fb0ef0bc40a4deaa6491954ba20d731 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9fb0ef0bc40a4deaa6491954ba20d731 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f6f2bd66cf8045d28b2a15a90f67dcb4 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f6f2bd66cf8045d28b2a15a90f67dcb4 (154929140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b8395abce4647f3b03233bde90f697e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\de9ac198b84342abac034d4ad3f230be (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8baa3759876243bd94dd60714eb80121 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a7f11861f294f889ef7c81d88f9034f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\835fcababf6e45708aa3963874b8708c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9412a4e31cd44922a73e5c610352b61b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b70c2d69a7094d959d4f147dd720cffc (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\54de1e784d07431981fe697e2ed1d0f8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\54de1e784d07431981fe697e2ed1d0f8 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0eac8b52017e4f0bbbb666a3004c4931 (3780385 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\db7ac51710fd4a4181b06a64e6c860df (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\db7ac51710fd4a4181b06a64e6c860df (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7b1a0df663a246c7869c651402cd6e3b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7b1a0df663a246c7869c651402cd6e3b (154736627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\668a5738839c42faaa945e4ae113da3f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3fb4c2b870194e41821ac1bc192e986d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7fb57deb763242fc87e3250b291f2abe (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4fef4b6f5aa548eca29cbb4a97db7e36 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4a7f522f506e4d4ea723c559a324ecf6 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\04de6af68987446eb877e6d5d0cdc38a (3780328 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a064f42f60ee48fe8c505ac8cc87d9da (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a064f42f60ee48fe8c505ac8cc87d9da (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\4645fd923ad048515584a44ed5e9f71f50fdea6721b200205ffd92833f35f1ac (99812687 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dc494e7f3d964cdd8f27a9c94ba6cff2 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6b3949c24f584f6c9ec884a514c086e5 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\26caed6eb3ca49b99b11702f8ff613cf (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8ac6c13b1973413c8f20a116b01c4450 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ca5ec79b4a76400eaac0e0e4ef39557a (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7f3b7838190145faadff31da3c8bc9b9 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5ab735065df04f66b500f93b4c013df1 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\433ad15bb4484692acc0be170801ec87 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\433ad15bb4484692acc0be170801ec87 (154929139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1ce59baf2bdb4b618f9942dbdd2e585d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b2cb6571417477a84c1fc11b6d83b06 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9b2cb6571417477a84c1fc11b6d83b06 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c92604244fcc4aaeb3d8ff36be91d7fa (3919560 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ccd60045828f409e82da1170cf2e51d1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ccd60045828f409e82da1170cf2e51d1 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\039f3afcf5264c80bb8c1b6b4fbad304 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\039f3afcf5264c80bb8c1b6b4fbad304 (154929140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8c255809dcfc443e855473c43111ed9e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8c255809dcfc443e855473c43111ed9e (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\102f3b32dc954cf6be71c41833195018 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\102f3b32dc954cf6be71c41833195018 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\407815bc9ad04c79a56c8e73c3e9dbbd (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\407815bc9ad04c79a56c8e73c3e9dbbd (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\76748af9337c4b6c93a3dd4d7641af2c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\76748af9337c4b6c93a3dd4d7641af2c (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cfdd67d5f0094bb8a69dab2562d20cbd (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\73EE196E5E5418A3A6E0077A2DC95611314FCA472CB1AD1A234DEEC3E3FCF74A (154928923 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\cfdd67d5f0094bb8a69dab2562d20cbd (154929140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b059b2ae21a7404dacb9ff00b0fc647d (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b059b2ae21a7404dacb9ff00b0fc647d (154736627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\817f437874b941919407403676ec890d (3780330 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c2d178b3a90a449987d9567bc51092e7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c2d178b3a90a449987d9567bc51092e7 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\570a3ec07ff04c48b038ab7174eae29a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8513107879c4adc9bca16403fe21379 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e901000ec12f4d43aead1e01d29a4e12 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6c005d967c984592b333c14467ace35f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7f8d7ea4a64a4720bdf53c937dea2a9c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\53d6f9f8536d44cf8a5ccb81c1dc59c4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\599cd7330a564e10af24ec0d981c2f5a (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0c545beb641747c39d380c34899e516d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0c545beb641747c39d380c34899e516d (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b394bdd8cd284822817b4e0e87a65830 (3780440 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fdc30689a937497e9af1b5378dc3bdd9 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fdc30689a937497e9af1b5378dc3bdd9 (154736627 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bdbba2400fbb40e3a748bca7772beb9b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\310007989FC3F8008658A87228B4D9FF0C26FA8B626E80C8F6BF09C00C9DF02A (154736411 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bdbba2400fbb40e3a748bca7772beb9b (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d75ce6c76e2f44c9940dae50e52027dc (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08fe9b11530743fbac66ed5d607cfd3a (3919114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\585998fbc1dc467d87194975a2fcd1a4 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0709a5cab34045668579bb5a7aa06b58 (3919114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b03a48cd58024b2490c99483b294eefc (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\abc402ca586940b7addab4b931bfebe6 (3919114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\655383c49d824af1b80e8f3cc7149c5f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ae26cc1e50849e1898ce840ba69e8e6 (3780050 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\548e5f720ddb4f469c99da97874110e9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\1E4159CC10D941ED0CE4DDB69051857A2E0E4486989419A84A1B70983BD1952A (154736411 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\548e5f720ddb4f469c99da97874110e9 (154736628 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38285df15d534bc2baf6d37faff1bde1 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ba8198be74944096bf7707bbd3d044ba (3780049 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\255027255db94ac2b4aa1d05c46bfb38 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e31502c71a144964be7b575d9af6409a (3780050 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f90daecf48c64c7187b79b6688809ac9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\396ce525e26e4785ad10aebdbdec5888 (3780050 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\25d94f2964e24d008f102c568151ac44 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\785d89972a2548bd8085e933fed6f9d8 (3780050 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\983a21ec582a42b69ba5ab6c48f73f75 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\22001eaf598445fc98a667292617608a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\22001eaf598445fc98a667292617608a (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a8b8095b95046e48d13be3ab832df5a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8a8b8095b95046e48d13be3ab832df5a (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ae85f4626b84d229c322a7121e809ab (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0ae85f4626b84d229c322a7121e809ab (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\be9915bae4604024b14fa4fd21916f8e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\be9915bae4604024b14fa4fd21916f8e (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\31e160252b83441aa4fff89d883a40b9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\31e160252b83441aa4fff89d883a40b9 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6bd4714bef7d43a39679ef08098d03ef (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6bd4714bef7d43a39679ef08098d03ef (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fde444960cb24850b43f3d33619e6c8f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fde444960cb24850b43f3d33619e6c8f (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\291ac1c21cbf4f9cad4dc21ad225b1fe (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\291ac1c21cbf4f9cad4dc21ad225b1fe (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\156af7e66831499291cf725e8490fa53 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\156af7e66831499291cf725e8490fa53 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d283f510d9104e2286f04558b51bf4f6 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d283f510d9104e2286f04558b51bf4f6 (154755613 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\356dfa8284b241008d334f4a7dacfd28 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\356dfa8284b241008d334f4a7dacfd28 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3abbe1072171451f9976b2771b6532df (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3abbe1072171451f9976b2771b6532df (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4d4919249f104c99b311c9a187cb1d15 (3780485 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ff3ba4a11a6e4772838e021425dc64c0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ff3ba4a11a6e4772838e021425dc64c0 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3aa5312439a04432a5d244872f6ed041 (3780261 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b632744240104c878698ddecd892adb3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d0bfa7ed6fa342de9ea5a7a7e7006bff (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0f659ff7b96a45a4a6fce44927666d48 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dcf72d6cba72409280dcac8ab3b2c82a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0f659ff7b96a45a4a6fce44927666d48 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3f959c023138478a860b062bb7b3ace9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e70b92633bf543aca894868d458a110e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d0bfa7ed6fa342de9ea5a7a7e7006bff (154755613 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b632744240104c878698ddecd892adb3 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3f959c023138478a860b062bb7b3ace9 (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e70b92633bf543aca894868d458a110e (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\91149337C7531714E3194590229F6C4E7CC07DF6C94D772874037714D4BB2904 (154755397 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\dcf72d6cba72409280dcac8ab3b2c82a (154755614 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e06bd3e7e9ba434b9625f7deacec98a6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\42cf637a057b4453a7de9c4e4e9a5226 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\673c19777d10459ead25af3b1f012110 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5e3de17d53bd473f8f4a28769a5517b0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\deb3aa21d11347b09acbdf39b79ff669 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\517be3ecd97c44cea42a7218c649d551 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dcd9b07ea344444dba37bc6b53e361f1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38a7c439ca4943629ab31a249d533085 (3780250 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5121e20a3a7c4b8182105ad7e0a935be (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dff8e2a6feec44639f037b8e4be85f9b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\dff8e2a6feec44639f037b8e4be85f9b (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7ab88ecd8d024c85ba98755387deee06 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\31676b380e0d4826ae24123ffcbe70c5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3a57c9c50f174f72ac2678b0a204cfcc (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a853d765a34c44a6b011fc44b1e8c3c5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4e886f24c5db4766877f6bbd834f97e0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\699a505d6b124df3baa3e35f426431ac (3780361 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1ee638493f9a435aa26e7a604c1f1316 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1ee638493f9a435aa26e7a604c1f1316 (154756125 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8f8cf6a00c8d4b4faca80a0f3918e537 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bde56cab914645d89e4175ffa6c2a337 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bde56cab914645d89e4175ffa6c2a337 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\76153666ac4a46daa805615008f71cdf (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\76153666ac4a46daa805615008f71cdf (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f05e0873ae4840f5a0f498d3bb98bafe (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f05e0873ae4840f5a0f498d3bb98bafe (154756125 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e6762f57ebfa41b89a316a0175a81cfb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e6762f57ebfa41b89a316a0175a81cfb (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8e74e737c6549bfa9dbffcbc98a3f1e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a8e74e737c6549bfa9dbffcbc98a3f1e (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\17ed95a7ddf34ddc8403633a717ea82a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\17ed95a7ddf34ddc8403633a717ea82a (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\024ff61e8f3644fc879d700916075311 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\024ff61e8f3644fc879d700916075311 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f7dd001869bf495daf24dde9f8a76017 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f7dd001869bf495daf24dde9f8a76017 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3a983af8ca8e4308b1cf5c9000a155b9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c10a09fa452f459790b177a1963aba7c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3a983af8ca8e4308b1cf5c9000a155b9 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c10a09fa452f459790b177a1963aba7c (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b5c59d6f645541ba9449ebfa41d2838a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b5c59d6f645541ba9449ebfa41d2838a (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5425493a59a44f56ba0a2eb626a5f111 (3780529 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\516a0e95ddf2405a9b495776d0d254a8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\516a0e95ddf2405a9b495776d0d254a8 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2809a6a0c4104767bf43bf2ab89905c1 (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c58e579f806b48ec86d98d665d6ec9e9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c58e579f806b48ec86d98d665d6ec9e9 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b964211679d8499ebe00d9e06a589250 (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4164cf5536dc4033bbc660e4be29ebbc (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4164cf5536dc4033bbc660e4be29ebbc (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7b3b95fbe8ad4ec5ab20e9927b89252a (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\768d0a06daab49afb241516f6e22c6c2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\6CBD62EA1155CECD3C96061F77E8DFF7F044921B035E37C3A7EA7FF12B4E73E5 (154755909 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\768d0a06daab49afb241516f6e22c6c2 (154756126 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d230554b9e4a4597aa52c52d04200259 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\17916e05896c41aeb72f86388adc9677 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\74241c83643b491e906f5e045a719cfc (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f09337cb96af475ba4b2cb00db535284 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\06bdfc4a78c94925b1be4547bcd9570f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d4f3e68079b0418aba3cc36e68443771 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d09b3cbd2ed941839e011aea288c04af (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a38e6c3230ff4a60b819594913ba5609 (3780228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d6f41447508249d191e8c17e35b9cbf4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bb8c6783f6824575bba005b755735394 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\efe5d84f6fac4b9a8d6cda2b8963469b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1ccde9ede437438c80b01a791614f665 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ba8c0269142248588044f23de436e312 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1b554d8ac54f439789c34a161aaccb45 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0d652d133d514510992dc738dc065c93 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b62e37ee10742688e5c8495bafa046d (3780248 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\47c0752db0ea4c8ba3423e7d24403b22 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\375879ecdad041ba8bc175b6c9a5e1b6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\375879ecdad041ba8bc175b6c9a5e1b6 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b4caf64a5d8c489d8a0706a9c3b8967f (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3bb70cb3579541a8a7f61c27d08cb04c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b60d2b9b69ad4877b8db439f640163c4 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7292d5cf53b14107be1f0c26050f7402 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3ef6e42823c44adc9ac8c1ac16829957 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7debc383b9b249f58c9f9c7209e9e53c (3780305 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\41c19523886643afb80c9fb66c0f260b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\41c19523886643afb80c9fb66c0f260b (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b965116427ba4890993cddc3d25dc2a4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2d53f32bb8fd4af69dbe94e0451930a7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2d53f32bb8fd4af69dbe94e0451930a7 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ad51286f4eaf44aead014523a094bc26 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ad51286f4eaf44aead014523a094bc26 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d11ea7dfe6874e9083024c6ec43effd6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d11ea7dfe6874e9083024c6ec43effd6 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44aa01ae67c3417dbb4fc59bc3e19e60 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cef4f7b8a3ee4708a504f42000f5f9d6 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\cef4f7b8a3ee4708a504f42000f5f9d6 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\400218c9716345c697bd6cbec189f4e0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\400218c9716345c697bd6cbec189f4e0 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0679f8a3f8244ea597ef07c8e05c4776 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0679f8a3f8244ea597ef07c8e05c4776 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b720c1a5c479490797615c2fbcf090b0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b720c1a5c479490797615c2fbcf090b0 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\09604f10fc904c01a9baffe6efcad18f (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\09604f10fc904c01a9baffe6efcad18f (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6c0870fe5817489194f60cdf416d837c (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6c0870fe5817489194f60cdf416d837c (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b7d13e7d4c40472ebc539e905ad38c93 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b7d13e7d4c40472ebc539e905ad38c93 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d630cf1889464972a7dded88901aee4c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d630cf1889464972a7dded88901aee4c (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\861ce602b29544c1baef04ba5e072978 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0f8e4bd0e1ba49bdbdb5a5b3225e60c5 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9e97345521c44f0a9dd6b64a7880f6b2 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\538e1e63261142dd8ee7022563940143 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\577619f6dcc447a49eba10f3a9615509 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1bf3553a408247d0b224168c5b6e14b1 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f89dbb768e4e4f87857c6e33314836c1 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f89dbb768e4e4f87857c6e33314836c1 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb0a194f1d5f46ee86af367501ac8f61 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b47bb8ec478a4d9ca6db351419cd667b (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b47bb8ec478a4d9ca6db351419cd667b (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4197012a996b42a0ba68078e20564cd4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4197012a996b42a0ba68078e20564cd4 (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b520d7628fa74050b598a8b7afe65f36 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b520d7628fa74050b598a8b7afe65f36 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ecb02d9b2ed843e59beaa9748a87f147 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c7c7fef2d1ff4cd0a33525896157b12e (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ecb02d9b2ed843e59beaa9748a87f147 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c2355e650ce94105ae0d461921f0ba8c (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c7c7fef2d1ff4cd0a33525896157b12e (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c2355e650ce94105ae0d461921f0ba8c (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1c5e384f6edc4ff0912317fb66b6c2fd (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1c5e384f6edc4ff0912317fb66b6c2fd (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a1da63f3f0e947c98acf2894587e70f0 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a1da63f3f0e947c98acf2894587e70f0 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e340a9c1046a4dd2801516e009d4aa71 (3780528 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9a1a4fea6ee1409dacac8b7478feacd1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9a1a4fea6ee1409dacac8b7478feacd1 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c0ed40a876204750a7e914adcd1d93c2 (3919616 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0e8429fd2126407aa198fba73cc59d42 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0e8429fd2126407aa198fba73cc59d42 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\27c4f8c7625c46baad46ca03620268a6 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\27c4f8c7625c46baad46ca03620268a6 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\26f9a0e5b177402f8bbe705357679459 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f5f3367d97a842f8a66df5aaaac2871c (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7d5db3a8cc1d4ac7b38007fb4ceb56ec (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7d5db3a8cc1d4ac7b38007fb4ceb56ec (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\693d78c357b3490c930d73f2c79e9972 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\313de36a407141bbb75ab1b925f947ba (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b9dfd98132094a7fb80f776241074112 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d37909c72e534d9b94b458209ad29b77 (3919169 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\54aace88f3a54b60b9163773155c61d9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8b8b87c6fd0d40e9828345cc7d8b8c65 (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c837abfe4db842438c015fb33184c04d (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c837abfe4db842438c015fb33184c04d (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eff5ca881c85470cbf52667ddfa9c5b7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ed667587e18b4d128c3e102afce2a9f4 (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fa02ffc110fa491986ac85ea14e8936e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6fccf4f6dd604881871322385702da48 (3780026 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6538ea45f1d5425da2f2a15981a3c53f (3919114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bf1d948f1aca495da48cfc8c33fb21a8 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\3D09528ABE914FFAA31AE2437EC1E5CEA1ADB1684FA0EE8F14D70B0E463ECE75 (154951493 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bf1d948f1aca495da48cfc8c33fb21a8 (154951710 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4811c34e0c10475a915c8c840dd4f8ed (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4811c34e0c10475a915c8c840dd4f8ed (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a1c3664de64143c698a4d142094d3972 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9aab2ef58acd43c1b1e771422a32a62b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\be5f73f3ecff4f65bf653273f4944d9f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\141045ac8f1741a6ba3363283fe557f2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\05d1fa6da747495cbde60bc5a0874129 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fca90745324c4916bf59f57a291abe8b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8cc244a89f5c42998b270c8581dbef44 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\731edf7d8f194a69adfa0c6d01329601 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\731edf7d8f194a69adfa0c6d01329601 (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a5b29b66452b4375843f145e91fbc216 (3780359 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cce1577f15b54ec8889caa790c3ea107 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\cce1577f15b54ec8889caa790c3ea107 (154757149 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\80ba5168ed8f402eaa3c51fab48d4356 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\ACF56F040B09FDF1BD9C95727D0ADBA4DBAE2C46A63E58296F66DE560AFF5704 (154756933 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\80ba5168ed8f402eaa3c51fab48d4356 (154757150 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e2404f96583f476184be176eb688a7ba (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1ec6fb267d774545be69ea7c721143ce (3919094 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9445ac0a318942aeba5b93cc6c32c7e6 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\27fa3637090740e48145c2b0d5e7ee17 (3919094 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7e12ab00b68349808c52729c12fcc078 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\95ebbc4f89084c15bc98ec4ab480ba4d (3919094 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dce39e3b318844298bffa547c216f054 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0382be4376a44ea9a1c81bd3d6f81deb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\18d350f0acf64d7bb0dc7609eed93757 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\89f80f37719944bd8bf674b3cfa8d011 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df0df7769cba4c3fa1fb1fced55f1092 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9e243f2f07334cab9f81d1e5632fa73d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b557fabfffe14fdab7e98c83c0908cc0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6655138138de44bfa813277c7bb9c7d9 (3780273 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\456e4e569d654cffa36d5c5e11f32f5f (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bf64eba7e1a64c63a2fc7a00e4cd0430 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bf64eba7e1a64c63a2fc7a00e4cd0430 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\13b62c9bf2294a5ba84c7aea404d164d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f71d51db936649bb9007c11a30a4ec77 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c5b930f542774270a0890def813f1c68 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fbaa5466b07f4f569d00808163a90619 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a2408b28cfa648b1b0bb308c3d244a3d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d26d4e86f9a64572aa516b5d7263c2a3 (3780384 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\332e19e568ff4e9685ab807d0a962682 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\332e19e568ff4e9685ab807d0a962682 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2f570c0217124571ac7d3638fcb62995 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c3223c16a76e4b0bb01b0dddd3529228 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c3223c16a76e4b0bb01b0dddd3529228 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fd2ef9eb4b4e4629a970e07de0742079 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fd2ef9eb4b4e4629a970e07de0742079 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b42bcce7444a430486d5027dfb3dc991 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b42bcce7444a430486d5027dfb3dc991 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8ae987f079d840ac90e5f64c842f0572 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8ae987f079d840ac90e5f64c842f0572 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e12bb2d2d08b4c1ca9c0e40e77f29cbb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e12bb2d2d08b4c1ca9c0e40e77f29cbb (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\838b32fe4a0b4247b008c95c9942c7c5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\838b32fe4a0b4247b008c95c9942c7c5 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eb98d424ace84347b6b36a41972d502b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\eb98d424ace84347b6b36a41972d502b (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\decf042978084a5d965609697499fa32 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\decf042978084a5d965609697499fa32 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7833d3e1d6b24644babe22231387fac9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7833d3e1d6b24644babe22231387fac9 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bffd7991e6ca4c5d92521ae257069517 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bffd7991e6ca4c5d92521ae257069517 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d5331cd8f2fb4c119049b33029efed28 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\82e53da105a54e22a58cbd07d36e6866 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d5331cd8f2fb4c119049b33029efed28 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\82e53da105a54e22a58cbd07d36e6866 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7ab72076f754245a5db86ba9acb4008 (3780496 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\214f9f495a9d49c2bbb57b7892ae5b31 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\214f9f495a9d49c2bbb57b7892ae5b31 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0d43052200eb4bc284aa668a4444753f (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4e2b1f4a6cc6496dbd1dccaf7fd37537 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4e2b1f4a6cc6496dbd1dccaf7fd37537 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\be232b077d034eaba51c0806cf967f42 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\be232b077d034eaba51c0806cf967f42 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\251f17e79ac14e04b0921070ae2700b2 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\251f17e79ac14e04b0921070ae2700b2 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d286ed43acf340439cc885841e8b0b9d (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d286ed43acf340439cc885841e8b0b9d (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\69733cd7c6ca4f33b32c4ad40f91dc4e (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\69733cd7c6ca4f33b32c4ad40f91dc4e (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\153a599201264996a358e6b1d559048e (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\153a599201264996a358e6b1d559048e (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0d156d0713f94306beb4901bea628ae1 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0d156d0713f94306beb4901bea628ae1 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\26d21eef37fd458192e0edd3ab1f077c (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\26d21eef37fd458192e0edd3ab1f077c (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc91d8bc94a347e7aa775e8f59b1156b (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fc91d8bc94a347e7aa775e8f59b1156b (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f60e9b58f3694a6782b41ca819e8b54d (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f60e9b58f3694a6782b41ca819e8b54d (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bb0e180ad17f484c91af6bfbcc5b6534 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\29e87a2c1b464660861df5efb0a9c808 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bb0e180ad17f484c91af6bfbcc5b6534 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\29e87a2c1b464660861df5efb0a9c808 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6160a8c80dd74dbc8be9db355b9e93e1 (3919504 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c28b2b6506dd470183c7c9c08da48d2d (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c28b2b6506dd470183c7c9c08da48d2d (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a9ac23b0bcea4af482f68be602844841 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b8cef1564a1b4a79806841e4a5398547 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b8cef1564a1b4a79806841e4a5398547 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7b392b674fee4e969a37eed72c439270 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4cf648e5b44547df9a7e9474c3c1201b (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d383d49dadd24ecda379417f94ca4e3c (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d1299294c13749debe2493fbcba1c2d4 (443714 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0a4daa1047594b22af2572c411a1a7d1 (443714 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e8d10951418948c48c6754f00910361e (443714 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ad9dc23d3b8c400494600c3fa2fd353c (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\216e792d30fa482f9427fd7dae2e6fb6 (443714 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\216e792d30fa482f9427fd7dae2e6fb6 (154952732 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d51288998df346b581851c57881acef7 (3919500 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bf513483ea194c4997e9a14cf65156fb (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bf513483ea194c4997e9a14cf65156fb (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\31418e81a85d4a2d802a53b3f670db56 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\31418e81a85d4a2d802a53b3f670db56 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\94798e421d0244e2a54d1ce38e95562d (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\94798e421d0244e2a54d1ce38e95562d (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6fb8ff93c3214c5685bb2715d0a646e1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b94c30a1ff4468cbe0e257ab68b6683 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b659e5d53e44271b1b388f89dbbc032 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1b1a249b11dd4717b65718a6567f4533 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\738032735fa4409abc725bb05d26eb13 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7123be768e244c0ab68a68f4415ceacd (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\893fb31d6fa94f48a1e984cfcd5f0f07 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\49f9b4d88f374cb6b80fbaf9daaaeefe (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\49f9b4d88f374cb6b80fbaf9daaaeefe (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6eb2c140de53471294ec9f595d7d37fb (3780439 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\259875f5d9144ebab0b94a3d5921df1a (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\259875f5d9144ebab0b94a3d5921df1a (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\58993ddd35d045a4920695a74bd43a22 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\58993ddd35d045a4920695a74bd43a22 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\177c19130c234e32832a9054f7fed653 (419138 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6e78d7073df94fe390f005e0fd385254 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3cd875fa4fe6417aaf525a857b72d98b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0add1c6506564c9faee71b02ae24655d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\906620aaad0e448b9593d38b229ae114 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\80353ef5ae5d4fcfa106dcf8bb5bc343 (3780271 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f33ded1bc70949b69dda1a6ae2f2a209 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\037f75b65bda4b6fa308251b233edbf4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f33ded1bc70949b69dda1a6ae2f2a209 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\037f75b65bda4b6fa308251b233edbf4 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d23e32c61e4541d2aff3124cba020b20 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d23e32c61e4541d2aff3124cba020b20 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c0e1b920f1314076b0c0e126707d2926 (3780214 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\540b9ecb2a1d492fa9fd7cb5dd47f906 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\540b9ecb2a1d492fa9fd7cb5dd47f906 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\96d058d083084f078de5d7954b1adaa6 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1c6c2af97a6f46c78db3640af11f79d8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\96d058d083084f078de5d7954b1adaa6 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1c6c2af97a6f46c78db3640af11f79d8 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0dfa167c49934159a777c3af4feb5cf8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0dfa167c49934159a777c3af4feb5cf8 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e5af4d8fcd9e4092bb0514f82f10739b (3780216 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5ecb2246ac854a5d861bb2ef7141bb44 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\7C12D80D50C7F4D3B04B4C5346EBD0552B868887F92FFEB896F8E8780D399707 (154758469 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5ecb2246ac854a5d861bb2ef7141bb44 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ee5e98b32e544d3da8a2c593f2ef4cfb (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3e0d4685e5e94efe858c8ba3a157b868 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ee5e98b32e544d3da8a2c593f2ef4cfb (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3e0d4685e5e94efe858c8ba3a157b868 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\610f5c5befed40fcaf3803827c800601 (443714 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\610f5c5befed40fcaf3803827c800601 (154952732 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ba8cf37993f4c4abaf591ab001eae72 (3919279 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\189e58bdd1a4429799eaa7387b183f2a (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\17280F1EBDC370A5CEBED62377186DF623A7485B2A745A863CBCBA03D23BC78E (154952517 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\189e58bdd1a4429799eaa7387b183f2a (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f629667586dd4dd98e1cc8fcb7aff547 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ff742f48199d4b47a20f0e0cb80b9818 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\10843e5cb0fb40f98c8a8ebb0110ba3b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\209fd10d095942678d81cbdfff6db456 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\90808eeb0d3d465bbac70f69e3efb6b9 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4ff4e946f3ae4355a651a40f8dbb5db8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cd7d4ecf917f424b8060837bb448fc0b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ba55ad4cbe24488a9b3eff2cbfd984d1 (3780392 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eb2f7a4c5ab848aca510e90a4b2b64c6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\22da1579189a49989e1307ce24a9c827 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\22da1579189a49989e1307ce24a9c827 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8737144f51c64ba1a9d832dca1b0d98f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87498882c501490f95d13627ec046720 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0d846baa2b1043fda3e607e9c0608ed8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9ce07ddc4e934cfe842aa901cee0ae85 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a63822cd965644c09b7a34ae88b0b326 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8ccc687edea548aa99d4507f4b586e78 (3780339 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dde2243dde5c418db8b9ee65b97dc60e (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\dde2243dde5c418db8b9ee65b97dc60e (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\46ebf16000554bd9bf96b5e82fd8931f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\68030ea977b34d8cb5a33ff74faed205 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\68030ea977b34d8cb5a33ff74faed205 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\370d50e2c8c74bab8a38860eea759029 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\370d50e2c8c74bab8a38860eea759029 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f0193c18f7bb4b7ba87d2f31593f1b96 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f0193c18f7bb4b7ba87d2f31593f1b96 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9f5df6b3f54b4a32b2487b8bd4eb450e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9f5df6b3f54b4a32b2487b8bd4eb450e (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\928e25f80dad4592b62a7c31f60c04cf (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\928e25f80dad4592b62a7c31f60c04cf (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\913b09ed6f1b410c98d93714b7f5ed30 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\913b09ed6f1b410c98d93714b7f5ed30 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0994baec725c47749b7f5a162c20d986 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0994baec725c47749b7f5a162c20d986 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5ff71f34c8d6497aa2b91fc4bcb7193f (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5ff71f34c8d6497aa2b91fc4bcb7193f (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c2a9983da5094fbcb290d39e66ba022e (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c2a9983da5094fbcb290d39e66ba022e (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4229d6510bc848f496231483e0c9fef6 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\10b93ce8f826469aa3a77fbf5e56a6c1 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4229d6510bc848f496231483e0c9fef6 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\10b93ce8f826469aa3a77fbf5e56a6c1 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8b1689960cf41578802c5d68a094a10 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a8b1689960cf41578802c5d68a094a10 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cd9394058ff547be80e7967aceb90261 (3780451 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\099cfddfe9ee429685703ce31f0877ac (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\0C67D3B8AC9E047D69CC3CAE69CA6400E0D848CDB557065B4BF8E52E81E6B5DF (154758469 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\099cfddfe9ee429685703ce31f0877ac (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\475995d5300e4bbdaa3ef8737dda7fb6 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cec7f715b2ba4ab1b60076c6666fe719 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\cec7f715b2ba4ab1b60076c6666fe719 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\288509736ff143d8ac500dcae8db94e1 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\288509736ff143d8ac500dcae8db94e1 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bc89c2df54a6400e9820dc64cb1f37ce (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bc89c2df54a6400e9820dc64cb1f37ce (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a6281a55f9354c4787ad2c0d2550fb58 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a6281a55f9354c4787ad2c0d2550fb58 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f10c1aa197d345c69df78f7fea2ba830 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f10c1aa197d345c69df78f7fea2ba830 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\90c1965db3e14097932d37c35a16ff93 (3919513 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\863cb81e0d95446ca8b3fbb24da8a734 (443715 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\863cb81e0d95446ca8b3fbb24da8a734 (154952733 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc94e20ce81d473ab54cbbb7c5cc3beb (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fc94e20ce81d473ab54cbbb7c5cc3beb (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0e7217e50be64864b0e0b5b8a98b3f52 (443716 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\40E343CA999D8F7E7C042099B0EF28EE206FE92F7BD1BDEFED15BAA8ADB245F8 (154952517 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0e7217e50be64864b0e0b5b8a98b3f52 (154952734 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3382caf565d4826bf0a217ffb0fd925 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\04080dc7723548fc8d3090a13fe216ec (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\71bb1ace40c841f0832417f13cf846be (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\590c7d43a30b49029d1d715a4b00147a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f68f716525384e71a3c00fdbde097424 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5d9bc93fbeef42cb9a31780384081c67 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\260131f8b4f04443b40824606c4b3498 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\238804616ce04bd286ff252bdb7ffde8 (3783113 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6378ecc245d149978d3f83f8714836e9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ba926f7789b74619b80e96ff934ca8f5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ba926f7789b74619b80e96ff934ca8f5 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4eb247b9e8e34e90bfa4d6b2c0c5a46d (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cd709414571647f8b7bd2b1f3367a924 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb61294afae240d0a3cade5ea8220955 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6419b651fbcf49d48699f044c1685575 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5aec25ebae694bb19c4ab21165dd83a7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\59431d06de244886857de18ec6c9445d (3783169 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38414bfe36c642f895dc52547da9f936 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\38414bfe36c642f895dc52547da9f936 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\55bca1f39d7447eb950a0bd7c8264978 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5828196139e440878a454125e9f65cdc (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5828196139e440878a454125e9f65cdc (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\461404f256c342729b408b548df2b4b5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\461404f256c342729b408b548df2b4b5 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\24cf11c2f0a14fcb8d6d8425579f1fad (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\24cf11c2f0a14fcb8d6d8425579f1fad (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4559fe572da640f99dee6ce2fffe454e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4559fe572da640f99dee6ce2fffe454e (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cbfba0caf42946ea9df8b15113a7be0e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\cbfba0caf42946ea9df8b15113a7be0e (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1165b23bcfad440ab392b493896f095c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1165b23bcfad440ab392b493896f095c (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f93f9b30c83744f5b88a935375bd9852 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f93f9b30c83744f5b88a935375bd9852 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ea9bfad1395449b9bd89d6fd1293ccc3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ea9bfad1395449b9bd89d6fd1293ccc3 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2090f48af2d440acb9f55d183962ebf0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2090f48af2d440acb9f55d183962ebf0 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4c23b18531b94628a6cc98b03fccfe26 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9707701d8a9a4f1aa320f18d31334208 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4c23b18531b94628a6cc98b03fccfe26 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9707701d8a9a4f1aa320f18d31334208 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\be9f750397ba48f293e49583fb6d4057 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\be9f750397ba48f293e49583fb6d4057 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1e25e188381e48368e4f0d00afa0295e (3783504 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a68b4ae1601d4a09ac07cb795da46954 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a68b4ae1601d4a09ac07cb795da46954 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ffc913be4cfb4595bc4fd3f6817565f3 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\677291fb86d04c9a910b631a765d16d8 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\677291fb86d04c9a910b631a765d16d8 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7b65a9660c7e437a9ed84e8da7d7434b (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7b65a9660c7e437a9ed84e8da7d7434b (154953245 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb87e59c481d49f3ab8c092a51553cdf (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fb87e59c481d49f3ab8c092a51553cdf (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a0d6541099744d17a40d1a51622031a9 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a0d6541099744d17a40d1a51622031a9 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f8da2a45d4f244d2b668a6e25b9243bc (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f8da2a45d4f244d2b668a6e25b9243bc (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a73e516fe013440f8ca81cea302a0337 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a73e516fe013440f8ca81cea302a0337 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1b9204d1af72413f8eaeda6070cc5aee (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1b9204d1af72413f8eaeda6070cc5aee (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\73de545c6e0240ffb0f1b1241226b00a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\73de545c6e0240ffb0f1b1241226b00a (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\aee781c23ad44537a451713da810aaf8 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\aee781c23ad44537a451713da810aaf8 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2c03f098a58c4bfda3e1eb9ce0ff38d3 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8bdabe51d2244229be1aa12c5d0f589 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2c03f098a58c4bfda3e1eb9ce0ff38d3 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a8bdabe51d2244229be1aa12c5d0f589 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6568351149014f669ccb656b8f59d2e2 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6568351149014f669ccb656b8f59d2e2 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0a83aedab9a3403c885ec08436370bbb (3924673 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ce4d0ade0fa4f129822c9e994ca7370 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0ce4d0ade0fa4f129822c9e994ca7370 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\72556a51660a41f5b2eb648c6ffa5ea1 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4eb31287903d42288b76536f266def5e (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4eb31287903d42288b76536f266def5e (154953245 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d2dc88b839c9479c9835b3f12f008ef1 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f5952578da334122b5fc2f8dd78b8c39 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8972690b01a74f748a7f72dc4e02648f (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3664697a149541ff8989649fb2b731ed (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc21764fe302429f837a39f00f1aea9b (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb99c6fa9d4a4bcb96937c4b251e1cdc (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\75c96b1307e849b684c39968d26f94eb (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7c11ccc502f442c88d4dc8eb9979d30 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a7c11ccc502f442c88d4dc8eb9979d30 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6691b1238c604636b85ee19f74288144 (3924505 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a268d0eb1e2490588a9410159ea3d9f (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8a268d0eb1e2490588a9410159ea3d9f (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\865e73ee7856422f82c7bd6ce7f5b577 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\88E99661B1C20DEE8B0ED84128C8FE332E8C61AE917A87E9DC4B5CFB23B26D30 (154953029 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\865e73ee7856422f82c7bd6ce7f5b577 (154953246 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9c78f5e58c8a4ad082a5e8d2e51bebe2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9c78f5e58c8a4ad082a5e8d2e51bebe2 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87c7d8ea50f0443c8ebfc74ca672dfd8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\20a5386001ab4b2bb80816aea6036eb8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ca5542a7a14546b680f054f29d3b8fb8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\023c7864c5ff47d99932ffb95817f32c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f7db51e6db484cd384635111cc68eb3e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e50b8d5aa9944cc686fdbf4f023636a3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\919d61b7009b46188b3be46d9d02d2de (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\70c0d16737174774a8842c090da84acb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\70c0d16737174774a8842c090da84acb (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\719fca67c997465785235d92a83e0ff2 (3783337 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7ef5676537943a5b6fabcba85754c68 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a7ef5676537943a5b6fabcba85754c68 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c68a201b3fae4a5c9f8a02e0acda717d (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c68a201b3fae4a5c9f8a02e0acda717d (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a13fcebc61a47acbc16006183cc3396 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3de1a3181034563af884b531ecc2e37 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fac253bf23404064b82f806e46e7cfcb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7ca3b686541845aeae2de9db03401835 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0dacd306d0aa4aa0811e3840ad06e71e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\970e498641a5496e94c6b3d455ea62cb (3783002 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b76741c95de04ecfbf04892efa391e13 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4a6caf1ad8ec46e6a9885818f7c1cc7c (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eda81b35890d4f6c85c4b5875ed58c93 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b753fba0302a42e5abcc4e69f7a14327 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4dc3c2fe49e94055a1847f3d61d2fcc1 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b5ba1be865974918b69da71b2328e494 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\88f3b36fa9e2465db7a05af98e32890d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\61b0f6faeaa64f6c9c7e28aa29f00195 (3782890 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9fee336ca69c4aa7ba708048f835044b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\872D75F26FB963F081A958BD108806EBE3F69939B6DA1030CF76F4C934E40C7C (154758469 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9fee336ca69c4aa7ba708048f835044b (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38b96a51330a45faa90b0795e35a1075 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\21ae58662057480ba356e3e72d0d4024 (3782890 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b1106ada01c440e4901846769629c38c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\efa61f09c44c46b094ced21393f0bb8f (3782890 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\94a4f459051a40dba70883e5cc6971c8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\13f7ac91496d46a596581b4430338d2f (3782889 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\43996a3308b84da39ff32407d37be831 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0dbf4c08c4f04c5b816266fbc3d729dc (3782890 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a03c99ab92b84256b156ee3ac1d739fb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6efe2d9238034e90bc8c5bd653de9fd6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6efe2d9238034e90bc8c5bd653de9fd6 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b59549056a0842c090d17fa065a9dad4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b59549056a0842c090d17fa065a9dad4 (154758685 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08dc2cff51bd4e30a8a8d5a618074e76 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\08dc2cff51bd4e30a8a8d5a618074e76 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eaacb7d563dd463a82c932f5e9a5d616 (3783113 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d63992ca75b24466800c60553dc84f74 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\C729E9BD96666351BB4AC324F6355EAECADEDF63C0AB5F7BBBC088CD9D8875DF (154758469 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d63992ca75b24466800c60553dc84f74 (154758686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ee2edb10d3e449d28827b850b4cd8d7c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d73d11d60ec24b37bd2a8b9a9e57589b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d73d11d60ec24b37bd2a8b9a9e57589b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\763c6e18bede4c9785af86b4f8b25a89 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\763c6e18bede4c9785af86b4f8b25a89 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6729b8317c784323a96fd52c61d828a5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6729b8317c784323a96fd52c61d828a5 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ffbb80ebe7c41d292c6fe96b4171d7b (3783002 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\beab96fb41294dd382d98cf504afb211 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\562E39D5EF6B5D2C87F8EB34639876273B336B060FA0641A3DF8A4D0C01E46EF (154762565 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\beab96fb41294dd382d98cf504afb211 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4905f82042454c2db8dbb37bbe38f376 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\481d0dc14b304fd191a624d7378a7ef9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\481d0dc14b304fd191a624d7378a7ef9 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\354de8973cbd4f1ba32377a7b9561ca3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\354de8973cbd4f1ba32377a7b9561ca3 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\673c41ecf1b742f5a8ef6a980223d3af (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\673c41ecf1b742f5a8ef6a980223d3af (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44b84f1d49d44d78bebbefa644465555 (3783002 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8d641410b30426e99b86472522baa6d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a8d641410b30426e99b86472522baa6d (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\77d495c185234c0ebd2abb8e45c85ba5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1a3daf3d73904e0da7bd9a553ed5ce21 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5eaeda7d01e440cbaf3498a8dabda86b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3b6fdd1ea8d471d9a9794ec889d09a0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b581aae852f84407a579ba0a7650e0e0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3e2c414bb57e4c22a63f69af9f7edc31 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\708c93e143ad4a078a4290b3662350b2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8615df82b66c44e79b9550b63c0315dd (3783154 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8799033465664477b74f8802f205453b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8799033465664477b74f8802f205453b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d7114e65e85b4a1ab380668e47608dd8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e1538540e0b641fb863c94e78681bc66 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\edc02d7948b5464f8afdd0f7b3c14b4f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\edc4dd5de65f4496aad56fe28b3a5f21 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\81a008377fc94309a7a377552ecbf906 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3ea35e30d577425097f5e502c7e2404d (3783154 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\43b941de8c534ef8b8b28821c2c60cea (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\43b941de8c534ef8b8b28821c2c60cea (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6ffd4dd3a68d4e31ac16eade17dd166c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08e6988fcff54ec2a32515de0022fd87 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\08e6988fcff54ec2a32515de0022fd87 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\02370425e6c44539abcff8ac4b6bf085 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\02370425e6c44539abcff8ac4b6bf085 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\acaaefc9f8224523925521f36a77e1b2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\acaaefc9f8224523925521f36a77e1b2 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8afd042102ae42dd9fca5456bd8c4f0d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8afd042102ae42dd9fca5456bd8c4f0d (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\db9cc8823446479283e4e997f989c78f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\db9cc8823446479283e4e997f989c78f (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\801bc0101a124b37b3070fb8e1879836 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\801bc0101a124b37b3070fb8e1879836 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b73800a03604d72af5222d120f7d62b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9b73800a03604d72af5222d120f7d62b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4b657d64a3164a4094aaa2fa05227711 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4b657d64a3164a4094aaa2fa05227711 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2985ea24d3a946f29a05e08cd9bb69a3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2985ea24d3a946f29a05e08cd9bb69a3 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\20f54b95aa214a779c9f35f01cb2ad86 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\27ed927588bf4904993a013c5ee9e1f8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\20f54b95aa214a779c9f35f01cb2ad86 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\27ed927588bf4904993a013c5ee9e1f8 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\09cf6a8d471a45589e69fdf31d88be2c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\09cf6a8d471a45589e69fdf31d88be2c (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1f807b3bee3d495c8648cd8544d5ac29 (3783322 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\60439b26283841f3a3149c4764faf734 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\60439b26283841f3a3149c4764faf734 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\22e631de8805472baea2e3ea60f8b650 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b0a91a339be347be842b14534818a024 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b0a91a339be347be842b14534818a024 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\233790d0e4fc4b22a730a465f87a0875 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\233790d0e4fc4b22a730a465f87a0875 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\945e094a7b2f4558bd385ba533517d37 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\945e094a7b2f4558bd385ba533517d37 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\770f408d470b48f381e786a5fb9a2f19 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\770f408d470b48f381e786a5fb9a2f19 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3613a87856f4b9e852d6d4b8defa9c4 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b3613a87856f4b9e852d6d4b8defa9c4 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\82e2b89c0eb84d38b24087d9bff35c8a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\82e2b89c0eb84d38b24087d9bff35c8a (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5c9e84917300408e82a342ba109787cd (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5c9e84917300408e82a342ba109787cd (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a8c6d9928e1e4252b7ae9d7e10a509a2 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a8c6d9928e1e4252b7ae9d7e10a509a2 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\45f5a67b64024d7db1d3498e92e2d40d (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4e3742e93854409080927f4bb53ec8bf (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\45f5a67b64024d7db1d3498e92e2d40d (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4e3742e93854409080927f4bb53ec8bf (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6b23dcf1c48445a5850fe91004c8c3f8 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6b23dcf1c48445a5850fe91004c8c3f8 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c3b1963ffb00423780da07d183654afe (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c3b1963ffb00423780da07d183654afe (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\305ea5c941cd4e5d878990989de0d989 (3924616 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\884a22c495c645b2b28372ddcbca5616 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\884a22c495c645b2b28372ddcbca5616 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\be4baad887664f38bd006b00d078320d (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c8fb2c2a09624c44a1f53cabd206e571 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c8fb2c2a09624c44a1f53cabd206e571 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\af8beda2574143f78072206bdc7674a2 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8fc7a036a5624cd1a79159ac26da9603 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c77f65beee7744a1b6a817a3815ca323 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6e79b7a74d694d368a9ffd4f15357061 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\46ef6e8b68734f0091ce5c8976eb1d71 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\55231ce9fc2d49cea8fbb49eff228e31 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ab4bbbb141ae432eb03eedfacf6d1b89 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\52514a4a3d3b4b068c3a5519ef5e4e18 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\52514a4a3d3b4b068c3a5519ef5e4e18 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ce11e75933704b309d6cab1f9ceac778 (3924616 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7dfd31ceae6949179e006bf131ac06de (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7dfd31ceae6949179e006bf131ac06de (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\761527d9ce37458f9b729616abb901dd (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\761527d9ce37458f9b729616abb901dd (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4fbb8862b6704ad89bb97117e9f0737c (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4fbb8862b6704ad89bb97117e9f0737c (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc2ea6eaa350406ea15280b66026a12d (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38449ab4093344b6a8c1b95168853a98 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a577c0bbe6c04061b6c692b4dace2bb3 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08f542e97cf74606a1a2f16c8db43c8b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e094207a44a0426cab9d3b7528fe2b39 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0bd6de3c7e6b448d9e0d944478f3a377 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\342b0bc4b95d468f8e32f1ed02e778aa (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\93cf45d4d7464681985920eef7617246 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\93cf45d4d7464681985920eef7617246 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5f8bb7971ef24f138402c9d6a9505a2c (3783264 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\202b86e7d2a6448d9e833b1b4974fc67 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\202b86e7d2a6448d9e833b1b4974fc67 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2050ec0e441f4f3f8f3eff1d73e8a98e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2050ec0e441f4f3f8f3eff1d73e8a98e (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\74f1923656c74cfaac0c8a8d0f3bacc5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1dfef5b8c42049deb76bcc253b97f789 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\16226815d0f948d6ad091caee3383677 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df37978687fa4bfa9e4ed9e3c7155912 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fdf6a5d8800e4dd2b3893b6c54183a92 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5d9452a0d92b4519bac480098052ec51 (3783095 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7c13df0f57ad4e478a4268d062fb6305 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a5e111be463e4039a9e872cba91fec36 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a10d16a4196f4921b9e03434733df160 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a10d16a4196f4921b9e03434733df160 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87f7556e338d403a8e7d93dd6b203d15 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0721d8b290f44c8db8b520ad448caed5 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bc7e471a38ce4ccfa668549662e66f94 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\052ff10c36884ebba87c53d0ec0cf175 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\57e4affc3afc40f4835b4210109ae78c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\167196975d5c469d961615a46faf4ff4 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\31f5ef350911494a94fffac1d8256acb (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c75ee76ca54d438eaae98c04b4ecfeeb (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3d4487fc06db4f65962a4536a59382f5 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5f3ecbeaaa704486b6ae28559536246f (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8a23c2e0862c43babf8d373c52d4695a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8d73f15bce55408ebb394c644aa360a6 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\48d2ece376c248a8ad621255b69c0270 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bf436658973743198339397b2a331482 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b742c632745f49188bbe0ed4ed7bf8d9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c9bcda3ed7584855b918ef42b1b94708 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b742c632745f49188bbe0ed4ed7bf8d9 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c9bcda3ed7584855b918ef42b1b94708 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f994300abbdd449f87c6f19834bf25bd (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f994300abbdd449f87c6f19834bf25bd (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dfe4aa585139445e9bef889bf0b4f26b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\dfe4aa585139445e9bef889bf0b4f26b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c43dd1a179b44ebabe8724ced43d4990 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c43dd1a179b44ebabe8724ced43d4990 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f7250ea27ad3454081425770707db464 (3783042 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\35c98aa0988a455cad9aeebf59dd8c9b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\35c98aa0988a455cad9aeebf59dd8c9b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5a7ee11c27cd4d8e9610d3f26beadc8e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9f7bf5f53ff14abbb1d1bf563672189a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0a47b844d7164434a1d6d8bb17e4b297 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0078797f83904761bec42552b20c3275 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c9335a20f4924e22b104c8a397488e73 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0e4f5c058c7d4e529843fee5075fb061 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9f859f11d20e46baad815b5f57ab205e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\08596b260caf45acb73e079198acddb9 (3783097 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e9178c9fa6bd4008b652f5226cce8bf5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e9178c9fa6bd4008b652f5226cce8bf5 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\78b123429e5846f5819f4d38ed37f78c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6be8c828b76749cfa54b4c2a7cefa1f4 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a57dba5463f84385b09c36b285af8433 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\126024977b704ac4a23b6124b1bb5f6c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4edd820eee1848b4a72cefb504cef84a (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\63c110805e624cbaa9e9cf3347107fa1 (3783152 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5838484437234ecb9b302bea44908fad (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5838484437234ecb9b302bea44908fad (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ffd85b840b834933bea939058e6876b0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8b50241301ba4d6fb011316eba4e5d2e (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d800bba570da40e285bba6f86b3ef96b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e197bfc29a7145c4af5c53f2f0b9b38a (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\113e1c024cd04a74a284ca383fcc70a5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8b50241301ba4d6fb011316eba4e5d2e (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e197bfc29a7145c4af5c53f2f0b9b38a (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\113e1c024cd04a74a284ca383fcc70a5 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ffd85b840b834933bea939058e6876b0 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d800bba570da40e285bba6f86b3ef96b (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6298668a64b44306ab5de9645ed3a065 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6298668a64b44306ab5de9645ed3a065 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\802349257e1a426c8a39651796e77979 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\802349257e1a426c8a39651796e77979 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\46cc44e749de418dbf7d7e17d3c04631 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\46cc44e749de418dbf7d7e17d3c04631 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\416f8b5340e645a2aefa6d4eb24b7aac (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\416f8b5340e645a2aefa6d4eb24b7aac (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d4943356ad22471bb78b0084f5fd8bc7 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d4943356ad22471bb78b0084f5fd8bc7 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9a06d4a12b1549968bff6af270b33c12 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9a06d4a12b1549968bff6af270b33c12 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f7301f6f0754433a957dec3e6e1984b6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f7301f6f0754433a957dec3e6e1984b6 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7e5a8c08799643059f5fae92624301be (3783319 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\52a311f159774e16a815a3066d36c7f6 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\52a311f159774e16a815a3066d36c7f6 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3b21874667ac4c0da43e9501c0d6a5ff (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\02684fe1fde641f784b9497d537d5049 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\284ee6f31bff4655a5e8e21f5b1f7c4a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\616ec4893844426a9b5c16e41518738a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f41ecc91d010402ea60e20c760bc14e7 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3b21874667ac4c0da43e9501c0d6a5ff (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\02684fe1fde641f784b9497d537d5049 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\616ec4893844426a9b5c16e41518738a (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\284ee6f31bff4655a5e8e21f5b1f7c4a (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f41ecc91d010402ea60e20c760bc14e7 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8155689ef8574367afb56671d6a43050 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8155689ef8574367afb56671d6a43050 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\114fe4b4cc30497fb299e8c941d45f6d (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\114fe4b4cc30497fb299e8c941d45f6d (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38cb7280a3ce4e4b8710f8ee3563914f (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\38cb7280a3ce4e4b8710f8ee3563914f (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fc91e807c4cc46539d0329b1f8a41624 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fc91e807c4cc46539d0329b1f8a41624 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\aab9ed42bac347fdb0f1f1b0065744ce (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\aab9ed42bac347fdb0f1f1b0065744ce (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ee040f34d548462fbdaf285a973cd2c5 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\698e55b676364694b31af29f72044333 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ee040f34d548462fbdaf285a973cd2c5 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\698e55b676364694b31af29f72044333 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\84bedacc4b5b4f9bb8be93b0ecac3fdf (3924616 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f5bd347d539d46fda5a66c39df98f739 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\f5bd347d539d46fda5a66c39df98f739 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b25623da464f46c79af1b2b952e31403 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\b25623da464f46c79af1b2b952e31403 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\de60f45e5b1440e298cc5e259f559635 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\809b1c7a3a454763ab56287ab0756d11 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7febb5c82ad9420296cd65acce20d5f5 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\06b3c4cb5a404354b0d62d41f0fafa9b (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\31001f4296c54f67bf2a03080b9d9278 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\09faf3ffbd2c47d5a2fde068ed59fe22 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e78f0e3022cb4025a3006e928825f81b (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4110fff16a07407490743d980e6e206e (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\4110fff16a07407490743d980e6e206e (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7fb87af25d5f40fd94b28fe16db3eb84 (3924505 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8fd7d1482de446bba6c1c59d009c59fc (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8fd7d1482de446bba6c1c59d009c59fc (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\98aeac26d6c146e8aac2acc51f1112f5 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\98aeac26d6c146e8aac2acc51f1112f5 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1505fbd9791c4089b02232dbd40a6220 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1505fbd9791c4089b02232dbd40a6220 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\839830eb1f39476290096a149cf3e20a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\37d44976f0bb4cd4943ddf71aa398e7c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7c1d78455b844ee3acd55ebf5f3c24af (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4abe5fbca75b4c71bef0b0aa6706704b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\315747adbc3e4b169cce982288bd7ad7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cfca3674d77f44108e7131f781fb620b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\230ed2304b034e4b9250087ebf3f4f74 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3b6076c21c254be28f1f7377e63732fa (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3b6076c21c254be28f1f7377e63732fa (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e731961e575341d5bb3643d7dcb32c23 (3783264 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d02893b3be184a09b56db04b4906d122 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d02893b3be184a09b56db04b4906d122 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ab4f64233d884cd8ac45a8b379ba3617 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ab4f64233d884cd8ac45a8b379ba3617 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cbe047dc4113466dba42d41c532fa984 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44092cd99dd54ba6b2ef9c1e6f14ed36 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8746495c2c524538909c030aabec0792 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6a9a0fd925f149298fee047a10c3447b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3406c36cc6bb4e2a94492467529f2a6a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0ee4f109a90f4a4b80027e79ccda46b7 (3783098 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\74437108795149c5bc7d795e6039fc3f (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bc2f425253f94dba99089ca572af2932 (3924113 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\06522a9f7fa54034b87ffd4eb5458aef (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d896d40f8b384252873c87de009a1686 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d9edad4ec89348a2be0bfd8a6a62da94 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c34bf6e9e18d46818cb19b8a530d054b (3924113 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f182793516c24c748985b8fba507aab9 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\543a753181284c2dbc2332f985f09dfc (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\543a753181284c2dbc2332f985f09dfc (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8133d96a05a54fab887ea940c3ce9e10 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3ae074cbf164994baecdd607a439724 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8dc873843ab7411b85bce8628237e9fe (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fd79021fae044707b82d258ec68b161c (3782873 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a6ee75c0de0d4cd3a414057c8f5bc19c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9d64d9da883e4ff5b505ebad8b4dcd91 (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\333c7e7a4a1346279935678d5f5889de (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1d00d2c6ba144b479bd4a8c7548e000e (3782874 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\45e41063cfc94bf9bb0f33cda6201815 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\45e41063cfc94bf9bb0f33cda6201815 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c786741e9cda4014a929d85c994d7741 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c786741e9cda4014a929d85c994d7741 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4e53afe39b2144fea40dfd789da5ef09 (3924170 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a9e5304203cf482b913ccc0e9aff5c98 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a9e5304203cf482b913ccc0e9aff5c98 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9f5c8c5482a047958b143c957bda9495 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b7ebbc8e09154bf28b381e1ccda2aa2a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\02898c83c2a04af79ac759daf62c989f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cc15c96439ed4da19b7bfa3c4db29dda (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\86b0be8aaeda40bcb0afeb8be5fb2e2e (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d0facdbb75f5473a8a2dd86407c211d5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e1f7ab30b0c649288b5af3b7c3b73c24 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\99010f786c89483aab82b9bcc8d20798 (3783104 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\62f783f9ec114da3aaa5636985b541fe (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\62f783f9ec114da3aaa5636985b541fe (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\50da61d65fdf446199871e335c881144 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\26f1c15516e94222af41151983288e20 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\df97ee68b4984566bfa039f42ac4e4af (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3516b184ddc4af39ed9e5ef52b50c39 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\019669aa8d2d48039c9ea2e0659d2667 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\57555919b8d84140873956eb48af3176 (3783105 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c113fb8456ae4c3089ce78b0cce11251 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c113fb8456ae4c3089ce78b0cce11251 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87857263adf947d9a4d618a70cf05804 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bc204e3b129b4c63a4c6896fbb694368 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\87857263adf947d9a4d618a70cf05804 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\60512297a3aa4e1d9b59bda673dbd2e5 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\52464aaee1144e0288b7278254d3d0f5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\75a162f8a5684a17800d8dc9191f4ea5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\bc204e3b129b4c63a4c6896fbb694368 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\60512297a3aa4e1d9b59bda673dbd2e5 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\52464aaee1144e0288b7278254d3d0f5 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\75a162f8a5684a17800d8dc9191f4ea5 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\36249152b2b74f75a89a2f541834f614 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\36249152b2b74f75a89a2f541834f614 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c349cfcc8b1f4e6895b9cba7de7a0fa8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\c349cfcc8b1f4e6895b9cba7de7a0fa8 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\522f84b269894caeb4498de59ed9bc2a (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\522f84b269894caeb4498de59ed9bc2a (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\09c90d95fbbf414698d05903ed76c730 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\09c90d95fbbf414698d05903ed76c730 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\aafce47ac352416499e529a6745f44b4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\aafce47ac352416499e529a6745f44b4 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\11fecd6b24ab49c99b26b18302f71e9f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\11fecd6b24ab49c99b26b18302f71e9f (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7799c3c9a26949888e3fd0f66ff06101 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7799c3c9a26949888e3fd0f66ff06101 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\86547021360f467884a379413b45ed25 (3783327 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2338ab73a7414e2d9645dda4b596bb2e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2338ab73a7414e2d9645dda4b596bb2e (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e5c71869edde4ebe8dda62b5069f28f5 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e95e03b60f2e415cb5ca2e315bb7029a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a3b8bcdc6c7040d3ae386685d0eb78ae (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\96d7fad98d1d43c580cda165ec0ffa55 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\81154bb3e9ea4d41b8501af57e161d1a (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e5c71869edde4ebe8dda62b5069f28f5 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a3b8bcdc6c7040d3ae386685d0eb78ae (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\96d7fad98d1d43c580cda165ec0ffa55 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e95e03b60f2e415cb5ca2e315bb7029a (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\81154bb3e9ea4d41b8501af57e161d1a (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5fd3c0764a6244c883ffc58b85c4e2ba (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5fd3c0764a6244c883ffc58b85c4e2ba (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fcb202b0435b4a86bfbdc968c704c526 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fcb202b0435b4a86bfbdc968c704c526 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2a12744940da4bc4810a94087feb57ee (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2a12744940da4bc4810a94087feb57ee (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a34faca91d1440fe8ab8f99cd6a7a221 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a34faca91d1440fe8ab8f99cd6a7a221 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\15948ea0638042a79ddf81692b826894 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\15948ea0638042a79ddf81692b826894 (154957342 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eb65c5dba65848cb8bd1279ef4c887b4 (3924502 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ffc018dfc5ba4a81bb844e66efe8caa8 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\82b0c30cef62419c9d4b1213d90ee24f (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\ffc018dfc5ba4a81bb844e66efe8caa8 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d5ec45341af04d3c9f00e6e1c2e4a2e0 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\d5ec45341af04d3c9f00e6e1c2e4a2e0 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\82b0c30cef62419c9d4b1213d90ee24f (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7cae8b2781264a8489274c8ca2b0e9e9 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7cae8b2781264a8489274c8ca2b0e9e9 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\4b3cf27b93c6f965d160b59349a57228b31c44de3a7021e6f44b8b288ce75a14 (6971255 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\eba587628517a5b278b5f4490362366b235b3bbbeae14a3da2fbd059da575b23 (7055190 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\b3f45adb7d38f2cd4f6853e9efa51beb64e73eb6cbcab3d5291805a361e730b5 (1680741 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\da78dd3b4b7b7b315594a5aa0bafea784a87e969463f15cdb1518e39e67b95ac (5491557 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\5481f28f4e32e857b2fbd46226cf6ccf1928451d87a2d70d5b87d8599b3f7820 (12103519 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\15759ae75491c37d06eea20880a810f0b0ab9d8629765c52b0e939df35381200 (4477498 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\4d4cedba15df66e0c6463f6c6eae4fcfbb58317e0ee4d8638114d7d2a94ac80d (220526 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9b9be53007a149df98f718e69b5dd327 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a5022359d6ff440aa6088e1dbb0251cd (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ce2f897d2e8c4245990872d816381512 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e23387518e9e46bf875cc031129f5fad (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3f84f3355e884fd8b924f747181f52ea (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\56abcdd604d64f36be53862b830bf5f1 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1cf9c230511b40e8a5ab4fd6c0cc30ec (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7470934a2ede42779ff2bca8fb3218a9 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7470934a2ede42779ff2bca8fb3218a9 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b71a9af4d41a4fab85e392cc81851800 (3924446 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6b6f3a56362c45c6a20f8df4605c946f (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6b6f3a56362c45c6a20f8df4605c946f (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8c2896ef0f20423db7ce39cfc9f63ac9 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\AF513316737A81F678C9068F4712E8D90840F5F8226FBB66C151842EAC5ACADA (154957125 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8c2896ef0f20423db7ce39cfc9f63ac9 (154957341 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\616ae3fb5f7b482c9fe15c1427270d1b (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\616ae3fb5f7b482c9fe15c1427270d1b (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx\windows-x64\36de22096ac1663b6588b2673000191dcdfc8038c6b7f36128ced275a6d470a8 (1168253 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\sdl3\windows-x64\8c6121aaec46bd6887a1721faddc72691349563d9021d12e772574d2371b91ef (2798940 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\miniaudio\windows-x64\9e00e03df5a5a8479d0f2fa73f3ce4abbbedaed76c255eefc7d3a41109c25d9d (770923 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\text\windows-x64\b6b60ff72c4577a55112e82799c81dd65bdf92b2454d4a558817fea53269689d (1571179 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\ui\windows-x64\dfc1093d333d878ade0e29ac1d6f24edefbdcd0596968b69794acb9295ac989f (3023717 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimgui\windows-x64\fa63bcc03ffb37be86b704034acc7dd87af365eb347957233350200a92a1edda (2886412 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\cimguizmo\windows-x64\cded6a7e28837856efc3e2784f00e7dcffbe48ea0e34c9d504623a95a8d031d2 (93044 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b8f09689e4e94114912f16792cbc2691 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\36613c109b434f93b7f00910a0203991 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c615000c88364052a1977989e0dd853d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\da15fe3591214d428c1a1952a4eed278 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3558b461177a40c4a2dbe1e293c5d268 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2c8bb1cb5c1049a39e5735c1154c7c02 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\82ca6b8594a94a4db75c794abd0b5552 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1e17a43e0f8b4ae6bebcce55ed8c8051 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1e17a43e0f8b4ae6bebcce55ed8c8051 (154762782 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6cc8aad4ca244a6da088676162148f55 (3786056 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\66343a040e3a497997cf64edf4aeb6d4 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\66343a040e3a497997cf64edf4aeb6d4 (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\55959842d480424db0ac8a92372d6acf (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\F76C315FA1DEF1719DE95D2887ACDA03C02BFAE95213295997C7F735FF69CCEE (154762565 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\55959842d480424db0ac8a92372d6acf (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cafef9c177bc4541b5c42e645b2b2c83 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b302d789196a41a68817b896ed282250 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\802116ab80cf444d943e34aec6725d71 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1c44f3d3ac4947c8b35cbf18149a4d1e (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f9c03eb34ec347d6bf7ed3ed171f4d0b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a1c9f7f04dd84401b5e5c4fc2845e96c (3783104 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a9cc1559ce954a2f86f62aea5807c447 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dd7d55d8832649fb88b475ea8b08e3bc (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3c7a56128c9c4349915ffd897d8d63b6 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\d02c72f1a1d942f1953b9ea2285e0168 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87db6ab3d2d9408da002794f5cb27b43 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bd482a3d533343f09ca9b3e771a82de5 (3924114 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0bdd73f849774beba3ae85d56a81df56 (3782881 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\91480d608b2e4f4eb7d3044bf6fd087f (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\D26F31B9D2D534C8E0023B2A0F1F1F9A6A77120FA8661F99EF1D5C6808A9B9B9 (154762565 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\91480d608b2e4f4eb7d3044bf6fd087f (154762781 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\917e865822674579be1beb000c23d6c8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3ae7faa978844f0eb77d206ce4fe8de7 (3782881 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\4454dac1ff0d4f07860f87d6de5962b0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b466bd166fee448882ab826a37b3ff3a (3782882 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\646eef4a8f2340b68058e6c3689f47e4 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0f8833f9197f49ccb4480602b34f0fc6 (3782882 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9e862618a0604cacbd5e5286ab835db7 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\feaef07db2a342708f8b232eb3f66736 (3782881 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\native\bgfx-tools\windows-x64\e51fa98332e704c4451ad6cb2b1f3fd51c79149ee494bde1389b4d56044e78d0 (16489108 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\168fe85911b44a6cae9da7c10dc783d8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e2aa2680cfde4da1b8530b8a72a28c3a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\48dd29df07534d08b13f8dd51bc70961 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2130d663beb34d628524ca050c9789a2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\ab6c3bbc84754e87802229b839b0ce4c (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c5c581caaf1b4bb7bc5e44f6c3439ee5 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\95d7c3e4d3a54f02a6cddf305254a8c8 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c8a05c4c4a9e4fd0832dc75b699af2b4 (3785319 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9fd97702c7f045efac2e40066e26013c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\da96b8321a474a6fb6689c05c0ede933 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\da96b8321a474a6fb6689c05c0ede933 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1266bc7647664badb32f1932d5420965 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a7ce1fb43bbf474cb6bbdfb85b7cd5be (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\40606dea4ab24ad59634522ec743c442 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0f5d3ebd67184067ae913ecca36fe054 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b2dbd908f8f54985a29a483ec6bbf249 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6f9adc0665dc4966b977ae28590a1bff (3785376 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3c75f1eba3a4428c8e0fa3211c700e45 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3c75f1eba3a4428c8e0fa3211c700e45 (154762817 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\73bf4331cf6142bb82e8f99b067a7ae0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\67a98e211098456b97e1d21f6b766344 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\67a98e211098456b97e1d21f6b766344 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\405e2e2867134e20b00b539045d45938 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\405e2e2867134e20b00b539045d45938 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2f175885b7be4479a95f1802f47390d7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\2f175885b7be4479a95f1802f47390d7 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1c977b980ba949eb86a9d280d0c7c06a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\1c977b980ba949eb86a9d280d0c7c06a (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5ca41b2d7766450ba1d98b28efa6d34b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5ca41b2d7766450ba1d98b28efa6d34b (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5b54f8afed3b4f619e2ba64eaec1f79a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\5b54f8afed3b4f619e2ba64eaec1f79a (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\34e9f030a5dd4535a4eb8bf17602e0e0 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\34e9f030a5dd4535a4eb8bf17602e0e0 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\eda0381caec044f69a7f4e177a768799 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\eda0381caec044f69a7f4e177a768799 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3730f55546a54a0e82e7cdede6fd1b96 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3730f55546a54a0e82e7cdede6fd1b96 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\87d71343d9204c3a9529be9d3f0f53b8 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\87d71343d9204c3a9529be9d3f0f53b8 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\622778abf21648b98251d8bcf0390d35 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\622778abf21648b98251d8bcf0390d35 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Bgfx\build\Inno.Build.Toolchains.Bgfx\obj\native\windows-x64\a02dc5fdf63941da17414c5d51bf9787cc0ba8400375325f2ca6a36bb6596eb0 (255892827 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Sdl3\build\Inno.Build.Toolchains.Sdl3\obj\native\windows-x64\3a0767a67641ebfb085d1c14e66c2bb3282530c99db9be3594c2780d0b3f2ad1 (510705814 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\MiniAudio\build\Inno.Build.Toolchains.MiniAudio\obj\native\windows-x64\7e97e6dbae4d68b9a25d525caed78701403d6473b5d31694d3fcec839da47059 (37974229 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Text\build\Inno.Build.Toolchains.Text\obj\native\windows-x64\c8aeddca968d4aa324ebac00874623e225b02bb10adbbb09a3144f99e94bbccc (131344109 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\RmlUi\build\Inno.Build.Toolchains.UI\obj\native\windows-x64\f98c881adbdbde2c5e57a1c8a6346b0bb43ddcf7b4473f0336106e8f04ee2836 (193288531 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGui\obj\native\windows-x64\979887e97b3be2ba617ade5bf8ade1153685879f01bea357427a48576469e03f (104737216 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\1031543b5987484e8857d9174cbcf927 (3785545 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\3f569456f61966518c238cf42755077a702195d4957aa96e47ecab12c27c242e (96368686 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6bd652bcf32f43ee96b7c730f76e6bb1 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\79ce7539f212427f863ce748d9b8b2f7 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6bd652bcf32f43ee96b7c730f76e6bb1 (154762817 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\79ce7539f212427f863ce748d9b8b2f7 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\434a705c5a5045bbaf2b818409cd9884 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fb6701a219e54887a564ff219faee21b (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\fb6701a219e54887a564ff219faee21b (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\062fd76ee45e44a28238246ebac55ff4 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\062fd76ee45e44a28238246ebac55ff4 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6c585ee34f4949b99d8751041cd32f38 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6c585ee34f4949b99d8751041cd32f38 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0481a0f80b734c2d87f2878dafcf76f7 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0481a0f80b734c2d87f2878dafcf76f7 (154957377 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\18d766b292c7416c9a2cc1255f1483ae (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\18d766b292c7416c9a2cc1255f1483ae (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\46a037c2e74c4b7bb2fedbf123ccd5b2 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\46a037c2e74c4b7bb2fedbf123ccd5b2 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\846d9dc7e04749b9ac9c2a2ca7ff0a58 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\846d9dc7e04749b9ac9c2a2ca7ff0a58 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e6c81a3f6166483b9a725f9ce465467e (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e6c81a3f6166483b9a725f9ce465467e (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\893f0a677b424fccabdd4ca38a5f29bf (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\893f0a677b424fccabdd4ca38a5f29bf (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44577beae0fb48a3a3d35033636e2921 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3866523d45f04a9fa7d1d9af3d043e7a (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\44577beae0fb48a3a3d35033636e2921 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3866523d45f04a9fa7d1d9af3d043e7a (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a25476976ccf400e80eef90bd8625cf9 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a25476976ccf400e80eef90bd8625cf9 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Bgfx\build\Inno.Build.Toolchains.Bgfx\obj\native\windows-x64\b6568d56ac851b2001be37799011d52462a3e33d9b777ba166aac03b20eb3935 (288989000 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Sdl3\build\Inno.Build.Toolchains.Sdl3\obj\native\windows-x64\44cb40af0c0b6e7cbbccbac3f3b6164b97a07dc92267970339af24b95df5f3cf (553166896 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\MiniAudio\build\Inno.Build.Toolchains.MiniAudio\obj\native\windows-x64\41ff3415ee7f4c5bf137123b1d1d34d035762aef3b63830356253fb3d74a7a39 (46955443 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Text\build\Inno.Build.Toolchains.Text\obj\native\windows-x64\aec99ea82e7d7762a3423cbf597563bdffd082754b80a96b4bc9119f7eb7a131 (241486975 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\RmlUi\build\Inno.Build.Toolchains.UI\obj\native\windows-x64\2fd77c906fee89c842c9abe4807230d9ab7fe84203d9a0f5c45745d5732c11e9 (578292419 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\Bgfx\build\Inno.Build.Toolchains.Bgfx\obj\native\windows-x64\2194a838ed22c42aba2fe154c7fde77628e458881a32eb2366bb6c6c6d63d875 (1424914392 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGui\obj\native\windows-x64\326a94fd0d2bf4fd7863948bb398d70ed25cbf68591ee252a07cb2c723bf11c2 (124324065 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\379aaa27a5004f9dbeb19a859d2bc950 (3926837 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8bc3102913144374aee5291cdc8054ad (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\backends\ImGui\build\Inno.Build.Toolchains.ImGuizmo\obj\native\windows-x64\3dec7d7cf4c086e2c5e10516de1c7d1db1f5e3122f282c8b83bf44e426b919bf (99813314 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\8bc3102913144374aee5291cdc8054ad (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7c8ac67ec96b48599902e0bda08bd58d (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\454a2bcf3cba4b7389cc16f4dc345868 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\454a2bcf3cba4b7389cc16f4dc345868 (154957377 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fa0c5ec31fae46a189997ae84d598813 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c944c7886a3d431dab13a15602186990 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\c1e7dac37ebf46b583b2feadd2b28be4 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7afd4c671be24b54b9b86a2f6f612f5e (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f85b449b96614491bde70da2d1eae164 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\38a39691998a4b2384b9cb1568cd2feb (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7fd7476cacf14c2e98831b9ee48c0740 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\7fd7476cacf14c2e98831b9ee48c0740 (154957377 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\dfa07c0ddd624759b3bd038503bed49f (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\85a3c1166bcc423bb0858ad5ad7e352e (3926836 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e002241944e441108cd2d721ac8ef17c (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\e002241944e441108cd2d721ac8ef17c (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\aea4f11137294278b8e0b0c4c87caf86 (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\077D8E110581CE8624B5B362B90C671FFC55A0709EE72383F204A4F9F125CB16 (154957161 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\aea4f11137294278b8e0b0c4c87caf86 (154957378 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a754408586aa477d84dd29f4cd4e2a50 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\a754408586aa477d84dd29f4cd4e2a50 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\410edc183e51490bbe5623f9de2365b1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\f72d43f6264344dfa1b5fbc80218be8f (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\551ce3bf802e4e8ca25e91f84622e52b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7886be101780412a85eb4fc767d6870b (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6058df94deb84c05805eaa7a59bd7e51 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\116a0bac4a054ff0ab1feedf8cb8a7e2 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8d91e135efc24c25bd3545985754605c (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9e7cd002f9ec400bb75feab13f673040 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\9e7cd002f9ec400bb75feab13f673040 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\8e6e448a16c44bf5a20dd867bac9c34b (3785601 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0839265765304e818d168922cc38fbe9 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\0839265765304e818d168922cc38fbe9 (154762817 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\3ad013d7b358466ca88a57b4b9723622 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\3ad013d7b358466ca88a57b4b9723622 (154762818 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\fa8c35c146dc461dbb6c73964bab8ec9 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b8838355e2444f54aa1a502110582ab1 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\a6c387d710ae42de99abad97f60ac01d (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\44b043f9c568469fa11aa41926223839 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\562b820ef5044204889e8e8ac017f160 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\564e5771f2e547fb87ba77f987743aef (3785266 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\170679ad452b4beab5313f4274845039 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\361dc31c3f614ae1a7f1b36d7c861030 (3785097 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\workspaces\shared (772497669 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6dcbf5e1aa074606b490e89fbd6a72de (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\hosts\A6865407FDBCB553B6C8A918767C7CB8C00FFCCD3BA6DF768E6B54C6EDD6C136 (154762601 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\loads\6dcbf5e1aa074606b490e89fbd6a72de (154762817 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\833f0420a1514146843fbdd1ffa70c03 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9aa9ba0f028b43518b499dc50bf98450 (3785098 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\874057ff20fe4ccdb5b5b888bbd9a21a (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\cd7d613465464380a42f74a5cb910ca8 (3785098 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\0b89fe2bbe2544c79dc2e5a1a0788b44 (419140 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\e64b3d6346724f95ae6077ac4ef180e9 (3785098 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\bbc467cf81f146cc82806e5d14651558 (419139 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b3753b9edb3a4af9b7b7e1a1c34d4d0d (3785097 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\7d5c1e74717d41dabd4b5525716dd3af (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\5cc7e4455b81477daf456ec9a99e63f6 (3926334 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\b0807f064e9a4836844ae7d3d1343eb1 (444227 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\9e9a5aa180e043ea8b97767910ca4c0e (3926333 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\6400dd2ad9024ec2a048ae58993a2d2c (444228 bytes) +Would retire C:\Dev\GameEngineDev\InnoEngine\artifacts\build-tools\tasks\publishers\2eaa740b72714a928ec2b0628b85259c (3926334 bytes) +Cache: 90213428162 bytes; protected 174090357; reclaimed 0; skipped 1808. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance.log b/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance.log new file mode 100644 index 000000000..9157995f0 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/cache-maintenance.log @@ -0,0 +1 @@ +Cache: 90213428162 bytes; protected 174090357; reclaimed 90039337805; skipped 0. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/cold-warm-publications.json b/records/architecture/acceptance/editor-ui-2026-10-30/cold-warm-publications.json new file mode 100644 index 000000000..53b40734c --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/cold-warm-publications.json @@ -0,0 +1,5227 @@ +{ + "sourceRevision": "eed5fd9bdb13d5f29351aff2b8c52eed58138266", + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "status": "Passed", + "records": [ + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289225727293567, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289225727932704, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289225726568095, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289225726040772, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289225731541472, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289225728368977, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289225721192025, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289225729006257, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289225729451116, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289225724221419, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289225724531497, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289225725010109, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289225729879862, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289225730210190, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289225730607232, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289225731018221, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289225723575427, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289225722855496, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289225722308467, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "cold", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289225863629786, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289225863629786, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289225863629786, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289225863629786, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289225863385147, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289225852941276, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289225852895802, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289225852849337, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289225852889523, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289225851832534, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289225864335168, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289225863629786, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289225863629786, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289225863629786, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289225863385147, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289225852895802, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289225852849337, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289225852889523, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289225851832534, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 65351.6874, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289225727293567, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289225727932704, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289225726568095, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289225726040772, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289225731541472, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289225728368977, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289225721192025, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289225729006257, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289225729451116, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289225724221419, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289225724531497, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289225725010109, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289225729879862, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289225730210190, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289225730607232, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289225731018221, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289225723575427, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289225722855496, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289225722308467, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 1, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289225727293567, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289225727932704, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289225726568095, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289225726040772, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289225731541472, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289225728368977, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289225721192025, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289225729006257, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289225729451116, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289225724221419, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289225724531497, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289225725010109, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289225729879862, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289225730210190, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289225730607232, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289225731018221, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289225723575427, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289225722855496, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289225722308467, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "warm", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289225863629786, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289225863629786, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289225863629786, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289225863629786, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289225863385147, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289225852941276, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289225852895802, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289225852849337, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289225852889523, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289225851832534, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289225864335168, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289225863629786, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289225863629786, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289225863629786, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289225863385147, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289225852895802, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289225852849337, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289225852889523, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289225851832534, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 44981.4127, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289225727293567, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289225727932704, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289225726568095, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289225726040772, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289225731541472, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289225728368977, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289225721192025, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289225729006257, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289225729451116, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289225724221419, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289225724531497, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289225725010109, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289225729879862, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289225730210190, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289225730607232, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289225731018221, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289225723575427, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289225722855496, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289225722308467, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 1, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289226641282543, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289226641697016, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289226640805424, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289226640367030, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289226644935211, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289226642149703, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289226635554791, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289226642699867, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289226643087316, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289226638925956, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289226639275697, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289226639608484, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289226643409177, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289226643729842, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289226644091148, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289226644496785, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289226638287926, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289226637601570, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289226636820609, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "cold", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289226771254274, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289226771254274, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289226771254274, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289226771254274, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289226771025608, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289226760566625, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289226760566625, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289226760566625, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289226760566625, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289226759570263, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289226771894686, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289226771254274, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289226771254274, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289226771254274, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289226771025608, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289226760566625, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289226760566625, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289226760566625, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289226759570263, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 44392.5432, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289226641282543, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289226641697016, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289226640805424, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289226640367030, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289226644935211, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289226642149703, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289226635554791, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289226642699867, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289226643087316, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289226638925956, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289226639275697, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289226639608484, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289226643409177, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289226643729842, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289226644091148, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289226644496785, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289226638287926, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289226637601570, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289226636820609, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 2, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289226641282543, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289226641697016, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289226640805424, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289226640367030, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289226644935211, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289226642149703, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289226635554791, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289226642699867, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289226643087316, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289226638925956, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289226639275697, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289226639608484, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289226643409177, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289226643729842, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289226644091148, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289226644496785, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289226638287926, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289226637601570, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289226636820609, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "warm", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289226771254274, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289226771254274, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289226771254274, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289226771254274, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289226771025608, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289226760566625, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289226760566625, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289226760566625, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289226760566625, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289226759570263, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289226771894686, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289226771254274, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289226771254274, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289226771254274, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289226771025608, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289226760566625, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289226760566625, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289226760566625, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289226759570263, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 25939.7038, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289226641282543, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289226641697016, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289226640805424, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289226640367030, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289226644935211, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289226642149703, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289226635554791, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289226642699867, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289226643087316, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289226638925956, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289226639275697, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289226639608484, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289226643409177, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289226643729842, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289226644091148, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289226644496785, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289226638287926, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289226637601570, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289226636820609, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 2, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227233493758, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227233975006, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227233042444, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227232635250, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227236853587, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227234295958, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227228282918, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227234774116, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227235094903, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227231004869, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227231407660, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227231818698, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227235479141, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227235733728, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227236087908, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227236434941, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227230494639, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227229807499, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227229310501, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "cold", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289227342307190, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227342307190, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227342307190, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227342307190, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227341969881, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289227333869274, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227333869274, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227333869274, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227333869274, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227333009443, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289227342886954, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227342307190, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227342307190, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227342307190, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227341969881, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227333869274, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227333869274, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227333869274, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227333009443, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 29966.2801, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227233493758, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227233975006, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227233042444, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227232635250, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227236853587, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227234295958, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227228282918, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227234774116, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227235094903, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227231004869, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227231407660, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227231818698, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227235479141, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227235733728, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227236087908, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227236434941, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227230494639, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227229807499, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227229310501, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 3, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227233493758, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227233975006, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227233042444, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227232635250, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227236853587, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227234295958, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227228282918, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227234774116, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227235094903, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227231004869, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227231407660, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227231818698, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227235479141, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227235733728, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227236087908, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227236434941, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227230494639, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227229807499, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227229310501, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "warm", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289227342307190, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227342307190, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227342307190, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227342307190, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227341969881, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289227333869274, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227333869274, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227333869274, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227333869274, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227333009443, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289227342886954, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227342307190, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227342307190, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227342307190, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227341969881, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227333869274, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227333869274, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227333869274, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227333009443, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 21720.9164, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227233493758, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227233975006, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227233042444, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227232635250, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227236853587, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227234295958, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227228282918, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227234774116, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227235094903, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227231004869, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227231407660, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227231818698, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227235479141, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227235733728, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227236087908, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227236434941, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227230494639, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227229807499, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227229310501, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 3, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227752849153, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227753489189, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227752288049, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227751817510, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227756432042, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227753927890, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227747632203, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227754339899, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227754768706, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227750451482, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227750767494, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227751088872, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227755125158, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227755409065, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227755763259, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227756073228, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227749869360, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227749180882, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227748685271, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "cold", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289227862612114, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227862606261, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227862456092, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227862456092, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227862207959, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289227854337610, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227854337610, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227854330737, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227854337610, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227853535164, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289227863098232, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227862606261, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227862456092, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227862456092, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227862207959, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227854337610, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227854330737, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227854337610, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227853535164, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 30249.1799, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227752849153, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227753489189, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227752288049, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227751817510, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227756432042, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227753927890, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227747632203, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227754339899, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227754768706, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227750451482, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227750767494, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227751088872, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227755125158, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227755409065, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227755763259, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227756073228, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227749869360, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227749180882, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227748685271, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 4, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227752849153, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227753489189, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227752288049, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227751817510, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227756432042, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227753927890, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227747632203, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227754339899, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227754768706, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227750451482, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227750767494, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227751088872, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227755125158, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227755409065, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227755763259, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227756073228, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227749869360, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227749180882, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227748685271, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "warm", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289227862612114, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227862606261, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227862456092, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227862456092, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227862207959, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289227854337610, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227854337610, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227854330737, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227854337610, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227853535164, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289227863098232, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289227862606261, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289227862456092, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289227862456092, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289227862207959, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289227854337610, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289227854330737, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289227854337610, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289227853535164, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 20455.7501, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289227752849153, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289227753489189, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289227752288049, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289227751817510, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289227756432042, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289227753927890, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289227747632203, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289227754339899, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289227754768706, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289227750451482, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289227750767494, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289227751088872, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289227755125158, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289227755409065, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289227755763259, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289227756073228, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289227749869360, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289227749180882, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289227748685271, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 4, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289228268023883, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289228268463648, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289228267569760, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289228267224601, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289228271407094, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289228268816262, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289228263096256, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289228269295386, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289228269686053, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289228265797156, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289228266126260, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289228266448249, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289228269966719, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289228270295455, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289228270605517, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289228270928612, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289228265216253, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289228264528625, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289228264048312, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "cold", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289228377244852, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289228377244852, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289228377244852, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289228377244852, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289228376747781, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289228368690192, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289228368690192, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289228368644589, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289228368684962, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289228367754840, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289228377783335, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289228377244852, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289228377244852, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289228377244852, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289228376747781, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289228368690192, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289228368644589, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289228368684962, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289228367754840, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 31212.0025, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289228268023883, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289228268463648, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289228267569760, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289228267224601, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289228271407094, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289228268816262, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289228263096256, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289228269295386, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289228269686053, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289228265797156, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289228266126260, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289228266448249, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289228269966719, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289228270295455, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289228270605517, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289228270928612, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289228265216253, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289228264528625, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289228264048312, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 5, + "exitCode": 0 + }, + { + "coldMetadataTimestampWrites": [ + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289228268023883, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289228268463648, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289228267569760, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289228267224601, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289228271407094, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289228268816262, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289228263096256, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289228269295386, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289228269686053, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289228265797156, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289228266126260, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289228266448249, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289228269966719, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289228270295455, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289228270605517, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289228270928612, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289228265216253, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289228264528625, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289228264048312, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "phase": "warm", + "files": [ + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\diagnostics.cache", + "length": 8, + "written": 639289228377244852, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289228377244852, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289228377244852, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289228377244852, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": ".assemblies\\05A352DCE1F504406DDF770BBF9653BC38AF78833BCF50FA77444E5E8EC7A7ED\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289228376747781, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\diagnostics.cache", + "length": 8, + "written": 639289228368690192, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289228368690192, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289228368644589, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289228368684962, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": ".assemblies\\67E31A8FFF77E8C38700CA968B09B5967B70E857E1B035A8B067C023A4A13DA6\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289228367754840, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\diagnostics.cache", + "length": 8, + "written": 639289228377783335, + "sha256": "F0AE5CB3974F2C974AB748307FC72DA5C25D80230F5442322B3C65FA5A65AFFB" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.dll", + "length": 38400, + "written": 639289228377244852, + "sha256": "1166B3DF3086F862A96E0AA295B98BB7A84EB238C34499606AD4B502FC7C4A8B" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.pdb", + "length": 30576, + "written": 639289228377244852, + "sha256": "D51D3BEB4C5A57F20B1C9288348EEF49389723F84D8BAB9C374DEF4286836DEE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.types.cache", + "length": 27, + "written": 639289228377244852, + "sha256": "EE684967969ED5A2D0AC646BA4989C84CC27F7A62962B1E8A9329590DD4757C6" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.EditorScripts.xml", + "length": 6101, + "written": 639289228376747781, + "sha256": "3A63AB97CFDFDE3970326485788F31773878B796FCD7C038E2F4CF7FEEB4248C" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.dll", + "length": 60416, + "written": 639289228368690192, + "sha256": "4FF5B864887E4320F8F86E80BCB824095958DA6C7D763C7A592102BA3EBDF7BE" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.pdb", + "length": 35180, + "written": 639289228368644589, + "sha256": "C7E608FB6734F16135070E57DB37289492C63DC27D3E50B4739147097D750601" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.types.cache", + "length": 257, + "written": 639289228368684962, + "sha256": "D4249A8ECFF272C5D55A8F5020CBD98C5B51CCF8A48DE86152BD5DCC959AD83E" + }, + { + "path": "2DD27BAA5ABC423C2F0A4BE6FFF2C3E5CAF718A89884F4681D962181AFA960BE\\Inno.GameScripts.xml", + "length": 11895, + "written": 639289228367754840, + "sha256": "F58CC2748747697A6AF228C3C866E875D9FCB96A81C3F4D13BFC9C8976D96A03" + } + ], + "milliseconds": 24824.0079, + "input": [ + { + "path": "~Samples.imeta", + "length": 111, + "written": 639260112320526444, + "sha256": "367DB780C733BA7F2C2177D3CD0A5AF058AB1F9A8CEF9964F319218F282B8584" + }, + { + "path": "~Samples\\CanvasDemo.rml", + "length": 1442, + "written": 639260112320536722, + "sha256": "AC0494CEB677EC553ACB9548B0D1D25A5C55F3B064ADCE1F372FD2D68BDEBE5A" + }, + { + "path": "~Samples\\CanvasDemo.rml.imeta", + "length": 131, + "written": 639289228268023883, + "sha256": "927B0E81C8F792C1EC82D4F2F3157F73CB68D835DD41DE1ED15F50F8F178BF1E" + }, + { + "path": "~Samples\\CanvasDemoController.cs", + "length": 1114, + "written": 639262659715187111, + "sha256": "21D456CFBA20403F68ECEDA1FF13268841309C227D6CBD3265E90AEE89030129" + }, + { + "path": "~Samples\\CanvasDemoController.cs.imeta", + "length": 136, + "written": 639289228268463648, + "sha256": "634A4E42C7687841DAD9C71180CD0AEC5D3B059102A56B22902532E2479F89D8" + }, + { + "path": "~Samples\\Fonts.imeta", + "length": 111, + "written": 639260112320558206, + "sha256": "F76A1EB37937C436E5B7C9AEF4C3498C0E94C9854696BAD5190DBD566F6B7BF0" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf", + "length": 146156, + "written": 639260112320590696, + "sha256": "74DC638CF436CCE77A0217A2A55CE7906A4CC9E4C595C6F1DCD9DD858078C1B6" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Bold.ttf.imeta", + "length": 125, + "written": 639289228267569760, + "sha256": "7E1C2B77EA652208EAD61B56C6243C259881FEE8C98EFCBF4D4C4CB2C96AA175" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf", + "length": 148540, + "written": 639260112320617322, + "sha256": "D785334AC4E7810F571DEF986BBAD41161F68AC385DB8813F798BF04D71478E1" + }, + { + "path": "~Samples\\Fonts\\LatoLatin-Regular.ttf.imeta", + "length": 125, + "written": 639289228267224601, + "sha256": "576A2D81D3A5C8015BD8F8320A711E1DBA9FE808978560576D80266B3C75CE50" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt", + "length": 10168, + "written": 639260112320568918, + "sha256": "6D7B76BEB62AC65046291D4155FDA54895512EF2EDD720E62B912F00C2D35A43" + }, + { + "path": "~Samples\\Fonts\\LICENSE.txt.imeta", + "length": 127, + "written": 639289228271407094, + "sha256": "EAE2AB0EB702D0F2DA3D578260645DF96A871CF5AACE6A91677C174208275281" + }, + { + "path": "~Samples\\SampleScene.iscene", + "length": 1805, + "written": 639260112320628071, + "sha256": "BB92EE4D54BF0D1B2A54963E68744C0EFB1DCE4F52BB053CA642561E037D7F3A" + }, + { + "path": "~Samples\\SampleScene.iscene.imeta", + "length": 128, + "written": 639289228268816262, + "sha256": "9E859585415756A6D3C96E5809D37E174B10283CE6C003CDF832F0D30778E272" + }, + { + "path": "Editor.imeta", + "length": 111, + "written": 639260112320269934, + "sha256": "1659B9E0F8ED09EA3CF9B057721DD3B97EA017263149DD3F7FE3C95EF14A45DB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs", + "length": 2210, + "written": 639262659714283323, + "sha256": "9371186908FBA6C786CCD15C3404D0B3AE0EF6E60610A523CA657F022882D5EB" + }, + { + "path": "Editor\\CanvasGizmoProvider.editor.cs.imeta", + "length": 136, + "written": 639289228263096256, + "sha256": "59B99C9C5F03AEE778AB507BAFDB4991568579A4DBCFBD8718FC89C6037113F9" + }, + { + "path": "Editor\\Shaders.imeta", + "length": 111, + "written": 639260112320296430, + "sha256": "AB33EF080B2E3E9D3853B3CA5009E8535FA30DD4E52E86DE1398DF8A9EA5B74B" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs", + "length": 6358, + "written": 639262659714380513, + "sha256": "88647C72488DABE469D33020AA6FBCD8B29FD166629FAE71D68C7ED1CE5C25DC" + }, + { + "path": "Editor\\Shaders\\CanvasShaderPreview.editor.cs.imeta", + "length": 136, + "written": 639289228269295386, + "sha256": "5B67170F6261B2F8FA509814835556D93D67A9E7896B13CD51EE586CABF83E23" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs", + "length": 9984, + "written": 639262659714480507, + "sha256": "104540D3D469DEF030B83CAF869A77261BCB2C3F422B3FCE756AED13DE47136D" + }, + { + "path": "Editor\\Shaders\\CanvasShaderTemplate.editor.cs.imeta", + "length": 136, + "written": 639289228269686053, + "sha256": "584ADC04A1D9C5257E3B12F1D5678127727165CF8619FC058844F4D447CC7AE7" + }, + { + "path": "Materials.imeta", + "length": 111, + "written": 639260112320341574, + "sha256": "12C2D2A153118A4A4520BE0AEBAA68AAF9F13FB70AEDFE36A291AA23EA5369B6" + }, + { + "path": "Materials\\Canvas.imaterial", + "length": 462, + "written": 639260112320361597, + "sha256": "227A1923D400631C63D3E0FED90A850907118CA04EF3438B01A2F37D50174E05" + }, + { + "path": "Materials\\Canvas.imaterial.imeta", + "length": 134, + "written": 639289228265797156, + "sha256": "BBF9931F93C0CE4AA3B5A697C731D1EAA4688B6CCEAB15C1320A2BBE615D7A56" + }, + { + "path": "Runtime.imeta", + "length": 111, + "written": 639260112320371564, + "sha256": "697378BB007B993454D45E2E5A5CDD7118673FCA61FA4BBAAA6332BDF8E0BCBD" + }, + { + "path": "Runtime\\CanvasIds.cs", + "length": 1030, + "written": 639262659714580526, + "sha256": "55B7163BEACD0C1F3B67A8D2D3031E3E922C42FD1C18322B02AE204A0452A7C4" + }, + { + "path": "Runtime\\CanvasIds.cs.imeta", + "length": 136, + "written": 639289228266126260, + "sha256": "15A7E80712E4209255EB261EBBA8C56ED8DBA657EFDFBAAFD51BFB21B857CF96" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs", + "length": 1136, + "written": 639279145487450394, + "sha256": "AF65BC3E8DD06252ED39F0283A6D78AD46E1C8AED6BEFA75CBEA0805B466B2F8" + }, + { + "path": "Runtime\\CanvasProjectSettings.cs.imeta", + "length": 136, + "written": 639289228266448249, + "sha256": "4400A13FF1147FF0D50894976C3C410C5D490E34CB455EFF926642CFEA8FCC89" + }, + { + "path": "Runtime\\Components.imeta", + "length": 111, + "written": 639260112320412610, + "sha256": "C7CD81DE80CA337D8B317F2ADDC69051E52535E61066A86635D95F2E8D30D3F9" + }, + { + "path": "Runtime\\Components\\Canvas.cs", + "length": 10829, + "written": 639282477417997555, + "sha256": "7F260F159B5F0925B8F51100EC33DC1F31AC6A60B63FFB2B563F1BB690B8580F" + }, + { + "path": "Runtime\\Components\\Canvas.cs.imeta", + "length": 136, + "written": 639289228269966719, + "sha256": "F286D0D632897EBBDFBC6C4432EAB10B5F14379B4C5103C475582A0196898BF7" + }, + { + "path": "Runtime\\Rendering.imeta", + "length": 111, + "written": 639260112320439703, + "sha256": "766DCE173749724802BC074E9EC47D3BE55CE8C2F77F91E4E5A1B8E864387355" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs", + "length": 1080, + "written": 639271655238940178, + "sha256": "1285675F0DD81C068915E28913460E11F1F7B9A8C6E86EC6D05F873A87F1BF47" + }, + { + "path": "Runtime\\Rendering\\CanvasRenderState.cs.imeta", + "length": 136, + "written": 639289228270295455, + "sha256": "9224E5B9DA29CC11C32912859E460A800CC62FA280BA4E7E1D80978C67553475" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs", + "length": 19282, + "written": 639282479899957603, + "sha256": "483A958DAE4CAD36E6BF180CA882731E54F34E827AC53F311A88AF027BD166AC" + }, + { + "path": "Runtime\\Rendering\\CanvasViewContentSource.cs.imeta", + "length": 136, + "written": 639289228270605517, + "sha256": "1CAC279C4FDB47C67DC8A579B4FF330B5004AFC2A1AF9C9946323EA650900DA4" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs", + "length": 15674, + "written": 639271655238986240, + "sha256": "73F734EBB62A0C302AF896073CA7E69D022F3E976F2123246394D51147439C8C" + }, + { + "path": "Runtime\\Rendering\\CanvasViewDrawable.cs.imeta", + "length": 136, + "written": 639289228270928612, + "sha256": "DCB94BA17369E991F489ECECDA419870C54CC049956DD6AA495FB1ACDC5253A0" + }, + { + "path": "Shaders.imeta", + "length": 111, + "written": 639260112320472265, + "sha256": "9B405D2305A97E4D8212484F97B5BA30E05AC73EE8E99B491DE4375F85D53FB4" + }, + { + "path": "Shaders\\Canvas.ishader", + "length": 13729, + "written": 639260112320477646, + "sha256": "BFA88B92377C365880C1C32417EA50AAEAB3DE33E445BC8E6752BEACC001DFB6" + }, + { + "path": "Shaders\\Canvas.ishader.imeta", + "length": 132, + "written": 639289228265216253, + "sha256": "1AC354078FBBB9373F7632F4D31E04040BDE47EDAA2A65A225A5F31A3E58B8AD" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource", + "length": 256, + "written": 639260112320493954, + "sha256": "CDB350B31CFBE978BC135E2CBB33EB1095CDFF9FC287CA5FC11A3319907D440A" + }, + { + "path": "Shaders\\CanvasFragment.ishadersource.imeta", + "length": 401, + "written": 639289228264528625, + "sha256": "943972F52EC7F71C2B81077E91A5391E7B3903A043D637E09457A3E752EA5C97" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource", + "length": 361, + "written": 639260112320510242, + "sha256": "53549CCC06654678EB9D930761972B3EB4804295FA97593CE6A53C5AA9B5F8AF" + }, + { + "path": "Shaders\\CanvasVertex.ishadersource.imeta", + "length": 401, + "written": 639289228264048312, + "sha256": "D9986D5BC1DB3D556A3698A5BB14EEDE723415DFB82FB7329A8BF725D34E04DC" + } + ], + "trial": 5, + "exitCode": 0 + } + ] +} diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/current-ui.png b/records/architecture/acceptance/editor-ui-2026-10-30/current-ui.png new file mode 100644 index 000000000..ba2506211 Binary files /dev/null and b/records/architecture/acceptance/editor-ui-2026-10-30/current-ui.png differ diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-examples.json b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-examples.json new file mode 100644 index 000000000..ee2573f16 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-examples.json @@ -0,0 +1,1775 @@ +[ + { + "page": "docs/animation/Inno.Animation.md", + "example": 1, + "line": 23, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/architecture/CSHARP_DEVELOPMENT_STANDARD.md", + "example": 1, + "line": 48, + "referenceMode": "framework", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/assets/Inno.Assets.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/assets/Inno.Assets.Pipeline.md", + "example": 1, + "line": 66, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/assets/Inno.Assets.Pipeline.md", + "example": 2, + "line": 142, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/assets/Inno.Assets.Pipeline.md", + "example": 3, + "line": 203, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/assets/Inno.Content.md", + "example": 1, + "line": 38, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/audio/Inno.Adapter.Audio.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/audio/Inno.Audio.Assets.md", + "example": 1, + "line": 17, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/audio/Inno.Audio.md", + "example": 1, + "line": 59, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/audio/Inno.Audio.Runtime.md", + "example": 1, + "line": 96, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Bindings.md", + "example": 1, + "line": 24, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Bindings.md", + "example": 2, + "line": 81, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Composition.md", + "example": 1, + "line": 20, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Distribution.Standard.md", + "example": 1, + "line": 25, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Managed.md", + "example": 1, + "line": 27, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.md", + "example": 1, + "line": 38, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Products.Standard.md", + "example": 1, + "line": 16, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.SupportPacks.Core.md", + "example": 1, + "line": 21, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Toolchains.md", + "example": 1, + "line": 56, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/build/Inno.Build.Toolchains.md", + "example": 2, + "line": 124, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Collections.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Collections.md", + "example": 2, + "line": 50, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Collections.md", + "example": 3, + "line": 101, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Coroutines.md", + "example": 1, + "line": 22, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Diagnostics.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Diagnostics.md", + "example": 2, + "line": 54, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Diagnostics.md", + "example": 3, + "line": 73, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Diagnostics.md", + "example": 4, + "line": 99, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Diagnostics.md", + "example": 5, + "line": 159, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Events.md", + "example": 1, + "line": 37, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Events.md", + "example": 2, + "line": 88, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Execution.md", + "example": 1, + "line": 66, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Graphs.md", + "example": 1, + "line": 35, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Identity.md", + "example": 1, + "line": 28, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Identity.md", + "example": 2, + "line": 53, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Input.md", + "example": 1, + "line": 30, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.IO.md", + "example": 1, + "line": 44, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.IO.md", + "example": 2, + "line": 63, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Layers.md", + "example": 1, + "line": 44, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Logging.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Logging.md", + "example": 2, + "line": 51, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Logging.md", + "example": 3, + "line": 119, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Mathematics.md", + "example": 1, + "line": 17, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Mathematics.md", + "example": 2, + "line": 74, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 1, + "line": 52, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 2, + "line": 100, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 3, + "line": 156, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 4, + "line": 178, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 5, + "line": 197, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 6, + "line": 231, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 7, + "line": 245, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Serialization.md", + "example": 8, + "line": 308, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/core/Inno.Core.Settings.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Annotations.md", + "example": 1, + "line": 36, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Assets.md", + "example": 1, + "line": 16, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Assets.md", + "example": 2, + "line": 76, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Audio.md", + "example": 1, + "line": 19, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 1, + "line": 57, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 2, + "line": 90, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 3, + "line": 118, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 4, + "line": 141, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 5, + "line": 163, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Core.md", + "example": 6, + "line": 218, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Hosting.md", + "example": 1, + "line": 30, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.ImGui.md", + "example": 1, + "line": 123, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.ImGui.md", + "example": 2, + "line": 161, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.ImGui.md", + "example": 3, + "line": 226, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Inspection.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Inspection.md", + "example": 2, + "line": 56, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Inspection.md", + "example": 3, + "line": 83, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Inspection.md", + "example": 4, + "line": 108, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 1, + "line": 17, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 2, + "line": 42, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 3, + "line": 56, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 4, + "line": 74, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 5, + "line": 115, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 6, + "line": 132, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 7, + "line": 159, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 8, + "line": 170, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 9, + "line": 208, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 10, + "line": 220, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 11, + "line": 285, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 12, + "line": 303, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Interactions.md", + "example": 13, + "line": 323, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.FileBrowser.md", + "example": 1, + "line": 45, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.FileBrowser.md", + "example": 2, + "line": 80, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.FileBrowser.md", + "example": 3, + "line": 106, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.Hierarchy.md", + "example": 1, + "line": 21, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.Inspector.md", + "example": 1, + "line": 38, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.Settings.md", + "example": 1, + "line": 48, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.Settings.md", + "example": 2, + "line": 76, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.ShaderEditor.md", + "example": 1, + "line": 37, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Panel.Stats.md", + "example": 1, + "line": 17, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.PlayMode.md", + "example": 1, + "line": 68, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.PlayMode.md", + "example": 2, + "line": 81, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Rendering.md", + "example": 1, + "line": 66, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Scene.md", + "example": 1, + "line": 100, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Scripting.md", + "example": 1, + "line": 29, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Scripting.md", + "example": 2, + "line": 100, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Scripting.md", + "example": 3, + "line": 145, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Scripting.md", + "example": 4, + "line": 189, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Settings.md", + "example": 1, + "line": 25, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Settings.md", + "example": 2, + "line": 57, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Settings.md", + "example": 3, + "line": 75, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/editor/Inno.Editor.Shaders.md", + "example": 1, + "line": 20, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Catalogs.md", + "example": 1, + "line": 34, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Modules.md", + "example": 1, + "line": 51, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Reload.md", + "example": 1, + "line": 47, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Types.md", + "example": 1, + "line": 23, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Types.md", + "example": 2, + "line": 68, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Types.md", + "example": 3, + "line": 129, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/extensibility/Inno.Extensibility.Types.md", + "example": 4, + "line": 189, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/input/Inno.Input.md", + "example": 1, + "line": 18, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/Inno.Adapter.Platform.md", + "example": 1, + "line": 15, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/Inno.Adapter.Presentation.md", + "example": 1, + "line": 21, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/Inno.Platform.md", + "example": 1, + "line": 30, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/references/Inno.References.md", + "example": 1, + "line": 44, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Adapter.Rendering.md", + "example": 1, + "line": 18, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Assets.Authoring.md", + "example": 1, + "line": 20, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Assets.md", + "example": 1, + "line": 20, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.md", + "example": 1, + "line": 24, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Runtime.md", + "example": 1, + "line": 28, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Shaders.md", + "example": 1, + "line": 193, + "referenceMode": "editor-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2;Inno.ScriptApi.Editor:CF075B761880D3654205A07CC6D170305D919AB2A9BAA768A21C77A8AED21CB7", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Shaders.md", + "example": 2, + "line": 218, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/rendering/Inno.Rendering.Shaders.md", + "example": 3, + "line": 252, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Adapter.Authoring.Default.md", + "example": 1, + "line": 13, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Adapter.Default.md", + "example": 1, + "line": 20, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Adapter.md", + "example": 1, + "line": 13, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Player.Runtime.md", + "example": 1, + "line": 22, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Runtime.Contracts.md", + "example": 1, + "line": 37, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Runtime.Contracts.md", + "example": 2, + "line": 68, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Runtime.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Shell.md", + "example": 1, + "line": 40, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/runtime/Inno.Shell.md", + "example": 2, + "line": 67, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.Assets.md", + "example": 1, + "line": 36, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 1, + "line": 24, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 2, + "line": 51, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 3, + "line": 81, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 4, + "line": 95, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 5, + "line": 127, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 6, + "line": 145, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 7, + "line": 173, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scene/Inno.Scene.md", + "example": 8, + "line": 270, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 1, + "line": 31, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 2, + "line": 45, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 3, + "line": 63, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 4, + "line": 74, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 5, + "line": 90, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 6, + "line": 108, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Api.md", + "example": 7, + "line": 129, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/scripting/Inno.Scripting.Compiler.md", + "example": 1, + "line": 69, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/storage/Inno.Adapter.Storage.md", + "example": 1, + "line": 15, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/storage/Inno.Storage.md", + "example": 1, + "line": 17, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/testing/Inno.Testing.Processes.md", + "example": 1, + "line": 22, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/testing/Inno.Testing.Source.md", + "example": 1, + "line": 23, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/text/Inno.Text.md", + "example": 1, + "line": 22, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/text/Inno.Text.Runtime.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/ui/Inno.UI.md", + "example": 1, + "line": 22, + "referenceMode": "runtime-script", + "referenceIdentity": "Inno.ScriptApi.Runtime:A13B00AEAC71043B29882484FC406548E9870F28C8BEDAD8EBD6DF3583D38CC2", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/ui/Inno.UI.Runtime.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/BrowserWasm32/Inno.Adapter.Storage.BrowserWasm32.md", + "example": 1, + "line": 28, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/BrowserWasm32/Inno.Integration.BrowserWasm32.Bgfx.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/BrowserWasm32/Inno.Integration.BrowserWasm32.Bgfx.Runtime.md", + "example": 1, + "line": 18, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/BrowserWasm32/Inno.Integration.BrowserWasm32.Build.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/BrowserWasm32/Inno.Integration.BrowserWasm32.Sdl3.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/MacOSArm64/Inno.Integration.MacOSArm64.Bgfx.md", + "example": 1, + "line": 16, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/MacOSArm64/Inno.Integration.MacOSArm64.Bgfx.Runtime.md", + "example": 1, + "line": 18, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/MacOSArm64/Inno.Integration.MacOSArm64.Build.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/MacOSArm64/Inno.Integration.MacOSArm64.Sdl3.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/Windows/Inno.Build.Windows.Host.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/WindowsX64/Inno.Editor.WindowsX64.md", + "example": 1, + "line": 24, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/WindowsX64/Inno.Integration.WindowsX64.Bgfx.md", + "example": 1, + "line": 16, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/WindowsX64/Inno.Integration.WindowsX64.Bgfx.Runtime.md", + "example": 1, + "line": 18, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/WindowsX64/Inno.Integration.WindowsX64.Build.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/platform/WindowsX64/Inno.Integration.WindowsX64.Sdl3.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.md", + "example": 1, + "line": 22, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Shaders.md", + "example": 1, + "line": 10, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Tools.md", + "example": 1, + "line": 51, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Bgfx/Inno.Build.Toolchains.Bgfx.Tools.md", + "example": 2, + "line": 96, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/DotNet/Inno.Adapter.Modules.DotNet.md", + "example": 1, + "line": 31, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/DotNet/Inno.Adapter.Serialization.DotNet.md", + "example": 1, + "line": 21, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/FileSystem/Inno.Adapter.Content.FileSystem.md", + "example": 1, + "line": 40, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/ImGui/Inno.Build.Toolchains.ImGui.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/ImGui/Inno.Build.Toolchains.ImGuizmo.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/ImGui/Inno.Native.ImGuizmo.md", + "example": 1, + "line": 48, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Interop/Inno.Native.LibraryLoading.md", + "example": 1, + "line": 24, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/MiniAudio/Inno.Adapter.Audio.MiniAudio.md", + "example": 1, + "line": 24, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/MiniAudio/Inno.Build.Toolchains.MiniAudio.md", + "example": 1, + "line": 10, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/MiniAudio/Inno.Native.MiniAudio.md", + "example": 1, + "line": 40, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/RmlUi/Inno.Adapter.UI.RmlUi.Authoring.md", + "example": 1, + "line": 31, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/RmlUi/Inno.Adapter.UI.RmlUi.md", + "example": 1, + "line": 12, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/RmlUi/Inno.Build.Toolchains.UI.md", + "example": 1, + "line": 10, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/RmlUi/Inno.Native.UI.md", + "example": 1, + "line": 31, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Sdl3/Inno.Build.Toolchains.Sdl3.md", + "example": 1, + "line": 10, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Text/Inno.Adapter.Text.FreeTypeHarfBuzz.md", + "example": 1, + "line": 14, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Text/Inno.Build.Toolchains.Text.md", + "example": 1, + "line": 10, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + }, + { + "page": "docs/backends/Text/Inno.Native.Text.md", + "example": 1, + "line": 20, + "referenceMode": "host-sdk", + "referenceIdentity": "", + "errors": [], + "notEvaluatedReason": null + } +] \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-result.json b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-result.json new file mode 100644 index 000000000..08d8b298e --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki-result.json @@ -0,0 +1,13 @@ +{ + "productionRevision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "status": "Passed", + "exitCode": 0, + "evaluatedProjectOwners": 241, + "apiInventoryEntries": 34582, + "compiledExamples": 197, + "errors": 0, + "unevaluatedExamples": 0, + "apiInventorySha256": "a4bab227f10b39e7a02298c29884559e59ba86f23529627a2a5d9e4becd6fec3", + "examplesSha256": "6c71b7d8cb35fd7106ed0a77b2e2fd0dfb0bca92fd6a3a2660d046f1c309cbfe", + "scope": "Official complete architecture/XML/Wiki after final current-document changes, selected Windows target, prepared independent Release output and freshly generated formal logical references; three foreign product API compilations explicitly outside this target. Exact delivery-head CI remains separately attached to PR26." +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki.log b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki.log new file mode 100644 index 000000000..373054b0e --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/delivery-wiki.log @@ -0,0 +1,7 @@ +D:\InnoEngine-Verification\editor-ui-closure-2026-10-27\delivery-documentation-wiki\Scripts +Cache: 174090357 bytes; protected 174090357; reclaimed 0; skipped 0. +Wiki product 'platforms\MacOSArm64\player\Inno.Player.MacOSArm64\Inno.Player.MacOSArm64.csproj' is outside the selected target; API compilation was not evaluated. +Wiki product 'platforms\MacOSArm64\editor\Inno.Editor.MacOSArm64\Inno.Editor.MacOSArm64.csproj' is outside the selected target; API compilation was not evaluated. +Wiki product 'platforms\BrowserWasm32\player\Inno.Player.BrowserWasm32\Inno.Player.BrowserWasm32.csproj' is outside the selected target; API compilation was not evaluated. +Evaluated MSBuild ownership for 241/241 projects without executing build targets. +InnoEngine architecture validation passed. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/environment.json b/records/architecture/acceptance/editor-ui-2026-10-30/environment.json new file mode 100644 index 000000000..90c0c3ece --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/environment.json @@ -0,0 +1,89 @@ +{ + "recorded": "2026-10-30T09:18:32.6133699+08:00", + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "processor": [ + { + "Name": "AMD Ryzen 9 6900HX with Radeon Graphics ", + "NumberOfCores": 8, + "NumberOfLogicalProcessors": 16 + } + ], + "graphics": [ + { + "Name": "AMD Radeon(TM) Graphics", + "DriverVersion": "31.0.14084.6002" + }, + { + "Name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "DriverVersion": "32.0.15.9649" + } + ], + "operatingSystem": { + "Caption": "Microsoft Windows 11 专业版", + "Version": "10.0.26200", + "OSArchitecture": "64 位" + }, + "disk": [ + { + "drive": "C", + "freeBytes": 83861815296 + }, + { + "drive": "D", + "freeBytes": 8294096896 + } + ], + "dotnetInfo": [ + ".NET SDK:", + " Version: 10.0.402", + " Commit: a8dd0577c5", + " Workload version: 10.0.400-manifests.da132f47", + " MSBuild version: 18.9.15+a8dd0577c", + "", + "运行时环境:", + " OS Name: Windows", + " OS Version: 10.0.26200", + " OS Platform: Windows", + " RID: win-x64", + " Base Path: C:\\Users\\23842\\.dotnet\\sdk\\10.0.402\\", + "", + "已安装 .NET 工作负载:", + " [wasm-tools]", + " 安装源文件: SDK 10.0.400", + " 清单版本: 10.0.113/10.0.100", + " 清单路径: C:\\Users\\23842\\.dotnet\\sdk-manifests\\10.0.100\\microsoft.net.workload.mono.toolchain.current\\10.0.113\\WorkloadManifest.json", + " 安装类型: FileBased", + "", + "已配置为在安装新清单时使用 workload sets。", + "未安装任何 workload sets。运行 “dotnet workload restore” 以安装工作负载集。", + "", + "Host:", + " Version: 10.0.13", + " Architecture: x64", + " Commit: 4099f83977", + "", + ".NET SDKs installed:", + " 10.0.402 [C:\\Users\\23842\\.dotnet\\sdk]", + "", + ".NET runtimes installed:", + " Microsoft.AspNetCore.App 10.0.13 [C:\\Users\\23842\\.dotnet\\shared\\Microsoft.AspNetCore.App]", + " Microsoft.NETCore.App 10.0.13 [C:\\Users\\23842\\.dotnet\\shared\\Microsoft.NETCore.App]", + " Microsoft.WindowsDesktop.App 10.0.13 [C:\\Users\\23842\\.dotnet\\shared\\Microsoft.WindowsDesktop.App]", + "", + "Other architectures found:", + " None", + "", + "Environment variables:", + " Not set", + "", + "global.json file:", + " C:\\Dev\\GameEngineDev\\InnoEngine\\global.json", + "", + "Learn more:", + " https://aka.ms/dotnet/info", + "", + "Download .NET:", + " https://aka.ms/dotnet/download" + ], + "scope": "Fresh installed-device/SDK inventory. It does not prove exclusive GPU attribution, physical display/DPI behavior, OS IME operation or manual usability." +} diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/file-tree.md b/records/architecture/acceptance/editor-ui-2026-10-30/file-tree.md new file mode 100644 index 000000000..5b39a9b3e --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/file-tree.md @@ -0,0 +1,116 @@ +```text +InnoEngine/ +├── src/composition/editor/ +│ ├── contracts/Inno.Editor.Annotations/ +│ ├── framework/ +│ │ ├── Inno.Editor.Core/Runtime/EditorStatistics.cs +│ │ ├── Inno.Editor.Diagnostics/ +│ │ ├── Inno.Editor.Graph/ # Neutral documents/controller/History +│ │ ├── Inno.Editor.Inspection/ # Reusable inspection contracts +│ │ ├── Inno.Editor.Interactions/ +│ │ │ ├── Actions/EditorActionRouter.cs +│ │ │ ├── Menus/EditorMenuCatalog.cs +│ │ │ ├── Editing/EditorEditGesture.cs +│ │ │ ├── Selection/ +│ │ │ ├── History/ +│ │ │ └── Documents/ +│ │ └── Inno.Editor.Settings/ +│ ├── features/ +│ │ ├── Inno.Editor.Assets/ +│ │ ├── Inno.Editor.Audio/ +│ │ ├── Inno.Editor.Exporting/ +│ │ ├── Inno.Editor.PlayMode/ +│ │ ├── Inno.Editor.Rendering/ +│ │ ├── Inno.Editor.Scene/Documents/SceneEdits*.cs +│ │ ├── Inno.Editor.Scripting/ +│ │ └── Inno.Editor.Shaders/ +│ ├── presentation/ +│ │ ├── Inno.Editor.ImGui/ +│ │ │ ├── Runtime/EditorUiSession.cs +│ │ │ ├── Controls/ +│ │ │ ├── Widgets/ImGuiWidget.*.cs +│ │ │ ├── Menus/ +│ │ │ └── Styling/ +│ │ ├── Inno.Editor.Assets.ImGui/ # Browser presentation policy +│ │ ├── Inno.Editor.Graph.ImGui/Canvas/ +│ │ │ ├── GraphCanvasNode.cs +│ │ │ ├── GraphCanvasPort.cs +│ │ │ ├── GraphCanvasLayout.cs +│ │ │ ├── GraphCanvasPointerInput.cs +│ │ │ ├── GraphCanvasPointerResult.cs +│ │ │ ├── GraphCanvasPointerController.cs +│ │ │ ├── GraphCanvasView.cs +│ │ │ └── GraphCanvasView.Nodes.cs +│ │ ├── Inno.Editor.Shaders.ImGui/ # Shader-specific content +│ │ └── Inno.Editor.Workbench/ +│ ├── panels/ +│ │ ├── Inno.Editor.Panel.FileBrowser/ +│ │ ├── Inno.Editor.Panel.GameView/ +│ │ ├── Inno.Editor.Panel.Hierarchy/ +│ │ ├── Inno.Editor.Panel.Inspector/ +│ │ ├── Inno.Editor.Panel.Logging/Console/ +│ │ ├── Inno.Editor.Panel.SceneView/ +│ │ ├── Inno.Editor.Panel.Settings/ +│ │ ├── Inno.Editor.Panel.ShaderEditor/Canvas/ +│ │ └── Inno.Editor.Panel.Stats/FrameStatisticsSampler.cs +│ └── hosting/Inno.Editor.Hosting/ +│ ├── Hosting/ +│ └── Layout/ +│ ├── EditorDefaultLayout.cs +│ └── DefaultEditorLayout.ini +├── src/runtime/scripting/Inno.Scripting.Compiler/ +│ ├── ScriptCompiler.cs +│ ├── Compilation/ScriptCompilerEngine.cs +│ ├── ScriptCompilerOptions.cs +│ └── ScriptCompilationInputs.cs +├── build/toolchains/Inno.Build.Toolchains/Managed/ +│ ├── ManagedBuildInputCapture.cs +│ ├── ManagedBuildInputReader.cs +│ ├── ManagedInputEvaluation.cs +│ └── ManagedCompilerInputs.targets +├── src/foundation/core/ +│ ├── Inno.Core.IO/ +│ │ ├── AtomicFile.cs +│ │ ├── AtomicDirectory.cs +│ │ ├── FileByteDocumentStore.cs +│ │ ├── FileSystemRename.cs +│ │ └── PosixFilePublication.cs +│ └── Inno.Core.Serialization/ +│ ├── SerializationSnapshot.cs +│ ├── Capture/SerializationCaptureKeyBuilder.cs +│ ├── Converters/SerializationConverter.cs +│ └── Metadata/SerializationRevisionAttribute.cs +├── src/content/scene/Inno.Scene/ +│ ├── Service/HierarchyService.cs +│ ├── Storage/SceneStore.cs +│ ├── Storage/SceneStructureSnapshot.cs +│ └── Serialization/SceneGraph/ +│ ├── SceneGraphSourceIds.cs +│ └── ScenePropertyReferenceScope.cs +├── backends/ImGui/runtime/Inno.Adapter.Presentation.ImGui.Sdl3/ +│ ├── Context/PlatformImGuiContext.cs +│ ├── Context/PlatformImGuiContext.Fonts.cs +│ └── BuiltIn/Fonts/Supplemental/ # Official font + original OFL +├── tests/ +│ ├── editor/ # Public contract/native input/lifecycle +│ ├── scripting/ +│ │ ├── Inno.Editor.Scripting.Tests/ +│ │ └── Inno.Scripting.Compiler.Tests/ScriptCachePublicationTests.cs +│ ├── build/Inno.Build.Tests/ +│ ├── core/Inno.Core.Serialization.Tests/ +│ ├── core/Inno.Core.IO.Tests/ +│ ├── scene/Inno.Scene.Tests/ +│ ├── tooling/Inno.Tooling.Architecture.Tests/ +│ └── Invoke-EditorUiAcceptance.ps1 +├── docs/architecture/ +│ ├── EDITOR_UI_DESIGN_STANDARD.md +│ ├── EDITOR_UI_ARCHITECTURE.md +│ ├── CURRENT_ACCEPTANCE.md +│ └── CURRENT_ISSUES.md +└── records/architecture/ + ├── EDITOR_UI_IMPLEMENTATION_PLAN.md + ├── EDITOR_UI_CLOSURE_PLAN.md + └── acceptance/ # Dated reports and portable evidence +``` + +All 30 Editor projects are shown. The tree lists ownership boundaries and changed key files, not every unchanged source file. No empty symmetry project, platform-specific UI project or parallel foundational service is introduced. Native generation remains owned by the existing unmodified BGCS integration and platform contributions. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/gpu-context-observations.json b/records/architecture/acceptance/editor-ui-2026-10-30/gpu-context-observations.json new file mode 100644 index 000000000..862d352e0 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/gpu-context-observations.json @@ -0,0 +1,177 @@ +{ + "scope": "Read-only public NVIDIA process/context association for exact owned product PIDs. No desktop control, injection, exclusive-adapter or physical-display claim. Not a timing measurement.", + "recorded": "2026-10-30T12:10:15.3838406+08:00", + "status": "Stopped", + "observations": [ + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-D3D11\\02b3f498325e43469c5be8aa1274727e\\Products\\journey-D3D11\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D11", + "time": "2026-10-30T11:32:17.9641362+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 38112, + "created": "2026-10-30T11:32:14.509286+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-D3D11\\02b3f498325e43469c5be8aa1274727e\\Products\\lifecycle-D3D11\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D11", + "time": "2026-10-30T11:35:46.3731012+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 36020, + "created": "2026-10-30T11:35:44.475227+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-D3D12\\70955c80b80d41e6bdfce55d3288ca9d\\Products\\journey-D3D12\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D12", + "time": "2026-10-30T11:38:33.2950203+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 35712, + "created": "2026-10-30T11:38:29.975196+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-D3D12\\70955c80b80d41e6bdfce55d3288ca9d\\Products\\lifecycle-D3D12\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D12", + "time": "2026-10-30T11:42:38.8380660+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 7244, + "created": "2026-10-30T11:42:33.788184+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-Vulkan\\454b2eb9ba094cc4a18fb46a58328976\\Products\\journey-Vulkan\\Project --window-visible false --smoke-frames 30000 --graphics-api Vulkan", + "time": "2026-10-30T11:46:34.1593946+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 34372, + "created": "2026-10-30T11:46:30.47345+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Debug-Vulkan\\454b2eb9ba094cc4a18fb46a58328976\\Products\\lifecycle-Vulkan\\Project --window-visible false --smoke-frames 30000 --graphics-api Vulkan", + "time": "2026-10-30T11:50:07.9257201+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 24308, + "created": "2026-10-30T11:50:03.628168+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-D3D11\\b863e4f7483e4f118991d61c6e819d92\\Products\\journey-D3D11\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D11", + "time": "2026-10-30T11:53:05.5452927+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 26020, + "created": "2026-10-30T11:53:00.878645+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-D3D11\\b863e4f7483e4f118991d61c6e819d92\\Products\\lifecycle-D3D11\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D11", + "time": "2026-10-30T11:55:00.4809761+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 16016, + "created": "2026-10-30T11:54:57.371018+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-D3D12\\93cbca7f8244415c89c45dd9dd2e0f6a\\Products\\journey-D3D12\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D12", + "time": "2026-10-30T11:56:39.8187226+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 7924, + "created": "2026-10-30T11:56:36.664035+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-D3D12\\93cbca7f8244415c89c45dd9dd2e0f6a\\Products\\lifecycle-D3D12\\Project --window-visible false --smoke-frames 30000 --graphics-api D3D12", + "time": "2026-10-30T11:59:16.7532560+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 42688, + "created": "2026-10-30T11:59:12.312585+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-Vulkan\\a13c10f2cc674e2cbad3a53511b448db\\Products\\journey-Vulkan\\Project --window-visible false --smoke-frames 30000 --graphics-api Vulkan", + "time": "2026-10-30T12:01:43.0848367+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 42176, + "created": "2026-10-30T12:01:39.499794+08:00" + }, + { + "command": "\"C:\\Users\\23842\\.dotnet\\dotnet.exe\" D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\products-Release-Vulkan\\a13c10f2cc674e2cbad3a53511b448db\\Products\\lifecycle-Vulkan\\Project --window-visible false --smoke-frames 30000 --graphics-api Vulkan", + "time": "2026-10-30T12:03:38.2622966+08:00", + "gpuContextAssociations": [ + { + "memory": "N/A", + "name": "NVIDIA GeForce RTX 3070 Ti Laptop GPU", + "type": "C+G", + "bus": "00000000:01:00.0" + } + ], + "pid": 30976, + "created": "2026-10-30T12:03:34.257892+08:00" + } + ], + "started": "2026-10-30T11:22:56.0192639+08:00", + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee" +} diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-protocol.json b/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-protocol.json new file mode 100644 index 000000000..21a09296e --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-protocol.json @@ -0,0 +1,12 @@ +{ + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "reason": "Investigate two initial graph P95/max flags; preserve all original 150 trials.", + "producers": "Same unmodified production and public fixture assemblies; original b303d9f1, current dd5979c6.", + "pairs": 5, + "warmupFrames": 5, + "measuredFramesPerProcess": 50, + "order": "original,current on even pairs; current,original on odd pairs", + "metrics": "Pool all 50 frames in each process; nearest-rank P95; retain maxima, CPU, allocation, GC and geometry counts.", + "acceptance": "Same +10% median and at least three paired timing regressions; no hot-path allocation increase. No best-repetition selection.", + "pending": true +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-summary.json b/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-summary.json new file mode 100644 index 000000000..f7f9a7a30 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/graph-tail-summary.json @@ -0,0 +1,415 @@ +{ + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "trials": [ + { + "source": "original", + "pair": 0, + "medianMs": 32.14015, + "p95Ms": 38.1169, + "maximumMs": 44.106, + "cpuMsPerOperation": 45.3125, + "allocatedBytesPerOperation": 4506232.8, + "totalAllocatedBytesPerOperation": 4506881.12, + "gen0": 28, + "gen1": 6, + "gen2": 6, + "vertices": [ + 182260 + ], + "indices": [ + 456183 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-0-original" + }, + { + "source": "current", + "pair": 0, + "medianMs": 29.2441, + "p95Ms": 32.9627, + "maximumMs": 33.2867, + "cpuMsPerOperation": 36.875, + "allocatedBytesPerOperation": 3335401.28, + "totalAllocatedBytesPerOperation": 3335667.04, + "gen0": 21, + "gen1": 2, + "gen2": 2, + "vertices": [ + 184030 + ], + "indices": [ + 458625 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-0-current" + }, + { + "source": "current", + "pair": 1, + "medianMs": 22.2517, + "p95Ms": 27.737, + "maximumMs": 28.4866, + "cpuMsPerOperation": 28.75, + "allocatedBytesPerOperation": 3282931.68, + "totalAllocatedBytesPerOperation": 3283197.44, + "gen0": 21, + "gen1": 2, + "gen2": 2, + "vertices": [ + 184030 + ], + "indices": [ + 458625 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-1-current" + }, + { + "source": "original", + "pair": 1, + "medianMs": 28.0138, + "p95Ms": 32.4755, + "maximumMs": 37.801, + "cpuMsPerOperation": 34.6875, + "allocatedBytesPerOperation": 4469765.44, + "totalAllocatedBytesPerOperation": 4470309.92, + "gen0": 27, + "gen1": 5, + "gen2": 5, + "vertices": [ + 182260 + ], + "indices": [ + 456183 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-1-original" + }, + { + "source": "original", + "pair": 2, + "medianMs": 23.884900000000002, + "p95Ms": 32.8391, + "maximumMs": 37.4046, + "cpuMsPerOperation": 32.5, + "allocatedBytesPerOperation": 4469715.04, + "totalAllocatedBytesPerOperation": 4470363.2, + "gen0": 28, + "gen1": 6, + "gen2": 6, + "vertices": [ + 182260 + ], + "indices": [ + 456183 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-2-original" + }, + { + "source": "current", + "pair": 2, + "medianMs": 28.7754, + "p95Ms": 32.0666, + "maximumMs": 35.6308, + "cpuMsPerOperation": 40.0, + "allocatedBytesPerOperation": 3298463.84, + "totalAllocatedBytesPerOperation": 3298523.2, + "gen0": 19, + "gen1": 1, + "gen2": 0, + "vertices": [ + 184030 + ], + "indices": [ + 458625 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-2-current" + }, + { + "source": "current", + "pair": 3, + "medianMs": 23.92, + "p95Ms": 32.2866, + "maximumMs": 41.3669, + "cpuMsPerOperation": 30.3125, + "allocatedBytesPerOperation": 3298407.52, + "totalAllocatedBytesPerOperation": 3298467.36, + "gen0": 19, + "gen1": 1, + "gen2": 0, + "vertices": [ + 184030 + ], + "indices": [ + 458625 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-3-current" + }, + { + "source": "original", + "pair": 3, + "medianMs": 28.468899999999998, + "p95Ms": 31.4734, + "maximumMs": 33.0214, + "cpuMsPerOperation": 40.0, + "allocatedBytesPerOperation": 4469812.0, + "totalAllocatedBytesPerOperation": 4470461.28, + "gen0": 28, + "gen1": 6, + "gen2": 6, + "vertices": [ + 182260 + ], + "indices": [ + 456183 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-3-original" + }, + { + "source": "original", + "pair": 4, + "medianMs": 33.80315, + "p95Ms": 46.1429, + "maximumMs": 63.8343, + "cpuMsPerOperation": 50.0, + "allocatedBytesPerOperation": 4543119.52, + "totalAllocatedBytesPerOperation": 4543768.32, + "gen0": 28, + "gen1": 6, + "gen2": 6, + "vertices": [ + 182260 + ], + "indices": [ + 456183 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-4-original" + }, + { + "source": "current", + "pair": 4, + "medianMs": 28.86425, + "p95Ms": 31.9508, + "maximumMs": 34.8923, + "cpuMsPerOperation": 36.875, + "allocatedBytesPerOperation": 3298463.84, + "totalAllocatedBytesPerOperation": 3298523.68, + "gen0": 19, + "gen1": 1, + "gen2": 0, + "vertices": [ + 184030 + ], + "indices": [ + 458625 + ], + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-4-current" + } + ], + "comparisons": [ + { + "metric": "medianMs", + "values": { + "original": [ + 32.14015, + 28.0138, + 23.884900000000002, + 28.468899999999998, + 33.80315 + ], + "current": [ + 29.2441, + 22.2517, + 28.7754, + 23.92, + 28.86425 + ] + }, + "medians": { + "original": 28.468899999999998, + "current": 28.7754 + }, + "medianRatio": 1.0107661342728382, + "pairedRatios": [ + 0.9098930776614297, + 0.7943120890418294, + 1.204752793606002, + 0.8402151119291579, + 0.8538923147694815 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "p95Ms", + "values": { + "original": [ + 38.1169, + 32.4755, + 32.8391, + 31.4734, + 46.1429 + ], + "current": [ + 32.9627, + 27.737, + 32.0666, + 32.2866, + 31.9508 + ] + }, + "medians": { + "original": 32.8391, + "current": 32.0666 + }, + "medianRatio": 0.9764762128072937, + "pairedRatios": [ + 0.8647791399615393, + 0.8540900063124509, + 0.9764762128072937, + 1.0258376915109266, + 0.6924315550171316 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "maximumMs", + "values": { + "original": [ + 44.106, + 37.801, + 37.4046, + 33.0214, + 63.8343 + ], + "current": [ + 33.2867, + 28.4866, + 35.6308, + 41.3669, + 34.8923 + ] + }, + "medians": { + "original": 37.801, + "current": 34.8923 + }, + "medianRatio": 0.9230523002036982, + "pairedRatios": [ + 0.7546977735455495, + 0.753593820269305, + 0.9525780251626805, + 1.2527300477871925, + 0.5466073881909882 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 45.3125, + 34.6875, + 32.5, + 40.0, + 50.0 + ], + "current": [ + 36.875, + 28.75, + 40.0, + 30.3125, + 36.875 + ] + }, + "medians": { + "original": 40.0, + "current": 36.875 + }, + "medianRatio": 0.921875, + "pairedRatios": [ + 0.8137931034482758, + 0.8288288288288288, + 1.2307692307692308, + 0.7578125, + 0.7375 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4506232.8, + 4469765.44, + 4469715.04, + 4469812.0, + 4543119.52 + ], + "current": [ + 3335401.28, + 3282931.68, + 3298463.84, + 3298407.52, + 3298463.84 + ] + }, + "medians": { + "original": 4469812.0, + "current": 3298463.84 + }, + "medianRatio": 0.7379424101058388, + "pairedRatios": [ + 0.7401750925961925, + 0.7344751584995923, + 0.7379584180382112, + 0.7379298100233298, + 0.7260350130520009 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 4506881.12, + 4470309.92, + 4470363.2, + 4470461.28, + 4543768.32 + ], + "current": [ + 3335667.04, + 3283197.44, + 3298523.2, + 3298467.36, + 3298523.68 + ] + }, + "medians": { + "original": 4470461.28, + "current": 3298523.2 + }, + "medianRatio": 0.7378485112390908, + "pairedRatios": [ + 0.7401275851713612, + 0.7344451500579628, + 0.7378646996736193, + 0.7378360203580602, + 0.7259445129455896 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + } + ], + "flags": [], + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\measurements.json", + "frozenFiles": 16406, + "scope": "Predeclared five additional alternating independent pairs; all 50 frames pooled per process. No runtime, fixture, source or validation change; preserve initial 150-row results and every new outlier." +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/issue-map.json b/records/architecture/acceptance/editor-ui-2026-10-30/issue-map.json new file mode 100644 index 000000000..b584235fc --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/issue-map.json @@ -0,0 +1,515 @@ +{ + "status": "All applicable local gates verified at dd5979c6; exact delivery-head CI and final issue/PR transitions are recorded in the linked attachment.", + "scope": "Issue bodies must remain open until master merge; final evidence and CI still required", + "issues": [ + { + "id": 13, + "owner": "Editor Interactions History", + "commits": [ + "fa794a5a" + ], + "evidence": [ + "Contracts: EditorHistoryTests non-LIFO Commit/Rollback/Dispose and retained stack" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 16, + "owner": "SDK-evaluated Restore and process-owned loaded-package test", + "commits": [ + "564e00d0", + "4d0fdb0a" + ], + "evidence": [ + "Contracts: NuGetLoadedIdentityTests", + "e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 19, + "owner": "Build SDK capture -> neutral compiler inputs -> Host/IDE", + "commits": [ + "63d4f7dc" + ], + "evidence": [ + "Contracts: frozen multi-SDK/CSharp14 inputs", + "Companion-Canvas", + "Companion-Rendering2D", + "Companion-FlappyBird" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 27, + "owner": "Approved whole-plan traceability and final acceptance", + "commits": [ + "199ae0dc" + ], + "evidence": [ + "All final functional, product, visual, performance and CI gates" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 28, + "owner": "Session/context scopes and actual presentation callers", + "commits": [ + "65d222ae", + "e2e6ca32", + "a25d6302" + ], + "evidence": [ + "Contracts/Visual: extension failure and next-frame scopes", + "Lifecycle: owner and retirement protocols", + "e28/export-label: prior-producer negative and corrected public provider failure" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 29, + "owner": "Generation-owned Interactions menu/shortcut catalog", + "commits": [ + "88df3c79", + "bc88dc04" + ], + "evidence": [ + "Contracts: fresh Query/conflict/last-good/index reuse", + "Lifecycle: actual generation loop", + "Quantitative: menus, shortcut hit/miss" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 30, + "owner": "Shared presentation and domain-owned styles", + "commits": [ + "63d4f7dc", + "f7dce89c" + ], + "evidence": [ + "Architecture dependencies/API", + "Visual: frame-consistent Zoom/DPI/control matrix", + "Matched original/current native UI and viewport readback" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 31, + "owner": "Visible-order navigation and Diagnostics occurrence views", + "commits": [ + "63d4f7dc", + "2bb4da40", + "bc88dc04" + ], + "evidence": [ + "Workflows/Visual: native Hierarchy/Browser/Console input", + "10k search/range/scroll/drag/rename journeys" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 32, + "owner": "Neutral Graph document/controller and Graph.ImGui geometry/intents", + "commits": [ + "63d4f7dc" + ], + "evidence": [ + "Contracts: graph layout/pointer/controller", + "Visual: independent non-Shader native consumer/failure", + "Workflows: Shader document/History/Missing", + "Quantitative: deterministic 2k graph" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 33, + "owner": "EditorStatistics authority and bounded Stats Panel sampler", + "commits": [ + "b7a8924d", + "63d4f7dc" + ], + "evidence": [ + "Contracts: unchanged snapshot reuse/frame ordering", + "Visual: actual native trends, interval/window/reset", + "Products: actual Stats Panel 100 cycles", + "Quantitative: statistics" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 34, + "owner": "Visible tree/grid/Console rows and neutral graph display projection", + "commits": [ + "63d4f7dc", + "973a1aa7", + "bc88dc04" + ], + "evidence": [ + "Visual/Workflows: 10k native range/edit/drag and clipped content counts", + "Contracts: 2k affected-node geometry reuse", + "Quantitative: five paired static and input producers" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 35, + "owner": "Feature edits, unified History and actual Panel composition", + "commits": [ + "fa794a5a", + "a4d975ab", + "f7dce89c", + "8fd31cee", + "2bb4da40" + ], + "evidence": [ + "Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys", + "Contracts: History atomicity and Missing barriers", + "Products: eight FlappyBird journeys in six configurations" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 36, + "owner": "Real ImGui public input/geometry and matched content review", + "commits": [ + "d7f383f0", + "e3f02ebe", + "bc88dc04" + ], + "evidence": [ + "Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases", + "Final current and retained original 6000-preparation-frame capture", + "Physical monitor/IME/manual Mac explicitly unexecuted" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 37, + "owner": "Session/Panel/document/generation retirement", + "commits": [ + "d7f383f0", + "bced2aef", + "e3f02ebe" + ], + "evidence": [ + "Lifecycle: document cycles and ten success/reject/Missing/recovery reloads", + "Products: every discovered actual Panel 100 cycles, strict GC resource series", + "Host exit state/layout publication" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 38, + "owner": "Controlled public-host static/input/command/product measurements", + "commits": [ + "973a1aa7", + "bc88dc04", + "1917b253" + ], + "evidence": [ + "Frozen input/runtime hashes before/after", + "150 five-pair original/current trial rows", + "Ten absolute complete-inspection processes", + "All regression flags require explicit review" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 39, + "owner": "Current per-owner API contracts, design standard and final report", + "commits": [ + "0974c317", + "163c1938", + "5e329bbe" + ], + "evidence": [ + "Final architecture/XML/Wiki/project coverage", + "Eleven quality dimensions and complete requirement mapping", + "English issue verification, individual Fixes and partial Refs #18" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 40, + "owner": "Compiler-tool graph and SDK-owned output identity", + "commits": [ + "6a656b67", + "8dcceb94", + "870a0af0" + ], + "evidence": [ + "Contracts: CompilerToolBuildScope/GeneratorRestoreScope/owned Task inputs", + "Repeated official source, Editor and isolated output builds", + "Final Windows/ARM64 CI" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 41, + "owner": "Tooling consumes current SDK-evaluated source/compiled output", + "commits": [ + "14338d61", + "e053be5b", + "a38437f7" + ], + "evidence": [ + "Contracts: public API graph default/independent/stale output cases", + "Final real complete architecture/XML/Wiki validation" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 42, + "owner": "Scene-owned root placement indices", + "commits": [ + "63d4f7dc" + ], + "evidence": [ + "Scene: creation/reorder/parent/transfer/failure indices", + "Quantitative: five paired 10k Hierarchy producers", + "Original profile retains repeated IndexOf evidence" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 43, + "owner": "Asset source reconciliation", + "commits": [ + "273c42ad" + ], + "evidence": [ + "Workflows: stable source IDs through atomic refresh/recovery", + "Products: Scene/Asset save/close/reopen and installed sources" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 44, + "owner": "Asset source content verification", + "commits": [ + "e23f4ba5" + ], + "evidence": [ + "Workflows: same-length/same-mtime external content mutation", + "Source content/semantic identity and read-only source regressions" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 45, + "owner": "Existing Core Writer/member capture and encoding", + "commits": [ + "f7dce89c", + "6df06585" + ], + "evidence": [ + "Core Serialization: exact single-member/context/converter/failure semantics", + "Quantitative: five paired 10k/2k workloads and complete product" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 46, + "owner": "Scene-owned complete capture inputs and Core canonical output", + "commits": [ + "0b0a821c", + "2434cd96", + "1917b253" + ], + "evidence": [ + "Core97/Scene67 and complete Workflows", + "Ten independent idle/edit absolute inspection processes", + "Failed earlier tails and corrected focused ten-process proof retained" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 47, + "owner": "Standard Hosting layout resource, passive Core state document", + "commits": [ + "bb5b4bb9" + ], + "evidence": [ + "Contracts/Visual: absent/custom/empty/docking/resizing", + "Core IO concurrent creators and final ARM64 CI", + "Default layout focused report" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 48, + "owner": "Core IO atomic create-only publisher and compatible document reader", + "commits": [ + "318e30aa", + "98fd5a7d" + ], + "evidence": [ + "Core IO38 and ten independent publication/held-handle process repeats", + "Final Windows/ARM64 concurrency suites", + "Original deterministic reader-negative evidence retained" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 49, + "owner": "Existing native Context/atlas supplemental font lifetime", + "commits": [ + "e3f02ebe", + "23f0ceb2", + "397ae3ed" + ], + "evidence": [ + "Native font four-face/scale/CJK/Latin/icon/missing/corrupt cases", + "Products and matched visual content", + "Source bytes16,437,364, official Noto2.004/OFL unchanged, one pinned source per Context" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 50, + "owner": "Scene Workspace assigned source and existing recovery transaction", + "commits": [ + "8fd31cee" + ], + "evidence": [ + "Workflows: clean/edited source removal, pipeline reconciliation, exact restoration/rename", + "Save rejects unavailable assignment, explicit SaveToDirectory establishes new source" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 51, + "owner": "Application/context native retirement registration", + "commits": [ + "e3f02ebe" + ], + "evidence": [ + "Lifecycle: ten contexts collected while another remains active", + "Pending/failure preserves registration, successful direct Dispose/replacement", + "Native application shutdown" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 52, + "owner": "Core IO reserved publication name and frozen shader compiler workspace", + "commits": [ + "329568ba" + ], + "evidence": [ + "Contracts: ShaderBuildInputsTests inventory/content/empty directory/failure cases", + "e52: simultaneous independent SDK outputs with deliberately held candidate", + "Authored inventory/hash unchanged; final complete builds/CI" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 53, + "owner": "Core immutable snapshot/Writer proof, Scene complete neutral row inputs", + "commits": [ + "be44532f", + "2434cd96", + "1917b253" + ], + "evidence": [ + "Core97: callback retirement/thread/metadata/identity/converter/effect/mutation/GC guards", + "Scene67: complete writes/restore/references/Missing/Prefab and removed-System GC", + "Ten complete-inspection processes, architecture/Wiki/products/CI" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 54, + "owner": "Scene property reference scope through exact Core allocator", + "commits": [ + "e5268920", + "163c1938" + ], + "evidence": [ + "Scene67: current/stale/foreign/wrong-kind/alias/throw/nested scopes and canonical graph behavior", + "Actual SceneEdits History continuous-input comparison", + "Formal companion SDK/IDE and complete products/CI" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + }, + { + "id": 55, + "owner": "Core atomic directory publication and Compiler-owned complete cache trees", + "commits": [ + "dd5979c6" + ], + "evidence": [ + "Public Compiler red/green temporary-reader regression; persistent failure, cancellation and exact whole-tree replacement preservation", + "Core IO41 and Compiler17 complete focused suites; five independent cold/warm formal publications", + "Corrected-source full matrices, formal companions, products, quantitative results and exact-head CI" + ], + "localVerification": "Passed; see final report and portable candidate results.", + "remoteVerificationAuthority": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "mergePolicy": "Keep Open; Verified on branch / awaiting merge after exact-head verification; close through individual PR Fixes on master merge only." + } + ], + "deliveryAttachment": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338" +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/local-acceptance.json b/records/architecture/acceptance/editor-ui-2026-10-30/local-acceptance.json new file mode 100644 index 000000000..807590fe9 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/local-acceptance.json @@ -0,0 +1,3556 @@ +{ + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "preparation": { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "stages": [ + { + "exitCode": 0, + "finished": "2026-10-30T08:59:04.4094977+08:00", + "stage": "neutral-source" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:14:53.8202665+08:00", + "stage": "selected-player-contract" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:24:39.3832018+08:00", + "stage": "release-suites" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:39:32.1522005+08:00", + "stage": "debug-suites" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:45:53.8268306+08:00", + "stage": "editor-Debug" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:51:46.2840053+08:00", + "stage": "editor-Release" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:52:39.9657629+08:00", + "stage": "build-entry" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:52:50.2154870+08:00", + "stage": "menu-harness" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:52:56.7494542+08:00", + "stage": "Inno.Core.Serialization.Tests" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:53:05.1308119+08:00", + "stage": "Inno.Scene.Tests" + }, + { + "exitCode": 0, + "finished": "2026-10-30T09:53:15.3681965+08:00", + "stage": "Inno.Core.IO.Tests" + } + ] + }, + "matrices": { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "stages": [ + { + "stage": "Contracts", + "status": "Passed", + "finished": "2026-10-30T11:05:25.0697943+08:00" + }, + { + "stage": "Workflows", + "status": "Passed", + "finished": "2026-10-30T11:22:04.9198214+08:00" + }, + { + "stage": "Visual", + "status": "Passed", + "finished": "2026-10-30T11:24:19.1158541+08:00" + }, + { + "stage": "Lifecycle", + "status": "Passed", + "finished": "2026-10-30T11:28:47.7293827+08:00" + }, + { + "stage": "Companion-Canvas", + "status": "Passed", + "finished": "2026-10-30T11:29:56.9465602+08:00" + }, + { + "stage": "Companion-Rendering2D", + "status": "Passed", + "finished": "2026-10-30T11:30:49.5395253+08:00" + }, + { + "stage": "Companion-FlappyBird", + "status": "Passed", + "finished": "2026-10-30T11:31:49.3277537+08:00" + }, + { + "stage": "Products-Debug-D3D11", + "status": "Passed", + "finished": "2026-10-30T11:38:22.9676779+08:00" + }, + { + "stage": "Products-Debug-D3D12", + "status": "Passed", + "finished": "2026-10-30T11:46:23.1405702+08:00" + }, + { + "stage": "Products-Debug-Vulkan", + "status": "Passed", + "finished": "2026-10-30T11:52:39.9804439+08:00" + }, + { + "stage": "Products-Release-D3D11", + "status": "Passed", + "finished": "2026-10-30T11:56:30.4340473+08:00" + }, + { + "stage": "Products-Release-D3D12", + "status": "Passed", + "finished": "2026-10-30T12:01:32.7484318+08:00" + }, + { + "stage": "Products-Release-Vulkan", + "status": "Passed", + "finished": "2026-10-30T12:05:09.6859416+08:00" + }, + { + "stage": "Visual-capture-data", + "status": "Passed", + "finished": "2026-10-30T12:10:10.6942580+08:00" + } + ] + }, + "profiles": { + "scope": "Actual TRX executions; overlapping profiles are not added into a distinct-test claim.", + "profiles": [ + { + "profile": "Contracts", + "executions": 824, + "outcomes": { + "Passed": 822, + "NotExecuted": 2 + }, + "uniqueNames": 822 + }, + { + "profile": "Workflows", + "executions": 704, + "outcomes": { + "Passed": 704 + }, + "uniqueNames": 704 + }, + { + "profile": "Visual", + "executions": 524, + "outcomes": { + "Passed": 524 + }, + "uniqueNames": 524 + }, + { + "profile": "Lifecycle", + "executions": 15, + "outcomes": { + "Passed": 15 + }, + "uniqueNames": 15 + } + ], + "uniqueNamesAcrossProfiles": 1526, + "unexecuted": [ + { + "profile": "Contracts", + "name": "Inno.Build.Tests.BuildCacheWorkspaceLinkTests.WorkspaceLibraryAliasesAreCountedOnceAndRetiredWithoutFollowingLinks", + "outcome": "NotExecuted", + "duration": "00:00:00.0010000", + "evidence": "Contracts\\fc2c983e1ebe4783aa2c5833d17daff3\\Contracts\\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx" + }, + { + "profile": "Contracts", + "name": "Inno.Build.Tests.BuildCacheWorkspaceLinkTests.FileAliasesCannotEscapeCycleDangleOrEnterImmutableArtifacts", + "outcome": "NotExecuted", + "duration": "00:00:00.0010000", + "evidence": "Contracts\\fc2c983e1ebe4783aa2c5833d17daff3\\Contracts\\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx" + } + ] + }, + "core": [ + { + "suite": "Inno.Core.IO.Tests", + "counts": { + "total": 41, + "executed": 41, + "passed": 41, + "failed": 0 + } + }, + { + "suite": "Inno.Core.Serialization.Tests", + "counts": { + "total": 97, + "executed": 97, + "passed": 97, + "failed": 0 + } + }, + { + "suite": "Inno.Scene.Tests", + "counts": { + "total": 67, + "executed": 67, + "passed": 67, + "failed": 0 + } + } + ], + "products": [ + { + "configuration": "Debug", + "api": "D3D11", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "D3D11", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "D3D11", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "49033328", + "private-bytes": "352280576", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "49032616", + "private-bytes": "341798912", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "49037944", + "private-bytes": "345079808", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "49757976", + "private-bytes": "344633344", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "49747688", + "private-bytes": "340570112", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "49747800", + "private-bytes": "349523968", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "49753112", + "private-bytes": "345624576", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "49748000", + "private-bytes": "349765632", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "49748152", + "private-bytes": "345661440", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "49751512", + "private-bytes": "349761536", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "49754832", + "private-bytes": "354324480", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": 721504 + }, + { + "configuration": "Debug", + "api": "D3D12", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "D3D12", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "D3D12", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "48972520", + "private-bytes": "903086080", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "49697456", + "private-bytes": "898969600", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "49697584", + "private-bytes": "898801664", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "49692448", + "private-bytes": "898183168", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "49697856", + "private-bytes": "894230528", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "49697944", + "private-bytes": "896512000", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "49698032", + "private-bytes": "900591616", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "49692920", + "private-bytes": "900878336", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "49698296", + "private-bytes": "901148672", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "48330144", + "private-bytes": "896540672", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "48322936", + "private-bytes": "893353984", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": -649584 + }, + { + "configuration": "Debug", + "api": "Vulkan", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "Vulkan", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "graphicsApi": "Vulkan", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "48714744", + "private-bytes": "529149952", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "48714008", + "private-bytes": "525459456", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "48718096", + "private-bytes": "534011904", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "49108104", + "private-bytes": "533610496", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "49097168", + "private-bytes": "533614592", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "49102480", + "private-bytes": "537378816", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "49089288", + "private-bytes": "533278720", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "49093184", + "private-bytes": "537313280", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "49093336", + "private-bytes": "533258240", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "49098648", + "private-bytes": "537358336", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "49093536", + "private-bytes": "537530368", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": 378792 + }, + { + "configuration": "Release", + "api": "D3D11", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "D3D11", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "D3D11", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "48950816", + "private-bytes": "346898432", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "49020000", + "private-bytes": "342114304", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "48964704", + "private-bytes": "342638592", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "49041840", + "private-bytes": "352235520", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "49049112", + "private-bytes": "351932416", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "49044288", + "private-bytes": "348078080", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "49044376", + "private-bytes": "348340224", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "49044464", + "private-bytes": "348602368", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "49775920", + "private-bytes": "348725248", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "49770784", + "private-bytes": "345067520", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "49773832", + "private-bytes": "350142464", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": 823016 + }, + { + "configuration": "Release", + "api": "D3D12", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "D3D12", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "D3D12", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "47885648", + "private-bytes": "932569088", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "47882552", + "private-bytes": "911589376", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "47882656", + "private-bytes": "916004864", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "47885096", + "private-bytes": "911597568", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "48619368", + "private-bytes": "917913600", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "48614256", + "private-bytes": "918040576", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "48619568", + "private-bytes": "914386944", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "48614432", + "private-bytes": "918560768", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "48619808", + "private-bytes": "914391040", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "48614672", + "private-bytes": "918495232", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "48614760", + "private-bytes": "914432000", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": 729112 + }, + { + "configuration": "Release", + "api": "Vulkan", + "results": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "Vulkan", + "frames": 30000, + "exitCode": 0, + "scenario": "Workflows", + "status": "Passed", + "diagnostic": "Eight actual-product feature workflows completed.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + }, + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "workingChanges": [], + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\release\\InnoEngine.Editor.dll", + "graphicsApi": "Vulkan", + "frames": 30000, + "exitCode": 0, + "scenario": "Lifecycle", + "status": "Passed", + "diagnostic": "Every actual product panel completed 100 cycles and eleven full-GC resource observations.", + "limitation": "Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence." + } + ], + "panels": [ + { + "panel": "asset.file-browser", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.console", + "completed-open-close-cycles": "100" + }, + { + "panel": "diagnostics.stats", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.game-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.scene-view", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering.shader-editor", + "completed-open-close-cycles": "100" + }, + { + "panel": "rendering2d.animation", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.hierarchy", + "completed-open-close-cycles": "100" + }, + { + "panel": "scene.inspector", + "completed-open-close-cycles": "100" + } + ], + "resources": [ + { + "completed-cycles": "0", + "managed-bytes-after-full-gc": "48759336", + "private-bytes": "548675584", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "10", + "managed-bytes-after-full-gc": "48756264", + "private-bytes": "547110912", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "20", + "managed-bytes-after-full-gc": "48756400", + "private-bytes": "542875648", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "30", + "managed-bytes-after-full-gc": "48758840", + "private-bytes": "542879744", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "40", + "managed-bytes-after-full-gc": "48764184", + "private-bytes": "542879744", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "50", + "managed-bytes-after-full-gc": "48746464", + "private-bytes": "542928896", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "60", + "managed-bytes-after-full-gc": "49154224", + "private-bytes": "541388800", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "70", + "managed-bytes-after-full-gc": "49154312", + "private-bytes": "550199296", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "80", + "managed-bytes-after-full-gc": "49157672", + "private-bytes": "546099200", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "90", + "managed-bytes-after-full-gc": "49153424", + "private-bytes": "549056512", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + }, + { + "completed-cycles": "100", + "managed-bytes-after-full-gc": "49153512", + "private-bytes": "549122048", + "selection-count": "0", + "scene-count": "1", + "history-resident-bytes": "0", + "history-disk-bytes": "0", + "pending-events": "0" + } + ], + "finalLiveManagedGrowthBytes": 394176 + } + ], + "visual": { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "status": "Passed", + "recorded": "2026-10-30T12:14:37.0025039+08:00", + "reviewMethod": "assistant-image-inspection", + "original": { + "sha256": "0EFE2EC81D6E10AB0652E72368AAFECBFC12F612353E0DFA71757450B91274FE", + "path": "D:/InnoEngine-Verification/editor-ui-closure-2026-10-27/e09/visual-capture/original/Captures-cold-settled/native-ui-replay.png" + }, + "current": { + "sha256": "707EB8D5A0DDF2AAC4D60B9F7ECAE5C68D1699E00F60FBECDA2A5B1852B39E67", + "path": "D:/InnoEngine-Verification/editor-ui-closure-2026-10-27/e09/visual-capture/current/Captures-dd5979c6/native-ui-replay.png" + }, + "size": [ + 1600, + 900 + ], + "observations": [ + "Both Scene and Game contain actual FlappyBird title, bird, pipes, scenery and ground; no blank placeholder viewport was accepted.", + "Major docking boundaries, tabs, scene tool column, hierarchy rows and empty Inspector remain recognizable and aligned.", + "Main menu is intentionally organized as File/Edit/View/Window/Tools; Stats adds the planned numeric trends within its existing scrolling owner.", + "Completed replay reports 1827 triangles, zero unavailable GPU commands and zero unexecuted callbacks." + ], + "limits": [ + "Native triangle/atlas replay with actual GPU viewport texture readback; not a physical screenshot or final-backbuffer capture.", + "One matching full-product layout/content pair; other scales and inputs are separate real-context simulated-metric contracts.", + "No physical monitor/DPI, OS IME, manual usability or local Mac acceptance is inferred.", + "Single screenshot FPS/delta values are not performance evidence; controlled repeated measurements remain required." + ] + }, + "performanceReview": { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "status": "Passed", + "unresolved": [], + "reviewedComparisons": 68, + "initialTrials": 150, + "followupTrials": 10, + "initialSummarySha256": "bda0af282ada8860b38cf871ded6d7b9cc61bef7314da24cf306830984e0bb75", + "followupSummarySha256": "c34fff55878a619d7ba013e2d6aa31d00aa8f3002bae502ac55e7248e2d5cb41", + "followupEvidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6-graph-tail\\graph-tail-summary.json", + "decision": "The only initial flags concern the same graph tail, where ten measured frames make P95 the maximum. A predeclared additional five alternating process pairs used the unchanged existing fixture with its normal five repetitions (50 measured frames/process). No pair exceeds +10% for P95; median P95 ratio 0.976476, CPU ratio 0.921875, frame median ratio 1.010766 and allocation ratio 0.737942. The initial +19.98% observation remains in the report, not erased or called a passing initial trial. No persistent >10% CPU/P95 or hot allocation regression is established under this repeatability criterion.", + "initialFlags": [ + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "maximumMs", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "p95Ms", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + } + ], + "followupComparisons": [ + { + "metric": "medianMs", + "values": { + "original": [ + 32.14015, + 28.0138, + 23.884900000000002, + 28.468899999999998, + 33.80315 + ], + "current": [ + 29.2441, + 22.2517, + 28.7754, + 23.92, + 28.86425 + ] + }, + "medians": { + "original": 28.468899999999998, + "current": 28.7754 + }, + "medianRatio": 1.0107661342728382, + "pairedRatios": [ + 0.9098930776614297, + 0.7943120890418294, + 1.204752793606002, + 0.8402151119291579, + 0.8538923147694815 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "p95Ms", + "values": { + "original": [ + 38.1169, + 32.4755, + 32.8391, + 31.4734, + 46.1429 + ], + "current": [ + 32.9627, + 27.737, + 32.0666, + 32.2866, + 31.9508 + ] + }, + "medians": { + "original": 32.8391, + "current": 32.0666 + }, + "medianRatio": 0.9764762128072937, + "pairedRatios": [ + 0.8647791399615393, + 0.8540900063124509, + 0.9764762128072937, + 1.0258376915109266, + 0.6924315550171316 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "maximumMs", + "values": { + "original": [ + 44.106, + 37.801, + 37.4046, + 33.0214, + 63.8343 + ], + "current": [ + 33.2867, + 28.4866, + 35.6308, + 41.3669, + 34.8923 + ] + }, + "medians": { + "original": 37.801, + "current": 34.8923 + }, + "medianRatio": 0.9230523002036982, + "pairedRatios": [ + 0.7546977735455495, + 0.753593820269305, + 0.9525780251626805, + 1.2527300477871925, + 0.5466073881909882 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 45.3125, + 34.6875, + 32.5, + 40.0, + 50.0 + ], + "current": [ + 36.875, + 28.75, + 40.0, + 30.3125, + 36.875 + ] + }, + "medians": { + "original": 40.0, + "current": 36.875 + }, + "medianRatio": 0.921875, + "pairedRatios": [ + 0.8137931034482758, + 0.8288288288288288, + 1.2307692307692308, + 0.7578125, + 0.7375 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4506232.8, + 4469765.44, + 4469715.04, + 4469812.0, + 4543119.52 + ], + "current": [ + 3335401.28, + 3282931.68, + 3298463.84, + 3298407.52, + 3298463.84 + ] + }, + "medians": { + "original": 4469812.0, + "current": 3298463.84 + }, + "medianRatio": 0.7379424101058388, + "pairedRatios": [ + 0.7401750925961925, + 0.7344751584995923, + 0.7379584180382112, + 0.7379298100233298, + 0.7260350130520009 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 4506881.12, + 4470309.92, + 4470363.2, + 4470461.28, + 4543768.32 + ], + "current": [ + 3335667.04, + 3283197.44, + 3298523.2, + 3298467.36, + 3298523.68 + ] + }, + "medians": { + "original": 4470461.28, + "current": 3298523.2 + }, + "medianRatio": 0.7378485112390908, + "pairedRatios": [ + 0.7401275851713612, + 0.7344451500579628, + 0.7378646996736193, + 0.7378360203580602, + 0.7259445129455896 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + } + ], + "productionChangeForFollowup": false, + "otherReview": [ + "Every other initial comparison was reviewed, including all paired values and scope; no repeatability flag remains.", + "Process CPU zero for very short command batches is quantization, not a claimed 100% CPU gain.", + "Product first-application wall time, source-activation CPU-frame interval and complete-run main CPU have different scopes; child CPU/GPU completion/physical display are not inferred.", + "Current source-activation CPU-frame median is 56.65s versus 52.72s (+7.46%), within the stated repeatable >10% gate; retain the cost, not claim startup uniformly improved.", + "2k graph P95 around32ms is not a 60FPS guarantee; 10k asset search around73ms is an explicit search operation, not a steady-frame guarantee.", + "Ten absolute complete-inspection processes separately satisfy median<16ms/P95<25ms/<8MiB at the unchanged100ms policy; worst observedP95 24.5695ms has limited margin.", + "All initial outliers, GC counts, counts of work, raw timings and all followup outliers remain preserved. No best-run selection, fixture validation removal, timeout relaxation or source/API change was made." + ] + }, + "inspections": [ + { + "p95": 12.3807, + "maximum": 22.1401, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\0-idle", + "median": 8.9222, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 9.6019, + 9.5651, + 9.551, + 9.4154, + 8.9608, + 8.6895, + 8.8458, + 21.0278, + 9.2889, + 10.043 + ], + "median": 9.4832, + "p95": 21.0278, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 9.6608, + 8.9665, + 8.7771, + 9.8036, + 8.6855, + 22.1401, + 9.9554, + 9.1504, + 8.7155, + 8.8836 + ], + "median": 9.05845, + "p95": 22.1401, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.6657, + 8.5388, + 9.2778, + 9.262, + 10.6916, + 12.3807, + 12.1319, + 11.3081, + 9.0471, + 8.8326 + ], + "median": 9.2699, + "p95": 12.3807, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 187.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 9.2851, + 8.5787, + 8.4439, + 9.5828, + 8.44, + 8.5284, + 10.0074, + 8.6177, + 8.4987, + 8.2943 + ], + "median": 8.55355, + "p95": 10.0074, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.4635, + 9.4812, + 7.8818, + 8.8301, + 8.5445, + 8.1611, + 8.603, + 8.5867, + 7.9488, + 7.9857 + ], + "median": 8.504, + "p95": 9.4812, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 0, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 17.7009, + "maximum": 23.4513, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\0-edit", + "median": 12.3006, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.9998, + 14.9489, + 15.4932, + 14.1631, + 11.9199, + 12.2311, + 23.4513, + 12.3617, + 12.3436, + 12.7973 + ], + "median": 13.4802, + "p95": 23.4513, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 265.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.4663, + 12.5163, + 11.6394, + 23.1995, + 12.2105, + 11.7747, + 11.8486, + 11.6241, + 11.4952, + 11.5496 + ], + "median": 11.81165, + "p95": 23.1995, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 11.5752, + 13.0555, + 11.8782, + 12.0658, + 12.5633, + 11.6064, + 12.4525, + 13.5673, + 14.6442, + 12.2771 + ], + "median": 12.3648, + "p95": 14.6442, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 218.75, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.5485, + 11.7938, + 11.9887, + 12.1073, + 13.1001, + 11.9378, + 12.8684, + 17.7009, + 12.5765, + 12.0066 + ], + "median": 12.3279, + "p95": 17.7009, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.1162, + 12.5564, + 12.2221, + 13.4516, + 12.1862, + 12.0622, + 12.0893, + 12.05, + 13.6223, + 12.3241 + ], + "median": 12.204149999999998, + "p95": 13.6223, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 0, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 15.8753, + "maximum": 24.4228, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\1-edit", + "median": 12.2998, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.4602, + 13.9571, + 15.8753, + 11.9225, + 12.6494, + 12.5547, + 24.4228, + 11.9308, + 12.06, + 11.934 + ], + "median": 12.60205, + "p95": 24.4228, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 234.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.0769, + 12.3585, + 11.8625, + 23.7502, + 12.1167, + 12.445, + 12.9273, + 13.484, + 12.0659, + 11.6213 + ], + "median": 12.2376, + "p95": 23.7502, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 203.125, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 11.6032, + 13.2885, + 12.0036, + 12.1864, + 13.6702, + 11.7924, + 12.1898, + 13.8006, + 13.4968, + 12.0085 + ], + "median": 12.1881, + "p95": 13.8006, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 171.875, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 11.8119, + 12.1729, + 12.5391, + 12.3134, + 12.2831, + 12.6453, + 13.4729, + 11.8185, + 12.2862, + 12.515 + ], + "median": 12.2998, + "p95": 13.4729, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.5247, + 12.0979, + 12.1579, + 15.2212, + 11.9826, + 12.1325, + 12.3608, + 12.749, + 13.6585, + 11.7756 + ], + "median": 12.25935, + "p95": 15.2212, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 1, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 10.1327, + "maximum": 19.9138, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\1-idle", + "median": 8.53035, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.3233, + 8.9338, + 8.7193, + 9.1145, + 8.2816, + 8.9857, + 8.3798, + 19.9138, + 8.5373, + 8.9224 + ], + "median": 8.82085, + "p95": 19.9138, + "allocatedBytes": 9647808, + "totalAllocatedBytes": 9648464, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.0052, + 8.5423, + 7.9935, + 8.3199, + 8.3132, + 7.8977, + 19.8211, + 8.2266, + 8.339, + 8.6044 + ], + "median": 8.31655, + "p95": 19.8211, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.4134, + 8.2402, + 8.4949, + 9.1222, + 9.6246, + 10.1327, + 9.5116, + 8.4893, + 9.144, + 8.283 + ], + "median": 8.80855, + "p95": 10.1327, + "allocatedBytes": 9647064, + "totalAllocatedBytes": 9647544, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.1912, + 8.2234, + 8.4287, + 9.6206, + 8.785, + 8.6567, + 8.5234, + 8.9007, + 8.7537, + 8.4888 + ], + "median": 8.590050000000002, + "p95": 9.6206, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 93.75, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.2555, + 8.0363, + 9.6566, + 9.3987, + 8.2011, + 8.0976, + 8.5726, + 8.7949, + 8.6666, + 8.2265 + ], + "median": 8.41405, + "p95": 9.6566, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 62.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 1, + "allocatedBytesPerOperation": 964714.88, + "scenario": "idle" + }, + { + "p95": 10.051, + "maximum": 23.1796, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\2-idle", + "median": 8.54255, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.8773, + 8.4701, + 8.4841, + 8.6071, + 8.9514, + 8.5858, + 8.5847, + 20.1636, + 8.4375, + 8.6228 + ], + "median": 8.59645, + "p95": 20.1636, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.5586, + 8.1269, + 8.4946, + 8.3018, + 8.9074, + 23.1796, + 8.4899, + 7.92, + 7.8139, + 9.9828 + ], + "median": 8.49225, + "p95": 23.1796, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.3356, + 8.473, + 8.4143, + 8.7906, + 10.051, + 9.7758, + 9.583, + 8.6287, + 8.9725, + 8.1879 + ], + "median": 8.70965, + "p95": 10.051, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.0464, + 8.4645, + 8.5879, + 8.8999, + 9.1305, + 8.0816, + 8.5265, + 8.2143, + 8.6434, + 8.4977 + ], + "median": 8.5121, + "p95": 9.1305, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 9.1992, + 9.5064, + 9.276, + 8.1418, + 8.7612, + 8.0167, + 8.4035, + 8.1182, + 8.0503, + 8.1969 + ], + "median": 8.3002, + "p95": 9.5064, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 78.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 2, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 19.1123, + "maximum": 25.045, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\2-edit", + "median": 12.69685, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.1411, + 12.8804, + 19.1123, + 13.8906, + 11.9989, + 24.324, + 12.0374, + 11.8256, + 12.2041, + 12.5018 + ], + "median": 12.691099999999999, + "p95": 24.324, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179944, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 12.2034, + 12.6992, + 11.4427, + 25.045, + 12.5896, + 12.1224, + 12.6114, + 15.2748, + 14.2026, + 14.2415 + ], + "median": 12.6553, + "p95": 25.045, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 14.3339, + 12.8216, + 13.6663, + 13.5159, + 13.5648, + 13.01, + 13.2676, + 13.3107, + 15.2481, + 11.8418 + ], + "median": 13.4133, + "p95": 15.2481, + "allocatedBytes": 11179744, + "totalAllocatedBytes": 11180224, + "cpuMilliseconds": 265.625, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.1586, + 11.9246, + 15.9201, + 11.834, + 12.1561, + 13.4209, + 12.3512, + 11.7068, + 12.8034, + 13.22 + ], + "median": 12.2549, + "p95": 15.9201, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 12.3788, + 11.6936, + 12.6945, + 13.2038, + 12.024, + 11.947, + 12.0669, + 12.2406, + 12.0875, + 12.7541 + ], + "median": 12.16405, + "p95": 13.2038, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 2, + "allocatedBytesPerOperation": 1117912.32, + "scenario": "edit" + }, + { + "p95": 17.2107, + "maximum": 24.2276, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\3-edit", + "median": 13.011299999999999, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 12.8744, + 12.2026, + 13.354, + 14.1843, + 12.9206, + 12.7451, + 24.2276, + 12.5768, + 12.4068, + 12.8823 + ], + "median": 12.878350000000001, + "p95": 24.2276, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 13.3007, + 12.5622, + 11.9551, + 24.0171, + 12.5052, + 12.6458, + 12.3361, + 13.0841, + 12.4501, + 12.533 + ], + "median": 12.5476, + "p95": 24.0171, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 187.5, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 12.9385, + 13.6924, + 12.8055, + 14.0937, + 13.9949, + 13.2718, + 15.3084, + 17.2107, + 15.6573, + 13.7986 + ], + "median": 13.89675, + "p95": 17.2107, + "allocatedBytes": 11179680, + "totalAllocatedBytes": 11180160, + "cpuMilliseconds": 218.75, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 14.6498, + 13.8284, + 14.1835, + 13.9772, + 14.0462, + 13.9758, + 15.6751, + 16.3196, + 13.8047, + 12.4162 + ], + "median": 14.011700000000001, + "p95": 16.3196, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 13.7084, + 12.1913, + 12.161, + 13.557, + 12.4667, + 12.2528, + 12.4945, + 12.0847, + 12.3153, + 12.5764 + ], + "median": 12.391, + "p95": 13.7084, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 3, + "allocatedBytesPerOperation": 1117912.0, + "scenario": "edit" + }, + { + "p95": 10.351, + "maximum": 20.6685, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\3-idle", + "median": 8.44505, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 8.3865, + 8.2209, + 8.4115, + 8.5456, + 8.2323, + 8.3231, + 9.1648, + 20.6685, + 8.5411, + 8.4786 + ], + "median": 8.44505, + "p95": 20.6685, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9648040, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 7.9456, + 8.5688, + 8.3446, + 8.1275, + 7.6609, + 20.4989, + 8.3169, + 8.6972, + 8.3068, + 7.7247 + ], + "median": 8.31185, + "p95": 20.4989, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 7.8035, + 8.5878, + 8.3781, + 8.0937, + 10.2531, + 9.2406, + 9.8069, + 8.3478, + 8.5085, + 8.5664 + ], + "median": 8.53745, + "p95": 10.2531, + "allocatedBytes": 9647128, + "totalAllocatedBytes": 9647608, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.3166, + 8.5059, + 8.8209, + 9.0184, + 8.0193, + 8.7592, + 8.0322, + 8.7098, + 8.0016, + 7.9664 + ], + "median": 8.411249999999999, + "p95": 9.0184, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 62.5, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 7.9625, + 9.545, + 8.1059, + 8.5349, + 8.238, + 8.897, + 8.5141, + 7.8492, + 10.351, + 9.632 + ], + "median": 8.5245, + "p95": 10.351, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 3, + "allocatedBytesPerOperation": 964707.68, + "scenario": "idle" + }, + { + "p95": 10.1595, + "maximum": 20.0675, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\4-idle", + "median": 8.76125, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-inspections", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 9.5737, + 8.5151, + 9.0708, + 8.7022, + 8.3397, + 9.6961, + 9.1659, + 8.7308, + 19.7437, + 8.9403 + ], + "median": 9.00555, + "p95": 19.7437, + "allocatedBytes": 9647808, + "totalAllocatedBytes": 9648464, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 8.5027, + 8.6419, + 9.027, + 7.8713, + 8.9598, + 8.6629, + 20.0675, + 8.4933, + 8.6182, + 9.2698 + ], + "median": 8.6524, + "p95": 20.0675, + "allocatedBytes": 9647384, + "totalAllocatedBytes": 9647864, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 8.3681, + 8.3748, + 9.093, + 8.7078, + 8.5405, + 10.0968, + 10.1595, + 8.9771, + 8.7277, + 8.7153 + ], + "median": 8.721499999999999, + "p95": 10.1595, + "allocatedBytes": 9647064, + "totalAllocatedBytes": 9647544, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 8.3283, + 9.1009, + 8.6986, + 8.7188, + 9.3451, + 8.8962, + 9.0283, + 9.0802, + 8.5968, + 8.5217 + ], + "median": 8.807500000000001, + "p95": 9.3451, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 93.75, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 8.8543, + 8.6611, + 9.5965, + 8.8972, + 8.7917, + 8.71, + 8.5038, + 9.2104, + 8.5303, + 8.8305 + ], + "median": 8.8111, + "p95": 9.5965, + "allocatedBytes": 9646744, + "totalAllocatedBytes": 9647224, + "cpuMilliseconds": 109.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 4, + "allocatedBytesPerOperation": 964714.88, + "scenario": "idle" + }, + { + "p95": 24.5695, + "maximum": 42.0022, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\4-edit", + "median": 12.1435, + "passed": true, + "measurement": { + "workload": "10000-hierarchy-dirty-edits", + "configuration": "Release", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 42.0022, + 40.6075, + 12.9553, + 13.1752, + 12.6073, + 24.1638, + 11.7008, + 12.0114, + 12.389, + 12.5688 + ], + "median": 12.7813, + "p95": 42.0022, + "allocatedBytes": 11179336, + "totalAllocatedBytes": 11179992, + "cpuMilliseconds": 421.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 11.9311, + 12.3971, + 13.1053, + 24.5695, + 11.9685, + 12.0172, + 11.8971, + 12.1158, + 12.0578, + 12.1108 + ], + "median": 12.084299999999999, + "p95": 24.5695, + "allocatedBytes": 11179288, + "totalAllocatedBytes": 11179768, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 12.6066, + 11.7774, + 11.5886, + 12.5307, + 12.8895, + 11.9035, + 12.567, + 12.9019, + 15.1707, + 12.0228 + ], + "median": 12.54885, + "p95": 15.1707, + "allocatedBytes": 11179744, + "totalAllocatedBytes": 11180224, + "cpuMilliseconds": 265.625, + "gen0Collections": 2, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 12.0508, + 12.3723, + 12.0221, + 12.1205, + 11.5312, + 12.7472, + 11.6468, + 12.2152, + 12.1725, + 11.812 + ], + "median": 12.085650000000001, + "p95": 12.7472, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 11.9903, + 12.6222, + 12.0284, + 12.7907, + 12.0501, + 12.1665, + 11.8014, + 12.1062, + 12.2728, + 11.846 + ], + "median": 12.07815, + "p95": 12.7907, + "allocatedBytes": 11178648, + "totalAllocatedBytes": 11179128, + "cpuMilliseconds": 140.625, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "complete-authoritative-dirty-inspection-after-each-owned-write", + "preparationExcludedFromFrameTiming": true, + "supportsVertexOffsets": true + }, + "pair": 4, + "allocatedBytesPerOperation": 1117913.28, + "scenario": "edit" + } + ], + "performanceSummarySha256": "bda0af282ada8860b38cf871ded6d7b9cc61bef7314da24cf306830984e0bb75", + "sourceInventory": { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "changes": [], + "trackedProjects": 241, + "editorProjects": 30, + "forbiddenChanges": [], + "importsRequiringReview": [ + { + "path": "tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs", + "line": 551, + "text": "global using InnoEngine.Scene;" + }, + { + "path": "tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs", + "line": 129, + "text": "[InlineData(\"Directory.Build.props\", \"true\", \"implicit usings\", true)]" + }, + { + "path": "tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs", + "line": 130, + "text": "[InlineData(\"build/Probe.targets\", \"\", \"MSBuild Using\", true)]" + } + ], + "repositories": [ + { + "name": "InnoEngine", + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "branch": "editor-ui", + "changes": [] + }, + { + "name": "InnoEngine.Canvas", + "revision": "eed5fd9bdb13d5f29351aff2b8c52eed58138266", + "branch": "main", + "changes": [] + }, + { + "name": "InnoEngine.Rendering2D", + "revision": "067d4eb023b4dea0df114872247d9b44f69fe20f", + "branch": "main", + "changes": [] + }, + { + "name": "InnoEngine.Samples", + "revision": "3e331fd0fae08491fe11938faa5955c6e79e5089", + "branch": "main", + "changes": [] + }, + { + "name": "BindGen-CS", + "revision": "2894538911645ad28c74060840a02c154c15ce4d", + "branch": "main", + "changes": [] + } + ], + "commits": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(scripting): reuse atomic directory publication for script caches" + }, + { + "revision": "5e329bbe96e4812fcec1207151dd98c520237bbd", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "refactor(editor): remove an unreachable hierarchy mutation path" + }, + { + "revision": "4d0fdb0a47a90fdcf266b40c415f889afc268b81", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): own physical package probes through process exit" + }, + { + "revision": "1917b25381591efcd4e1d037bae71742291c0d32", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): reuse frozen graph identity projections during capture" + }, + { + "revision": "98fd5a7dff2606f1ebbdf57649b99e8f8252c03d", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(io): read published documents through rename-compatible handles" + }, + { + "revision": "163c193890feb07f508fcde6407531d0f7b9558a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(scene): complete reference scope failure and documentation contracts" + }, + { + "revision": "2bb4da4000bdd1c96d3ec0bdb9cc60ef2ec24e71", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): keep inline rename in the keyboard-focused browser view" + }, + { + "revision": "bc88dc0479398135b6debec254968baa2b31d467", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): cover large-view editing and shortcut dispatch workloads" + }, + { + "revision": "e52689204e8bf60cebd494ead307bcc24f502db9", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): resolve property references through the owning identity scope" + }, + { + "revision": "973a1aa76b1efff7b51d892383dbd681396ff1e4", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): cover large-view input and continuous edit workloads" + }, + { + "revision": "2434cd96cb10b0e6733f12ba6e4e83f7638f9b1c", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse proven array elements and bounded canonical blocks" + }, + { + "revision": "a25d630220603933cad8fd7bc16753f369940d42", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): retire export form tables when target presentation fails" + }, + { + "revision": "be44532f00dfa8644bceee8116830bd284e5d2b0", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse complete neutral capture keys within owned snapshots" + }, + { + "revision": "bced2aefd471cdfb00bc7ca5096f8c90aa652592", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): separate cold preparation from panel lifecycle acceptance" + }, + { + "revision": "329568bafab6fb60014bd88826d992e9d54bccf6", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): freeze shader inputs in owned compilation workspaces" + }, + { + "revision": "564e00d0733634c928508787baece2e837471248", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): verify loaded assemblies retire with their collectible contexts" + }, + { + "revision": "06f7e3c29893ab94fab96ba7ffb5f4b81079b06f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): record verified owner fixes and remaining acceptance gates" + }, + { + "revision": "d7f383f01dddbeaf184864c5d8d6e2cc32d73f61", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): prepare complete contract owners and execute scaled workflows" + }, + { + "revision": "397ae3edb6842126cd9e64ffde3d17c3d6e4a99b", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(imgui): document supplemental font ownership on its partial declaration" + }, + { + "revision": "6277db99b1475240e0062971eece72cb313d336a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): reuse authoritative attachments and ordinal scalar captures" + }, + { + "revision": "8fd31ceeaca311c1761ac84f6f25a95e0d790cb1", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve unavailable scene source assignments until recovery" + }, + { + "revision": "23f0ceb25e606e97ed8ac263b0204f69e563df09", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "chore(assets): preserve bundled font license bytes across checkouts" + }, + { + "revision": "e3f02ebeacdb908da6e274063f44c640adac0750", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(imgui): own supplemental glyph data through complete context retirement" + }, + { + "revision": "0b0a821c728a592ff18cde432c22e8d85b258d16", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse exact neutral captures without retaining source generations" + }, + { + "revision": "a38437f7d27307aadcf964f76e9228256d26185f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): verify output ownership through executed SDK results" + }, + { + "revision": "318e30aab746e49f549d0fa8083adf07ef2dbf09", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(io): publish complete files without replacing concurrent creators" + }, + { + "revision": "bb5b4bb9aff7d926cf585040c2feb4566f7ad74e", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "feat(editor): initialize new projects with the standard dock layout" + }, + { + "revision": "870a0af0be0977313054e0a1127977fa4e98317b", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): isolate product outputs and restore entry reference owners" + }, + { + "revision": "e2e6ca32f7aa07d9cfab230edcf1d2ad396e92fe", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): balance Shader settings scopes and presentation source contracts" + }, + { + "revision": "6df06585afb8f72dcb9d67abffe181be2b2d17c1", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): bound encoding buffers and converter lookup contention" + }, + { + "revision": "14c4d0b83c6c13b08d7b0961d222a72c1795e4f2", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): record controlled performance results and remaining acceptance" + }, + { + "revision": "f7dce89c0d91ecb0f2333b8ca3117fefc71db96a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve input semantics and bound serialization allocations" + }, + { + "revision": "660962a5bcd4bc393028738b0dc3b0d53a0b0e40", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): forward the selected tool host to UI acceptance" + }, + { + "revision": "0974c317c3afcdb97f8f95adf35e4677c40b6c46", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): synchronize current acceptance and API references" + }, + { + "revision": "a4d975abbd17cb12b866a36a5447ea0f07ad0e52", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): apply settings atomically and retire failed presentation" + }, + { + "revision": "e053be5b1de0ece46df91619711232eb54d5fd82", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): honor evaluated SDK output owners" + }, + { + "revision": "af6eac3153219c2457f9b187e332a1126952fb11", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): require explicit product initialization expectations" + }, + { + "revision": "081f41f917be7d1f4d33a6848d1fb69bfb68abf7", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): keep temporary SDK work independent of result paths" + }, + { + "revision": "14338d61df533f66646a82ba0f2d3184d264c003", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(tooling): validate complete graphs against their SDK output owner" + }, + { + "revision": "e23f4ba5b8e836264ec5a0f805707243c27ac5f6", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(assets): verify writable source content before reusing imports" + }, + { + "revision": "8dcceb94f4dd805075f6ed28db996eb017eae619", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): isolate neutral Foundation bootstrap compilation outputs" + }, + { + "revision": "63d4f7dce8b21daa22a6f7e5a00825be3f46dd2d", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "feat(editor): complete reusable presentation and SDK-owned compilation contracts" + }, + { + "revision": "273c42ad32be511c21fa21b1e99626af708f29c8", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(assets): preserve source identities during filesystem refresh" + }, + { + "revision": "b7a8924dfb0b9611ad8725f508a1d43cc30e6bfe", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(editor): reuse unchanged statistic snapshots" + }, + { + "revision": "6a656b674a90ebdda740ba6cb407f2f974564619", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): unify compiler tool references across SDK graphs" + }, + { + "revision": "88df3c798ab5186e4357e57bb5ff4d058b25b968", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(editor): index command metadata per extension generation" + }, + { + "revision": "65d222ae43631083c0a5db6747300d655611c298", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve drawing scopes when extensions fail" + }, + { + "revision": "199ae0dcff070c21728bc19994edb407378f43ce", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "chore(editor): track closure issues and acceptance requirements" + }, + { + "revision": "30d18489fbf68763229cd8ed35a7005f543019ad", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): reconcile UI acceptance with the approved plan" + }, + { + "revision": "2dc37114b7c9b5137e866efc057a66e0dfd72046", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): align export shutdown fixture with session ownership" + }, + { + "revision": "61f855c00275d26274e6faedfdab204b11b05a2f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): guard transient targets and script SDK owners" + }, + { + "revision": "fa794a5ab3f18a04d072c7f2fc923f4b678a8fea", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "refactor(editor): unify UI ownership and editing workflows" + } + ], + "coauthorTrailerCommits": [], + "documents": [ + { + "path": "C:\\Dev\\GameEngineDev\\InnoEngine\\AGENTS.md", + "sha256": "220f7eab11a12926dbec3bf93b062e249ff08e7728a40184658e72020c014503" + }, + { + "path": "C:\\Users\\23842\\Desktop\\InnoEngine_Editor_UI_Plan.md", + "sha256": "cbb7d50a26a08e4a0d50c7e0f9aabe6c8538ae3fbe229957d2094a4b60325d9e" + } + ] + }, + "packageIntegrity": { + "packages": [ + { + "package": "bgcs", + "version": "4.1.2", + "matched": 100, + "archiveSha256": "e45ae62dbb7cfa879999a4d91d58d6d17a0b26ac60d2044be47a7abbddab40f2", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.core", + "version": "4.1.2", + "matched": 5, + "archiveSha256": "25d3f0db2306792a08632278b02c3a08ef37c12f63330d6ca0b1eff55a971309", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.parserruntime.osx-x64", + "version": "4.1.2", + "matched": 15, + "archiveSha256": "da1b3ede4ffb6e0b370a36ff66be6a1c2610ccb5fea08d2e854b36a5accf10b5", + "matchesPreImplementationArchive": true, + "mismatches": [] + }, + { + "package": "bgcs.runtime", + "version": "4.1.2", + "matched": 6, + "archiveSha256": "5ab737cc30577290a0c049ba2254cc70642c30e7fb95c0a3188de6fd4643eb3e", + "matchesPreImplementationArchive": true, + "mismatches": [] + } + ], + "passed": true + } +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/manifest.json b/records/architecture/acceptance/editor-ui-2026-10-30/manifest.json new file mode 100644 index 000000000..9be481722 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/manifest.json @@ -0,0 +1,253 @@ +{ + "productionRevision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "files": [ + { + "path": ".gitattributes", + "raw": "Prepared final acceptance evidence", + "bytes": 162, + "sha256": "a6e93a6c30def3ddc4be06636e4643c263acc73639eaa9ccaac064a710e5a9d8" + }, + { + "path": "absolute-inspection-measurements.json", + "bytes": 38106, + "sha256": "767f55d8544f99556b177348ebd688f5c53cb612a23be187a87bab3efd504299", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e46\\inspection-pairs-afd3ba27331e494dbc33002b6562c011\\measurements.json" + }, + { + "path": "bgcs-package-integrity.json", + "bytes": 1094, + "sha256": "d83025144881a4e7c5564659e7badc592e18f6f3d48fe9129fc3874feddf695b", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\final-bgcs-package-integrity.json" + }, + { + "path": "cache-maintenance-dry-run.log", + "raw": "Prepared final acceptance evidence", + "bytes": 244153, + "sha256": "d67810c7a281439db9e9b81f3af7eb1dde4230d461223f893e5341ed0bafa1f9" + }, + { + "path": "cache-maintenance.log", + "raw": "Prepared final acceptance evidence", + "bytes": 82, + "sha256": "43e5e52cfad46bd7b40199df31d855404eeb8d2c4525fe3a4eafe3b1ed666082" + }, + { + "path": "cold-warm-publications.json", + "bytes": 215793, + "sha256": "8b25c6941006501cf23386b378f59be5233846459ad6ddf92de3015d5904b7c4", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\script-publication-investigation\\cold-content-dd5979c6\\results.json" + }, + { + "path": "current-ui.png", + "bytes": 215290, + "sha256": "707eb8d5a0ddf2aac4d60b9f7ecae5c68d1699e00f60fbecda2a5b1852b39e67", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e09\\visual-capture\\current\\Captures-dd5979c6\\native-ui-replay.png" + }, + { + "path": "delivery-wiki-examples.json", + "raw": "Prepared final acceptance evidence", + "bytes": 46556, + "sha256": "6c71b7d8cb35fd7106ed0a77b2e2fd0dfb0bca92fd6a3a2660d046f1c309cbfe" + }, + { + "path": "delivery-wiki-result.json", + "raw": "Prepared final acceptance evidence", + "bytes": 769, + "sha256": "04b974039d6f978fee1028cd2e50ba15430585a0ad3a6be7022a5b8b33493d66" + }, + { + "path": "delivery-wiki.log", + "raw": "Prepared final acceptance evidence", + "bytes": 791, + "sha256": "14149848b8fe1060e2f4f5bc53ad4c9bb5da084a792eb77dd5c54660f882a5cd" + }, + { + "path": "environment.json", + "bytes": 2875, + "sha256": "a99e55bdc6b6d0d8b095c0fa383f01974eb2498461705061b45e625c9a81d2a7", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\environment.json" + }, + { + "path": "file-tree.md", + "bytes": 5753, + "sha256": "0a6cc4af14a97ee6fd11afd78b25e1d65306598fdb69a29c1e0ff145cc280ad2", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\final-tree.md" + }, + { + "path": "gpu-context-observations.json", + "bytes": 9773, + "sha256": "7fd89e42b57af47f614876ed8ac3edc1d86848c84b88375c8c4f0d7027713af4", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\gpu-process-observations.json" + }, + { + "path": "graph-tail-protocol.json", + "bytes": 713, + "sha256": "6324bdae08c59f1a6f1e720a06fb5721e6685e2a9e29460193202d06417fa29e", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\graph-tail-review-protocol.json" + }, + { + "path": "graph-tail-summary.json", + "bytes": 11531, + "sha256": "c34fff55878a619d7ba013e2d6aa31d00aa8f3002bae502ac55e7248e2d5cb41", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6-graph-tail\\graph-tail-summary.json" + }, + { + "path": "issue-map.json", + "raw": "Prepared final acceptance evidence", + "bytes": 24767, + "sha256": "10a5dd071bb680feb821544ccb7753a20d4b48f04099e4a3d8fe2b0330535757" + }, + { + "path": "local-acceptance.json", + "bytes": 117785, + "sha256": "1b6a7b401b094446f6361502975e41f40cc0cb4cfea4258a007ffe78963a727e", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\delivery-data.json" + }, + { + "path": "original-plan-requirements.csv", + "bytes": 192631, + "sha256": "28455042820db7d21fab14cd09b3e2a273a760a16419cd4a93ea11c86abb5357", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\original-plan-requirements-prepared.csv" + }, + { + "path": "original-plan-source.json", + "raw": "Prepared final acceptance evidence", + "bytes": 433, + "sha256": "3faad8cf8b21757e84c4c63b8de34c9dfd181745e9775e22aa9aa14cf1878d1e" + }, + { + "path": "original-ui.png", + "bytes": 216695, + "sha256": "0efe2ec81d6e10ab0652e72368aafecbfc12f612353e0dfa71757450b91274fe", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e09\\visual-capture\\original\\Captures-cold-settled\\native-ui-replay.png" + }, + { + "path": "ownership-review.md", + "bytes": 13549, + "sha256": "f8435b6e7ef629bc23b8c4beaa6e16ce20b987350ac2a0434e8b271a195588ef", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\final-ownership-review-dd5979c6.md" + }, + { + "path": "performance-review.json", + "bytes": 9294, + "sha256": "6ec484e7f455b4bb347acc7c3d3627314a390cdac76a361a84f8654c44798d9d", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6-quantitative\\performance-review.json" + }, + { + "path": "performance-summary.json", + "bytes": 150629, + "sha256": "bda0af282ada8860b38cf871ded6d7b9cc61bef7314da24cf306830984e0bb75", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6-quantitative\\performance-summary.json" + }, + { + "path": "raw-commands-measurements.json", + "bytes": 57115, + "sha256": "78e70470237d68ddd257e1ad49bce4bd9099fb49d03062af756a6c8453622425", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\measurements.json" + }, + { + "path": "raw-graph-tail-measurements.json", + "bytes": 65583, + "sha256": "96844d1d880a25e82ccbd54dd000ab394944859a4a79b56dfbeb65a628eece14", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\measurements.json" + }, + { + "path": "raw-input-measurements.json", + "bytes": 49367, + "sha256": "ebf6b75eff1c10400320837fae825f301d2345da3b13962ea162663bd0c35fae", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\measurements.json" + }, + { + "path": "raw-product-measurements.json", + "bytes": 17664, + "sha256": "e7b42ffeff19ebfd558fe7cb8bab2bcd0d9ff3554ddd9091ba8f88bbc86e1ed9", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\measurements.json" + }, + { + "path": "raw-static-measurements.json", + "bytes": 55806, + "sha256": "b50e6cbcb4018e5eaf09cb2033ba9f1a74ea0c542372e3db2169bc9db29ef9a2", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\measurements.json" + }, + { + "path": "README.md", + "raw": "Prepared final acceptance evidence", + "bytes": 1507, + "sha256": "f78f220f5919f429880df9c8de469b0f89bcd8055c1861642c5cd6376867e987" + }, + { + "path": "source-inventory.json", + "bytes": 19088, + "sha256": "38f1346d01e037c06eece4ac5c0da45fb237d768f2ec5d6082e4bc6edf07f66d", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e54\\final-source-inventory.json" + }, + { + "path": "test-cases.csv", + "bytes": 584376, + "sha256": "5ddd02d7c5152655773ffd2a9509b009e82af46a91d4606dede70d89aab4d086", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\test-case-index.csv" + }, + { + "path": "visual-content-review.json", + "bytes": 1776, + "sha256": "74a2c807569b09caca5c3b4d6b8e9043b309fe43552c2dadb507d4094eb37673", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\visual-content-review.json" + }, + { + "path": "volume-after-maintenance.json", + "raw": "Prepared final acceptance evidence", + "bytes": 166, + "sha256": "bf9d661b6f80dd855cf219d9e1a326a9a88c79559fc9c3f70089f2b3bd07532b" + }, + { + "path": "volume-before-maintenance.json", + "raw": "Prepared final acceptance evidence", + "bytes": 166, + "sha256": "ad4dbd921eeec03ffd6c78f2da2a12e20dd4a07f60f2c37c206c0210b25e72c2" + }, + { + "path": "wiki-result.json", + "bytes": 216, + "sha256": "90a5964bacbf0fcdd7a8785594f26af13bdeca26c507b425e72ecafd3b47342a", + "raw": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6\\wiki-result.json" + } + ], + "requirementRows": 307, + "requirementSnapshotStates": { + "\u5f85\u9a8c\u6536": 15, + "\u660e\u786e\u4e0d\u9002\u7528": 9, + "\u901a\u8fc7": 283 + }, + "deliveryAttachment": "https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338", + "pendingOriginalLines": [ + 14, + 15, + 51, + 74, + 95, + 98, + 710, + 716, + 719, + 720, + 734, + 738, + 741, + 742, + 743 + ], + "rawEvidenceOwner": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27", + "scope": "Portable raw measurements and result indexes; complete TRX/binlogs/source/runtime manifests and initial failed attempts remain under the recorded owner. This manifest is not a production registry.", + "planSourceSha256": "cbb7d50a26a08e4a0d50c7e0f9aabe6c8538ae3fbe229957d2094a4b60325d9e", + "cacheMaintenance": { + "status": "Passed", + "exitCode": 0, + "logicalInventoryBytes": 90213428162, + "logicalRetiredBytes": 90039337805, + "protectedBytes": 174090357, + "observedCFreeGiBBefore": 77.79, + "observedCFreeGiBAfter": 90.32, + "observedCFreeDeltaGiB": 12.53, + "scope": "Formal owned cache clean, after dry-run; logical bytes and volume free delta are distinct. Five policy-rejected D: diagnostic runtimes remain untouched. D: free remained about5.78GiB. No user/SDK/NuGet global content deleted." + } +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-requirements.csv b/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-requirements.csv new file mode 100644 index 000000000..9d900b9ba --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-requirements.csv @@ -0,0 +1,308 @@ +originalLine,section,requirement,issue,owner,commits,state,evidence,command,verificationAuthority,scopeNote +14,已确认范围,完整闭合 Editor UI 原计划。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +15,已确认范围,完成已有 **#16、#19** 的验收及必要修复。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +16,已确认范围,完成 **#18 中本轮涉及的 API、生命周期与失败语义文档**;全仓其他 API 不因此视为完成。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +17,已确认范围,**#13** 复用已有修复和回归,不创建重复 issue。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +18,已确认范围,#14、#15、#17、#20、#21、#22 等独立问题继续保留,不把本轮扩大成全引擎重写。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +19,已确认范围,Canvas、Rendering2D、Samples 属于调用方、正式插件包和产品回归范围。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +20,已确认范围,不开展新的渲染模型、动画 Timeline 或 Browser Editor 产品。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +24,固定约束,保持 .NET SDK 10.0.402、.NET 10、C# 14、BGCS 4.1.2。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +25,固定约束,不修改 BGCS 源码、NuGet 包、第三方源码或生成绑定。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +26,固定约束,禁止 global using、implicit using、MSBuild `Using` item 和 metadata 隐式导入。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +27,固定约束,不增加 legacy reader、兼容 overload、旧路径 fallback、schema version 或双实现。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +28,固定约束,不新增平行 Events、Identity、History、Serialization、插件发现或缓存管理系统。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +29,固定约束,保持现有视觉辨识度;允许为了统一操作与布局修改局部外观。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +30,固定约束,不通过新增空项目、公开内部实现、IVT、私有反射或测试后门完成验收。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +31,固定约束,不降低 generation、Missing、原子恢复、ALC 回收和错误诊断要求。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +32,固定约束,原计划要求能够通过真实 ImGui Context、公开输入和 Native Host 完成的验证,必须实现自动验收;不能因 computer use 不可用而省略。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +48,1. 第一阶段必须创建真实 issue,检索当前开放和已关闭 issue,确认是否已有相同问题。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +49,1. 第一阶段必须创建真实 issue,创建一个总 issue,覆盖原 UI 计划的全部剩余工作与验收。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +50,1. 第一阶段必须创建真实 issue,为下表各专项创建真实 issue;已有完全对应项则复用并补齐正文。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +51,1. 第一阶段必须创建真实 issue,将实际 GitHub 编号回填到总 issue、实施台账和验收矩阵。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +52,1. 第一阶段必须创建真实 issue,明确依赖顺序,再开始实现。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +66,2. 每个 issue 的必填内容,原计划对应条目。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +67,2. 每个 issue 的必填内容,当前已经确认的事实。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +68,2. 每个 issue 的必填内容,尚未验证的风险,不能与已复现缺陷混写。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +69,2. 每个 issue 的必填内容,所属程序集、职责 owner 和依赖方向。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +70,2. 每个 issue 的必填内容,明确的实现范围与不涉及范围。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +71,2. 每个 issue 的必填内容,失败、取消、资源释放和 generation 边界。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +72,2. 每个 issue 的必填内容,可执行的验收清单。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +73,2. 每个 issue 的必填内容,所依赖的其他 issue。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +74,2. 每个 issue 的必填内容,完成后的提交号、命令、结果与证据链接。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +93,3. Issue 状态与关闭规则,分支验收通过后仍保持 Open,不能让尚未修复的 master 显示问题已解决。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +94,3. Issue 状态与关闭规则,未完成全部验收时,保持 Open,并写明失败、阻塞或未执行项。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +95,3. Issue 状态与关闭规则,PR 正文对每个完整解决的 issue 单独写一行 `Fixes #编号`。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +96,3. Issue 状态与关闭规则,部分完成的 issue 使用 `Refs #编号`,不能使用 `Fixes`。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +97,3. Issue 状态与关闭规则,#18 只完成本轮相关 API 时使用 `Refs #18`。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +98,3. Issue 状态与关闭规则,总 issue 只有所有适用子项和最终验收完成后才能关联关闭。,27,Approved whole-plan traceability and final acceptance,199ae0dc,待验收,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +99,3. Issue 状态与关闭规则,合并后核对实际关闭结果;仍有工作未完成的 issue 不得误关。,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Post-merge audit is conditional on a future user-authorized master merge. No automatic merge or premature issue closure. +100,3. Issue 状态与关闭规则,如果实现中发现新的必要工作,先检查已有 issue,再创建对应 issue,不把重要工作隐藏在提交说明里。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +137,基线记录,四仓库提交号、分支、tracked/untracked 差异及已有修改。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +138,基线记录,PR #26 当前 head、master 基线及检查状态。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +139,基线记录,原始计划与复审报告的内容哈希。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +140,基线记录,SDK、运行时、驱动、显卡、屏幕尺寸、DPI、UI Zoom、构建配置。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +141,基线记录,当前 UI、插件包与 FlappyBird 输入身份。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +142,基线记录,C/D 可用空间和本次证据 owner。,27,Approved whole-plan traceability and final acceptance,199ae0dc,通过,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +162,验收矩阵,未开始,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +163,验收矩阵,实现中,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +164,验收矩阵,待验收,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +165,验收矩阵,通过,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +166,验收矩阵,失败,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +167,验收矩阵,阻塞,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +168,验收矩阵,明确不适用,27,Approved whole-plan traceability and final acceptance,199ae0dc,明确不适用,"All final functional, product, visual, performance and CI gates",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,This line defines the allowed status vocabulary; it is not a separate functional test or seven additional passing requirements. +180,E01:绘制异常安全与资源所有权,审查改动范围内的 Begin/End、Push/Pop、ID、Clip、Style、Child、Table、Popup、OwnerScope。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +181,E01:绘制异常安全与资源所有权,优先处理 Shader Canvas 和 Console 中缺少局部异常保护的配对调用。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +182,E01:绘制异常安全与资源所有权,按各 ImGui API 的真实规则配对,不能一律按返回值决定是否 End。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +183,E01:绘制异常安全与资源所有权,使用现有作用域和 `try/finally`;只有多个真实调用方存在相同所有权需求时才提取内部封装。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +184,E01:绘制异常安全与资源所有权,保持 native borrow 的 frame、window、context、thread 校验。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +185,E01:绘制异常安全与资源所有权,失败时仅释放本次作用域,不能清空其他 owner 的状态。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +186,E01:绘制异常安全与资源所有权,手势回滚失败时保留可重试 owner 和诊断,不强制销毁仍承担事务的资源。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +190,E01:绘制异常安全与资源所有权,在真实可扩展 Drawer、菜单 source、节点内容绘制和生命周期回调处注入异常。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +191,E01:绘制异常安全与资源所有权,异常后下一帧仍可绘制,ID/style/clip/window 栈平衡。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +192,E01:绘制异常安全与资源所有权,两个 Session、两个窗口之间不互相污染。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +193,E01:绘制异常安全与资源所有权,非法释放顺序明确失败,后续合法释放仍能完成。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +194,E01:绘制异常安全与资源所有权,不吞掉原始异常,不降低原生断言,不增加测试专用生产入口。,28,Session/context scopes and actual presentation callers,65d222ae;e2e6ca32;a25d6302,通过,Contracts/Visual: extension failure and next-frame scopes;Lifecycle: owner and retirement protocols;e28/export-label: prior-producer negative and corrected public provider failure,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +206,E02:菜单与快捷键按 generation 建索引,在现有 extension catalog 生命周期内建立不可变静态菜单结构。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +207,E02:菜单与快捷键按 generation 建索引,静态索引保存 Action ID、area、规范化路径段、顺序、分隔信息和 shortcut metadata。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +208,E02:菜单与快捷键按 generation 建索引,shortcut 候选按 gesture/area 建立索引,避免每次按键扫描所有 Action。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +209,E02:菜单与快捷键按 generation 建索引,保持 target specificity、priority 和冲突诊断语义。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +210,E02:菜单与快捷键按 generation 建索引,`Query`、enabled、checked、display name、disabled reason 和动态菜单 contribution 继续使用当前上下文。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +211,E02:菜单与快捷键按 generation 建索引,动态参数只属于当前请求,不能进入 generation 长寿命缓存。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +212,E02:菜单与快捷键按 generation 建索引,候选成功后原子替换索引;失败保留 last-good;旧索引随 generation 退休。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +213,E02:菜单与快捷键按 generation 建索引,command search、toolbar、context menu 与 main menu 使用同一 Action 协议。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +217,E02:菜单与快捷键按 generation 建索引,重复打开菜单不重复规范化、拆分和排序静态声明。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +218,E02:菜单与快捷键按 generation 建索引,Selection、Play、只读源、Missing 状态变化后,可用性立即正确。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +219,E02:菜单与快捷键按 generation 建索引,扩展加入、删除、重载后索引更新,没有旧 Action 或 collectible 引用。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +220,E02:菜单与快捷键按 generation 建索引,重复和冲突明确诊断,不被静默覆盖。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +221,E02:菜单与快捷键按 generation 建索引,主菜单顺序稳定,无空组、多余分隔线或重复动作。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +222,E02:菜单与快捷键按 generation 建索引,没有原生上下文时,纯菜单模型查询仍可执行。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +223,E02:菜单与快捷键按 generation 建索引,菜单与快捷键进入 E11 性能采样。,29,Generation-owned Interactions menu/shortcut catalog,88df3c79;bc88dc04,通过,"Contracts: fresh Query/conflict/last-good/index reuse;Lifecycle: actual generation loop;Quantitative: menus, shortcut hit/miss",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +235,E03:三层样式与组件归属,基础值:字体、颜色语义、间距、边框、圆角、焦点、禁用态。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +236,E03:三层样式与组件归属,通用组件值:表单、工具栏、卡片、树行、表格、菜单、Tooltip、Modal。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +237,E03:三层样式与组件归属,领域呈现值:Browser 图标/列布局、Shader 节点、Viewport overlay、专属预览。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +241,E03:三层样式与组件归属,Browser 列比例、图标布局、面包屑和 pane 策略归 Assets presentation。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +242,E03:三层样式与组件归属,Shader 节点类别与端口语义归 Shader presentation。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +243,E03:三层样式与组件归属,通用 metrics 不认识具体资产类别或渲染模型。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +244,E03:三层样式与组件归属,同类控件统一空、错误、忙碌、禁用状态及 Tooltip。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +245,E03:三层样式与组件归属,保持 2:3 label/input 规则,操作列先扣除。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +246,E03:三层样式与组件归属,DPI 与用户 Zoom 分开计算,样式快照在帧边界更新。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +247,E03:三层样式与组件归属,删除被替换成员并更新调用方,不保留转发属性。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +248,E03:三层样式与组件归属,清理本轮文件的重复 facade 别名、无用引用和妨碍阅读的排版,不做无关全仓格式化。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +252,E03:三层样式与组件归属,通用层不存在领域反向引用。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +253,E03:三层样式与组件归属,同一帧尺度一致,不重复缩放。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +254,E03:三层样式与组件归属,窄 Inspector、长标签、图标按钮、错误提示和只读字段可读。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +255,E03:三层样式与组件归属,Widgets 文件符合现有组织规范。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +256,E03:三层样式与组件归属,公开成员、脚本导出、Wiki 与调用方同步。,30,Shared presentation and domain-owned styles,63d4f7dc;f7dce89c,通过,Architecture dependencies/API;Visual: frame-consistent Zoom/DPI/control matrix;Matched original/current native UI and viewport readback,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +268,键盘与选择规则,Up/Down 移动当前可见项。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +269,键盘与选择规则,Home/End 到首尾。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +270,键盘与选择规则,PageUp/PageDown 按可见页移动。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +271,键盘与选择规则,Shift 扩展到 anchor 的范围。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +272,键盘与选择规则,平台主修饰键行为沿用现有平台键盘策略。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +273,键盘与选择规则,Enter 激活;Escape 退出搜索或取消当前交互,保留既有手势协议。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +274,键盘与选择规则,焦点、Selection、激活和数据修改分别处理。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +275,键盘与选择规则,范围选择基于同一过滤、排序、展开结果。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +276,键盘与选择规则,文本输入、Popup、Modal 占有输入时,不派发底层列表命令。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +277,键盘与选择规则,Scene/Asset 对象通过 Core Identity 解析,不缓存 live object。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +278,键盘与选择规则,Console 使用现有 occurrence sequence/group identity,不新造对象身份系统。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +282,Console 搜索,搜索 message、source、file 和已有诊断文本。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +283,Console 搜索,默认不区分大小写的普通子串匹配,不引入正则或额外查询语言。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +284,Console 搜索,搜索与级别过滤同时生效。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +285,Console 搜索,Collapse 模式只统计和展示匹配 occurrences,详情与复制保持一致。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +286,Console 搜索,过滤结果按 snapshot revision、搜索文本及过滤条件更新。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +287,Console 搜索,Clear 后清理选中条目、展开状态和派生视图。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +288,Console 搜索,不修改 Diagnostics 原始记录,不建立第二个日志数据库。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +292,Console 搜索,Hierarchy、File Browser、Console 公开输入测试覆盖全部按键规则。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +293,Console 搜索,过滤、排序后 anchor 行为确定,不误选隐藏对象。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +294,Console 搜索,输入框和 Popup 无操作穿透。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +295,Console 搜索,中文、非 ASCII、空字符串、长文本搜索正确。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +296,Console 搜索,Collapse、详情、复制和键盘选择指向相同记录。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +297,Console 搜索,多选删除、复制、拖放继续走原子领域操作。,31,Visible-order navigation and Diagnostics occurrence views,63d4f7dc;2bb4da40;bc88dc04,通过,Workflows/Visual: native Hierarchy/Browser/Console input;10k search/range/scroll/drag/rename journeys,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +309,职责,`Inno.Editor.Graph`:文档、ID、选择、导航、领域无关编辑与 History。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +310,职责,`Inno.Editor.Graph.ImGui`:节点几何、绘制、命中、框选、连线交互和可见范围。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +311,职责,Shader owner:节点类型、端口类型、默认值、编译、参数和诊断。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +315,职责,扩展现有 `GraphCanvasView`,不另建 Graph 框架。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +316,职责,使用最小不可变节点/端口呈现输入:稳定 ID、几何、文本、视觉状态和连接端点。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +317,职责,呈现层返回中立交互意图:选择、框选、移动、连接、删除请求和菜单目标。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +318,职责,文档修改仍由 controller/feature 完成并记录 History。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +319,职责,自定义节点内容只在当前绘制作用域调用,不把 plugin callback 保存进长期缓存。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +320,职责,Shader Canvas 的通用命中、框选、拖动移入通用 owner;Shader 保留语义解释。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +321,职责,连续拖动使用既有手势,取消恢复起点,完成产生一个逻辑 Undo。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +322,职责,几何缓存只含中立值和 ID,其有效性由 document、generation、revision 与尺度决定。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +326,职责,非 Shader 测试图通过相同公开契约绘制与交互。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +327,职责,不修改中央 switch、Shader Panel 或类型白名单。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +328,职责,覆盖平移、缩放、重叠节点命中、框选、连接、断开、删除、剪贴板和 Undo/Redo。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +329,职责,Missing 节点保留结构和属性 bytes。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +330,职责,关闭文档、切换 generation 后不保留旧实例或 callback。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +331,职责,已有 Shader 布局与编译诊断保持正确。,32,Neutral Graph document/controller and Graph.ImGui geometry/intents,63d4f7dc,通过,Contracts: graph layout/pointer/controller;Visual: independent non-Shader native consumer/failure;Workflows: Shader document/History/Missing;Quantitative: deterministic 2k graph,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +343,E06:Stats 快照与趋势,`EditorStatistics` 保持统计贡献权威。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +344,E06:Stats 快照与趋势,保持当前 frame 与上一 completed frame 的既有合并语义。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +345,E06:Stats 快照与趋势,内容和排序不变时复用不可变快照。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +346,E06:Stats 快照与趋势,Panel 按快照变化建立分组,不每帧重复 LINQ。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +347,E06:Stats 快照与趋势,Frame Timing 每 0.5 秒生成展示样本。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +348,E06:Stats 快照与趋势,保留最近 60 秒、最多 120 个样本。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +349,E06:Stats 快照与趋势,展示 FPS 和 frame time 趋势,不把任意统计字符串猜测成数值。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +350,E06:Stats 快照与趋势,使用 Panel 所属有界缓冲,不新建事件队列或对象池。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +351,E06:Stats 快照与趋势,关闭或长期停止后明确重置采样,不伪造连续历史。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +355,E06:Stats 快照与趋势,无变化读取复用快照。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +356,E06:Stats 快照与趋势,修改、删除、换组、排序正确刷新。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +357,E06:Stats 快照与趋势,Provider 绘制顺序不导致数据丢失。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +358,E06:Stats 快照与趋势,采样时间、窗口和容量有边界测试。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +359,E06:Stats 快照与趋势,Panel 重开不增长、不保留旧引用。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +360,E06:Stats 快照与趋势,E11 证明静态显示不重复构造同一批数据。,33,EditorStatistics authority and bounded Stats Panel sampler,b7a8924d;63d4f7dc,通过,"Contracts: unchanged snapshot reuse/frame ordering;Visual: actual native trends, interval/window/reset;Products: actual Stats Panel 100 cycles;Quantitative: statistics",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +368,E07:大列表、树与 Graph 的工作量控制,固定高度列表按可见行提交;网格按可见行裁剪。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +369,E07:大列表、树与 Graph 的工作量控制,树维护由 revision、展开、过滤、排序确定的扁平可见序列。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +370,E07:大列表、树与 Graph 的工作量控制,选择、anchor、编辑和拖动状态不随裁剪丢失。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +371,E07:大列表、树与 Graph 的工作量控制,不可见正常条目不执行完整控件绘制。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +372,E07:大列表、树与 Graph 的工作量控制,Console 可变高度条目缓存测量结果;内容、宽度、尺度、展开变化后失效。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +373,E07:大列表、树与 Graph 的工作量控制,Graph 保留已有离屏节点判断,避免无变化时全图端口坐标重建。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +374,E07:大列表、树与 Graph 的工作量控制,平移应用坐标变换;节点、端口和尺寸变化只更新相关几何。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +375,E07:大列表、树与 Graph 的工作量控制,不凭猜测增加复杂空间索引、池或第二张图文档数据库。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +379,E07:大列表、树与 Graph 的工作量控制,"10,000 项树/列表的滚动、搜索、范围多选、拖放、重命名正确。",34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +380,E07:大列表、树与 Graph 的工作量控制,裁剪不丢焦点、展开状态或控件身份。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +381,E07:大列表、树与 Graph 的工作量控制,"2,000 节点图修改一个节点时,其余有效几何不重建。",34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +382,E07:大列表、树与 Graph 的工作量控制,删除、Missing、reload、尺度变化正确失效。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +383,E07:大列表、树与 Graph 的工作量控制,记录实际提交项数量和分配,不能只展示 FPS。,34,Visible tree/grid/Console rows and neutral graph display projection,63d4f7dc;973a1aa7;bc88dc04,通过,Visual/Workflows: 10k native range/edit/drag and clipped content counts;Contracts: 2k affected-node geometry reuse;Quantitative: five paired static and input producers,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +393,E08:全界面工作流与 Undo/Redo,Workbench → 菜单、命令搜索、快捷键、工具栏使用相同动作和禁用原因,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Action/Menu/Search/Toolbar/Shortcut public contracts and Products real Workbench drawing; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +394,E08:全界面工作流与 Undo/Redo,Hierarchy → 多选、范围选择、复制、删除、重设父级及失败回滚,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,SceneHistoryTests BatchMove/BatchDelete/LaterBatchSetterFailure; Products mixed selection/batch rollback; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +395,E08:全界面工作流与 Undo/Redo,File Browser → 搜索、排序、面包屑、重命名、批量操作、只读安装源,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,ListPanelKeyboardNativeTests/LargePanelPerformanceTests/installed-source mutations; Products FileBrowser save/reopen; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +396,E08:全界面工作流与 Undo/Redo,Inspector → mixed value、单轴编辑、手势、布尔离散操作、引用选择/定位/清除/拖入,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,InspectionPipelineTests/reference/drop native contracts; Products mixed axis/continuous/discrete/reference journeys; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +397,E08:全界面工作流与 Undo/Redo,Component/System → 重复类型实例对应、增加/删除/排序、Missing 保留与恢复,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Products duplicate Component instances/order/removal/identity restoration; Missing generation recovery; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +398,E08:全界面工作流与 Undo/Redo,Scene View → 导航、Gizmo、选取、绘制职责分开;多选变换与取消,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,EditorViewportNavigationTests/Scene gestures and multiselection transform/cancel; Products real SceneView; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +399,E08:全界面工作流与 Undo/Redo,Game View → Play 输入、比例、缩放、焦点、停止后恢复 Edit,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,EditorGameInputCaptureTests/EditorPlayModeTests and Products Play→Stop→Edit; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +400,E08:全界面工作流与 Undo/Redo,Shader Editor → 文档、节点、连线、剪贴板、保存和 History,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,ShaderEditorWorkflowTests/GraphCanvasNativeTests/document lifecycle: links/clipboard/save/History/Missing; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +401,E08:全界面工作流与 Undo/Redo,Settings → 验证、dirty、Apply、Cancel、关闭、失败恢复,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,SettingsApplyNativeTests/ProjectSettingsExecutionContextTests: dirty/Apply/Cancel/close/failure/retry; test-cases.csv; local-acceptance.json products,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +402,E08:全界面工作流与 Undo/Redo,Export/编译窗口 → 进度、取消、错误、重试、关闭及后台 owner 生命周期,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,"BuildPipelineTests: actual export form, progress/cancel/retry and asynchronous retirement; test-cases.csv; local-acceptance.json products",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +406,E08:全界面工作流与 Undo/Redo,#13 的 Commit/Rollback/Dispose 拒绝后栈保持正确。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +407,E08:全界面工作流与 Undo/Redo,两次独立拖动产生两次 Undo,单次拖动合并。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +408,E08:全界面工作流与 Undo/Redo,布尔、创建、删除、排序不被连续值 mergeKey 合并。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +409,E08:全界面工作流与 Undo/Redo,Escape、切换选择、关闭文档、Play、reload 的边界行为明确。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +410,E08:全界面工作流与 Undo/Redo,批量中途失败恢复全部已修改内容。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +411,E08:全界面工作流与 Undo/Redo,Missing barrier 不移动栈、不丢记录,恢复后自动可用。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +412,E08:全界面工作流与 Undo/Redo,回滚失败不提前释放 payload 或 owner。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +413,E08:全界面工作流与 Undo/Redo,保存、打开、纯选择不错误进入数据 Undo。,35,"Feature edits, unified History and actual Panel composition",fa794a5a;a4d975ab;f7dce89c;8fd31cee;2bb4da40,通过,Workflows: actual Inspector/Scene/Asset/Settings/Shader/Play/export journeys;Contracts: History atomicity and Missing barriers;Products: eight FlappyBird journeys in six configurations,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +425,已有 #16:还原 owner 与加载身份,保留 SDK 求值的 `ProjectAssetsFile`,不恢复默认 obj 猜测。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +426,已有 #16:还原 owner 与加载身份,默认输出与独立 `ArtifactsPath` 分别验证。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +427,已有 #16:还原 owner 与加载身份,缺少或存在过期默认 obj 时,独立输出只使用自身输入。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +428,已有 #16:还原 owner 与加载身份,关联实际恢复包身份与实际宿主/worker 加载程序集内容。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +429,已有 #16:还原 owner 与加载身份,错配反例在测试自有目录构造,不修改全局 NuGet 包。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +430,已有 #16:还原 owner 与加载身份,普通与进程隔离入口遵守相同 owner 语义。,16,SDK-evaluated Restore and process-owned loaded-package test,564e00d0;4d0fdb0a,通过,"Contracts: NuGetLoadedIdentityTests;e16/process-owned-fixture: ten ordinary/isolated entries, forty parent scenarios",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +442,已有 #19:脚本与 IDE 的 SDK/reference 一致性,已选 SDK 的真实 MSBuild 求值成为 reference 输入权威。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +443,已有 #19:脚本与 IDE 的 SDK/reference 一致性,复用既有 SDK resolver、managed input capture 和 Restore/compilation 隔离。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +444,已有 #19:脚本与 IDE 的 SDK/reference 一致性,删除脚本编译器自行从候选根挑选最高 reference pack 的独立决策。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +445,已有 #19:脚本与 IDE 的 SDK/reference 一致性,Build/Host 组合层将 SDK 结果映射为不可变中立编译输入。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +446,已有 #19:脚本与 IDE 的 SDK/reference 一致性,`Inno.Scripting.Compiler` 不引用 Build、具体平台或 MSBuild 类型。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +447,已有 #19:脚本与 IDE 的 SDK/reference 一致性,同一操作固定 TFM、C# 版本、reference 内容、预定义符号和编译器身份。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +448,已有 #19:脚本与 IDE 的 SDK/reference 一致性,IDE 与运行编译消费相同输入事实。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +449,已有 #19:脚本与 IDE 的 SDK/reference 一致性,IDE 实际求值不匹配时明确诊断,不静默换引用,不改写用户 SDK 配置。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +450,已有 #19:脚本与 IDE 的 SDK/reference 一致性,SDK/reference 变化进入产物指纹,逻辑 API 契约指纹继续独立。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +451,已有 #19:脚本与 IDE 的 SDK/reference 一致性,临时工程及结果留在 Library/构建 owner,不散落源码、不产生依赖锁清单。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +459,已有 #19:脚本与 IDE 的 SDK/reference 一致性,多稳定 pack、多 SDK root、显式 SDK 选择。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +460,已有 #19:脚本与 IDE 的 SDK/reference 一致性,高版本干扰 pack 不影响已选 SDK 结果。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +461,已有 #19:脚本与 IDE 的 SDK/reference 一致性,缺失、不兼容、求值失败、取消明确失败。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +462,已有 #19:脚本与 IDE 的 SDK/reference 一致性,IDE 与 Runtime 的引用内容、语言版本及符号一致。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +463,已有 #19:脚本与 IDE 的 SDK/reference 一致性,C# 14 的 `field`、空条件赋值、Span 重载、泛型 `nameof`、Lambda 修饰符、扩展成员及跨插件调用一致。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +464,已有 #19:脚本与 IDE 的 SDK/reference 一致性,输入变化正确失效,失败不发布半成品。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +465,已有 #19:脚本与 IDE 的 SDK/reference 一致性,正式重新生成 Canvas、Rendering2D、Samples 投影,不手改派生文件。,19,Build SDK capture -> neutral compiler inputs -> Host/IDE,63d4f7dc,通过,Contracts: frozen multi-SDK/CSharp14 inputs;Companion-Canvas;Companion-Rendering2D;Companion-FlappyBird,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +473,E09:视觉、尺度与输入矩阵,Zoom:75%、85%、90%、100%、110%、125%、150%。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +474,E09:视觉、尺度与输入矩阵,DPI:100%、125%、150%、200%。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +475,E09:视觉、尺度与输入矩阵,至少覆盖 28 个尺度组合。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +476,E09:视觉、尺度与输入矩阵,基础窗口:1280×720、1920×1080。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +477,E09:视觉、尺度与输入矩阵,另测窄 Inspector、小窗口、浮动窗口、拆分停靠。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +478,E09:视觉、尺度与输入矩阵,使用真实 ImGui Context、公开输入和 Host。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +479,E09:视觉、尺度与输入矩阵,模拟 metrics 明确标为契约测试,不冒充全部物理显示器实测。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"The requirement to distinguish simulated/native automated results from unexecuted physical/manual checks is satisfied; physical displays, OS IME and manual Mac are NOT claimed passed." +483,E09:视觉、尺度与输入矩阵,长标签、中文/非 ASCII、剪贴板、文本选择。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +484,E09:视觉、尺度与输入矩阵,mixed、read-only、Missing、disabled、error、busy、empty。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +485,E09:视觉、尺度与输入矩阵,Popup、Modal、浮动窗口遮挡。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +486,E09:视觉、尺度与输入矩阵,单一滚动 owner、真实溢出、下拉边界。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +487,E09:视觉、尺度与输入矩阵,同帧移动点击、双击、箭头展开、叠加按钮。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +488,E09:视觉、尺度与输入矩阵,键盘导航、焦点、快捷键。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +489,E09:视觉、尺度与输入矩阵,不仅依赖颜色表达状态。,36,Real ImGui public input/geometry and matched content review,d7f383f0;e3f02ebe;bc88dc04,通过,Visual: independent Zoom/DPI/size matrix and occlusion/text/state cases;Final current and retained original 6000-preparation-frame capture;Physical monitor/IME/manual Mac explicitly unexecuted,tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +501,E10:生命周期与连续 reload,每类实际有状态 Panel 至少 100 次关闭/打开。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +502,E10:生命周期与连续 reload,Scene、Shader 等实际文档各至少 100 次打开/关闭。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +503,E10:生命周期与连续 reload,连续 10 次脚本/plugin reload,包含成功、候选失败、Missing、恢复。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +504,E10:生命周期与连续 reload,每轮检查 owner、订阅、控件状态、手势、文档引用及旧 ALC。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +505,E10:生命周期与连续 reload,正常关闭 Host 后检查 Session、窗口、订阅和手势退休。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +509,E10:生命周期与连续 reload,保持严格 GC/finalizer/weak-monitor barrier。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +510,E10:生命周期与连续 reload,timeout 明确 Faulted,不清空 pending monitor 继续。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +511,E10:生命周期与连续 reload,未完成恢复时不得覆盖磁盘状态。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +512,E10:生命周期与连续 reload,editor.ini 不保存 Selection、Undo、runtime 引用或编译中间态。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +513,E10:生命周期与连续 reload,退出时最终状态和最新 layout 原子保存。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +514,E10:生命周期与连续 reload,资源返回预期存量,不随已完成操作次数线性增长。,37,Session/Panel/document/generation retirement,d7f383f0;bced2aef;e3f02ebe,通过,"Lifecycle: document cycles and ten success/reject/Missing/recovery reloads;Products: every discovered actual Panel 100 cycles, strict GC resource series;Host exit state/layout publication",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +526,E11:完整性能验收,优化前固定复审源码作为本轮基线。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +527,E11:完整性能验收,恢复原重构前基线,完成原计划前后比较。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +528,E11:完整性能验收,基线无法构建或输入不可恢复则标记阻塞,不用微基准替代。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,Conditional failure rule reviewed. Original production baseline b303d9f1 is runnable with recorded test-only adapters and matching gameplay/assets/layout; original setup failures remain preserved. +529,E11:完整性能验收,插件与正式包身份单独记录;只有相同输入和条件才进行比较。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +533,E11:完整性能验收,FlappyBird 常规布局。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +534,E11:完整性能验收,"10,000 项树和资源列表。",38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +535,E11:完整性能验收,"2,000 节点、确定性连接结构的 Graph。",38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +536,E11:完整性能验收,空闲、滚动、搜索、属性拖动、菜单打开。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +537,E11:完整性能验收,冷启动和预热后稳定运行分别采样。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +541,E11:完整性能验收,同机器、SDK、配置、驱动、尺寸、DPI、Zoom。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +542,E11:完整性能验收,每场景至少五组有效重复,成对交替运行。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +543,E11:完整性能验收,保存 CPU、分配、GC、frame median/P95 和工作量原始数据。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +544,E11:完整性能验收,功能结果一致,热路径分配不增加。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +545,E11:完整性能验收,超过 10% 的可重复 CPU/P95 回退必须解决。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +546,E11:完整性能验收,噪声大则重新采样,不挑最好一次。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +547,E11:完整性能验收,不降低正确性、隐藏错误或减少验证达标。,38,Controlled public-host static/input/command/product measurements,973a1aa7;bc88dc04;1917b253,通过,Frozen input/runtime hashes before/after;150 five-pair original/current trial rows;Ten absolute complete-inspection processes;All regression flags require explicit review,Invoke-FinalQuantitative.ps1; Invoke-GraphTailPairs.ps1; summarize_closure.py; review_graph_tail.py,performance-summary.json; performance-review.json; graph-tail-summary.json; raw-*-measurements.json,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +557,E12:文档、代码质量与最终审查,完成 `EDITOR_UI_DESIGN_STANDARD.md`:操作、菜单、样式、焦点、选择、手势、错误反馈和组件使用规范。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +558,E12:文档、代码质量与最终审查,同步架构树、API Wiki、脚本导出、项目引用。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +559,E12:文档、代码质量与最终审查,对新增/修改 API 审查所有权、线程、借用、取消、失败、Missing、generation。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +560,E12:文档、代码质量与最终审查,#18 的本轮成果作为子项证据,全仓 issue 保持真实状态。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +561,E12:文档、代码质量与最终审查,删除被替换成员、旧目录、无效配置和重复实现,不留转发 facade。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +562,E12:文档、代码质量与最终审查,检查 `.sln`、`.csproj`、命名空间和源码排版。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +563,E12:文档、代码质量与最终审查,十一项质量维度分别列出证据、判断与限制。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +564,E12:文档、代码质量与最终审查,验证解耦深入执行链,Core 能力真实复用,扩展具有实际消费者。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +661,五、目标文件组织与 API 边界,通用 Graph 呈现输入与交互结果。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +662,五、目标文件组织与 API 边界,实际外部消费者需要的中立导航/控件契约。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +663,五、目标文件组织与 API 边界,Host 向脚本编译器传递冻结输入的边界。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +677,验收入口,`Contracts`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +678,验收入口,`Workflows`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +679,验收入口,`Visual`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +680,验收入口,`Lifecycle`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +681,验收入口,`Performance`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +682,验收入口,`Products`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +683,验收入口,`All`,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +687,验收入口,引擎与 companion revision。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +688,验收入口,SDK、配置、设备、输入身份。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +689,验收入口,命令、退出码、TRX、日志、截图、性能数据。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +690,验收入口,通过、失败、跳过、阻塞、未执行分类。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +691,验收入口,对应 GitHub issue 和原计划条目。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +701,产品验收,Editor Debug/Release。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +702,产品验收,D3D11、D3D12、Vulkan 启动和退出。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +703,产品验收,选择、多选、Inspector、Scene/Prefab、Undo/Redo、保存、关闭重开。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +704,产品验收,Play→停止→Edit,焦点、输入、History 隔离正确。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +705,产品验收,Canvas、Rendering2D 开发态和安装态脚本/Drawer。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +706,产品验收,插件源变化时正式导出包,不编辑包内容。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +710,产品验收,现有 ARM64 CI 执行托管、原生和可运行产品检查。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +711,产品验收,人工 Metal、真实显示器、交互体验单列未执行。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"The requirement to distinguish simulated/native automated results from unexecuted physical/manual checks is satisfied; physical displays, OS IME and manual Mac are NOT claimed passed." +712,产品验收,既有 Metal warning 保留原始结果,不静默忽略,也不未经证据归责本轮或 BGCS。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +716,CI 分工,PR CI:架构、XML、Wiki、契约、确定性布局/输入、构建、产品 smoke。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +717,CI 分工,完整 lifecycle suite 属于正式验收,保留进程隔离。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +718,CI 分工,性能在固定 Windows 机器执行并上传证据;共享 runner 不代替稳定环境比较。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +719,CI 分工,最终通过结果必须对应同一候选源码状态。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +720,CI 分工,不把不同提交的成功结果拼成最终通过。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +728,交付物,总 issue、全部专项 issue 与原计划条目的映射。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +729,交付物,每项实现提交、删除项、公开 API 必要性和当前文件树。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +730,交付物,四仓库调用方及正式插件包状态。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +731,交付物,十一项质量维度逐项评价。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +732,交付物,测试、视觉、生命周期、性能和产品证据。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +733,交付物,未执行设备项及独立问题。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +734,交付物,各 issue 的英文验收结论和 PR 关闭关联。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +738,PR #26 转 Ready 的条件,E01–E12、#16、#19 的适用验收全部通过。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +739,PR #26 转 Ready 的条件,#13 回归继续通过。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +740,PR #26 转 Ready 的条件,#18 本轮相关语义审查完成。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +741,PR #26 转 Ready 的条件,无已知新增编译错误、架构违规、错误缓存发布或所有权泄漏。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +742,PR #26 转 Ready 的条件,原计划不存在无 issue、无 owner、无证据或仅靠文字宣布完成的条目。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +743,PR #26 转 Ready 的条件,最终 CI 完成,报告准确对应其 revision。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,待验收,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Delivery/CI condition was pending when this immutable source report was authored; the exact-head attachment records its final resolution. Not an unresolved local feature failure. +767,提交与资源规范,当前分支增量提交,保留用户已有修改。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +768,提交与资源规范,author、committer 均为 `Aaron Liao `。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +769,提交与资源规范,不添加 co-author,不使用 Codex/bot 提交身份。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +770,提交与资源规范,Commit、issue、PR 标题均符合 `type(scope): description`。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +771,提交与资源规范,Issue、PR 正文全部英文。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +772,提交与资源规范,不重复创建 PR,不自动合并 master。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +773,提交与资源规范,大型验收优先使用 D 盘独立输出。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +774,提交与资源规范,每阶段仅清理明确 owner、无活动读取者的本次临时产物。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,Known owner maintenance only; five previously policy-rejected diagnostic runtime directories are retained without bypass. No claimed reclamation of their logical bytes. +775,提交与资源规范,不删除有效当前产物、用户内容、SDK 或 NuGet 全局缓存。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +776,提交与资源规范,分别记录实际空间和逻辑大小。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Reviewed requirement against actual owner, public regression and complete candidate results; profiles overlap." +777,提交与资源规范,合并后检查各 `Fixes` issue 是否正确关闭,未完成项不得误关。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,明确不适用,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338,Post-merge audit is conditional on a future user-authorized master merge. No automatic merge or premature issue closure. +778,提交与资源规范,按规范尝试提示音;不可用时说明。,39,"Current per-owner API contracts, design standard and final report",0974c317;163c1938;5e329bbe,通过,"Final architecture/XML/Wiki/project coverage;Eleven quality dimensions and complete requirement mapping;English issue verification, individual Fixes and partial Refs #18",tests/Invoke-EditorUiAcceptance.ps1; frozen candidate preparation/matrices; tests/Invoke-WikiChecks.ps1,local-acceptance.json; test-cases.csv; per-issue evidence; final report,"Specified Glass.aiff is absent on this Windows environment; silent operation respected, no substitute sound." diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-source.json b/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-source.json new file mode 100644 index 000000000..a8e762a1a --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/original-plan-source.json @@ -0,0 +1,23 @@ +{ + "plan": "C:\\Users\\23842\\Desktop\\InnoEngine_Editor_UI_Plan.md", + "sha256": "cbb7d50a26a08e4a0d50c7e0f9aabe6c8538ae3fbe229957d2094a4b60325d9e", + "scope": "Prepared clause-level traceability; no verification outcome inferred from a mapping.", + "requirements": 307, + "issues": [ + 16, + 19, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39 + ] +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/original-ui.png b/records/architecture/acceptance/editor-ui-2026-10-30/original-ui.png new file mode 100644 index 000000000..5befbaff9 Binary files /dev/null and b/records/architecture/acceptance/editor-ui-2026-10-30/original-ui.png differ diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/ownership-review.md b/records/architecture/acceptance/editor-ui-2026-10-30/ownership-review.md new file mode 100644 index 000000000..0b481c117 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/ownership-review.md @@ -0,0 +1,70 @@ +# Final ownership review at dd5979c6 + +Source review at the production candidate. Complete local gates now pass; the final report and exact delivery-head attachment provide acceptance/CI scope. + +## Actual authority and retirement + +Input flows through the Core Events adapter into one EditorUiSession, the current control or Action request, the owning feature and its existing domain mutation/History protocol. EditorHostResourceStack's List is a reverse cleanup stack for one Host, not an event dispatcher. Action registrations are generation-owned discovery records, not another event bus. No changed UI owner introduces a listener registry or live-object authority. + +The menu template retains neutral IDs, strings, ordering and immutable path structure. Dynamic menu sources build request-local contributions and publish them after successful callbacks. Availability and execution query the current target; shortcut indexes select candidate Action IDs without retaining request targets. The catalog clears its generation indexes when replacing extensions. Logger materializes exception arguments into neutral text synchronously. This keeps a failed extension's exception object out of the long-lived diagnostic record. + +GraphCanvasLayout is an owner-thread neutral display projection. SetNodes validates the whole candidate, rejects duplicate IDs before publication and reuses equivalent immutable node/port values. It neither resolves live objects nor owns graph mutation. Reordering remains draw-order state. Controllers/features own document changes and History; custom drawing is frame-scoped. The independent non-Shader public consumer is necessary evidence of reuse rather than a nominal interface. + +SceneStore already owns the attachment structure snapshot. Its source-ID projection is EngineObject-to-current-persistent-ID serialization input, never persistent-ID-to-live-object resolution. Core identity rebinding is checked on every capture. A changed identity replaces the read-only projection instead of mutating an already borrowed graph write. Structure invalidation drops the snapshot. Systems have another lifecycle: SceneGraphSourceIds indexes them only for the current write, preventing the structure cache from pinning removed Systems. Subtree and Prefab mappings retain their separate necessary graph-local semantics. + +ScenePropertyReferenceScope pins the exact current allocator and Scene runtime identity for one synchronous property operation. Each encountered object is resolved and checked through Core Identity, including domain, exact registration, liveness and Scene ownership. Neutral Guid aliases are validated against that authority and never cache live targets. Missing, destroyed, foreign and colliding targets reject. A constructed but not yet loaded Scene remains valid when its current public ownership contract is valid. Full graph maps remain necessary for graph-local construction and are not silently substituted. + +Core Serialization owns capture proof, immutable nodes and canonical output. Scene supplies complete neutral row inputs; arbitrary converters, unsupported metadata, Missing and Prefab paths still use the same authoritative writer. The borrowed proof builder retires after its callback, with current metadata/converter/identity checks. Scratch arrays are borrowed from the BCL and returned cleared. An immutable node's memoized canonical block is bounded at 64 KiB. No snapshot stores source objects, plugin Type, converter, callback or ALC. No persistent revision/schema field is added. Ordinary serialization does not call optional snapshot proof hooks. + +Stats uses EditorStatistics as authority. FrameStatisticsSampler is a Panel-owned numeric presentation buffer: at most 120 samples, a 0.5-second interval and a 60-second window. A discontinuity resets it. Console derives a filtered view from existing Diagnostics occurrences and groups; it does not own a second log store or object identity protocol. + +The standard Host owns the default layout resource. Editor Core consumes a neutral readable document and Core IO publishes it atomically only when absent, preserving an intentionally empty/custom document. Normal shutdown captures Module/Panel state and current layout before final atomic save. Selection, History, runtime references and compilation intermediates stay transient. + +FileByteDocumentStore.Read owns one rename-compatible FileStream and a newly allocated exact output byte array. FileShare.Read|Delete permits the existing atomic publisher protocol while the opened handle pins one complete file. This is a BCL file-sharing correction, with no production OS branch, retry, swallowed cleanup error or changed publisher algorithm. + +The negative loaded-package fixture owns its physical image in an existing isolated public SDK child. The child still physically loads the altered same-name/same-MVID DLL, rejects content, and verifies weak Assembly/context collection through full GC/finalizer/full GC. Its success marker follows those assertions. The parent deletes its fixture only after successful child exit. This is a test resource boundary, not a BGCS workaround or weaker loading mode. + +## Explicit extension work + +- A new Action/menu/toolbar contribution uses existing stable metadata and behavior discovery, without editing a central Panel switch. +- A new graph domain implements its controller/semantic operations and supplies neutral display nodes and ports to Graph.ImGui; the shared presenter does not acquire that domain's type knowledge. +- A new native product platform supplies its existing platform/provider contribution. Changed UI and Scripting features do not select engine platforms or backends. General filesystem host mechanics remain inside Core IO's existing portable primitive boundary: Windows create-only publication uses the SDK no-replace move; Unix uses public POSIX link on a complete same-directory candidate, with explicit unsupported-capability/filesystem failure. FileSystemRename therefore does contain a Windows/POSIX distinction. This is separate from product SDK, ABI, backend and deployment policy, which remain platform-owned. +- A new serialization provider can decline optional proof and retain exact normal capture. It does not need a Scene reference or private reflection. A provider claiming reuse must provide the complete declared capability. +- A new Panel owns its transient session state and semantic persistence hooks, uses existing Identity/Events/History and supplies failure/retirement tests. It must not create a foundational manager to avoid those contracts. + +## Source integrity observations + +The automated source inventory reports 241 tracked projects, 30 Editor projects, clean engine/companion/BGCS checkouts and no master-to-candidate BGCS/vendor/generated-binding/bindgen/dependency-lock change. All branch commits use Aaron Liao as author and committer, with no co-author trailer. Three import scan hits are intentional negative test text: rejected script global using, rejected implicit import XML, and rejected MSBuild Using XML. They are not production imports. + +Four baseline BGCS 4.1.2 package archives retain their pre-implementation SHA-256, and 126 installed archive entries match their package contents. Package container metadata is distinguished from runtime assets. This package check, source audit, actual restored/loaded identity tests and formal generation logs are separate evidence scopes. + +Complete local source/product/test, strict lifecycle, visual-content, controlled quantitative review and Wiki results are attached in the final report. Exact delivery-head CI and Ready state are signed in its PR attachment. Physical displays, manual Mac interaction and OS IME operation remain explicitly outside automated simulated-metric claims. + +## Necessary public boundary review (#18 subset) + +| Boundary | Necessity and ownership | Failure / lifetime contract | +|---|---|---| +| GraphCanvasNode/Port/Layout and pointer input/result/controller | Another graph domain in another assembly supplies neutral geometry and receives intents without referencing Shader | Candidate validation precedes replacement; frame callbacks are not cached; document controller retains mutation/History authority | +| ScriptCompilationInputs and compiler options injection | Host transfers the actual selected SDK's evaluated immutable target/reference/symbol/compiler facts to the runtime compiler and IDE without leaking Build/MSBuild types | Missing/inconsistent inputs, cancellation and failed evaluation do not publish a valid compilation artifact; content identity invalidates products | +| SerializationSnapshot/CaptureSnapshot and per-member/array-row writer input | External owners retain neutral canonical state and optionally certify complete input facts through the same Writer | Ordinary capture remains authoritative; unsupported proof declines reuse; callback borrows retire synchronously; partial errors cannot publish false clean state | +| Converter capture hook, borrowed key builder and metadata capability | Replaceable Converter/metadata providers can explicitly certify complete pure input without a central Scene/plugin allowlist | Exact metadata/converter/current-identity guards remain; mutation, wrong thread, retired builder and ambiguous/contextual effects reject or disable reuse through documented semantics | +| Optional initial layout document on EditorContext | Standard composition supplies its layout while Core remains unaware of concrete standard Panels | Only absent files are initialized; existing empty/custom bytes survive; atomic creation/replacement use Core IO; pure selection/History do not persist | +| AtomicFile.IsCandidatePath | The external build-input owner recognizes the reserved candidate naming protocol of the existing Core atomic writer without duplicating it | Recognition neither establishes ownership nor authorizes deletion; only the complete reserved marker/operation identity matches, and permanent authored files remain inputs | +| Existing presentation and drop/interaction borrows | External controls and plugins compose the same current Session/input/domain contracts | Thread/frame/window/context/generation validation applies; request targets are synchronous borrows; persistent routes use Identity, not retained live objects | + +Menu templates, shortcut indexes, Console filtering, Stats sampling, Scene source-ID projections and canonical block caches remain internal. Public visibility was not widened for tests. English XML and current Wiki describe these boundaries; the complete local signature/example gate now passes. This is not a repository-wide semantic-documentation completion claim for #18. + +## Final corrected-source execution review + +At `5e329bbe`, the uncalled HierarchySelection.DeleteObject route was removed with its unused context parameters/imports. Real deletion remains in the Action/feature path through SceneEdits.DeleteGameObjects and the same History/identity protocol. This is actual dead-path removal, not a forwarding facade or a changed user action. + +At `dd5979c6`, both Compiler cache commits now use the existing Core atomic directory transaction. Complete validation and staging remain Compiler responsibilities. Absent destinations use Publish; invalid existing trees use Install instead of destructive best-effort deletion. The existing compilation token reaches both boundaries. Install adds one optional token, no compatibility overload, and restores an evacuated old tree using an uncanceled rollback. No postcommit cancellation undoes a complete cache. Existing DirectoryCleanupException distinguishes committed installation from later cleanup failure, and AggregateException preserves both failures and the old-tree location. Compiler gains only an implementation-only Core.IO reference, not a Build/MSBuild/platform/BGCS dependency. Private staging retirement remains separate. + +Public Compiler regression evidence is red before/green after for temporary staged readers. Compiler17 and CoreIO41 pass, including cancellation after old-tree evacuation, persistent rejection with another generation unchanged, exact whole-tree preservation for invalid-cache replacement, and explicit repair. The original handle owner is unknown and is not attributed to BGCS/Roslyn/antivirus. Five independent cold/warm formal requests, all complete corrected-source profiles/companions/six products, controlled quantitative review and full Wiki now pass. Focused tests were not used as a substitute. Exact delivery-head CI remains separately signed in the final attachment. + +The Core Install token is necessary because actual asynchronous Compiler operations already own cancellation. It completes one existing transaction boundary rather than exposing Compiler internals or adding a second filesystem policy. Existing two-argument source callers continue to use the same method with its default token, and are compiled again in the complete source/product graph. The unchanged Core filesystem retry budget is not increased. + + +## Final local verification authority + +See ../EDITOR_UI_FINAL_ACCEPTANCE_2026_10_30.md and local-acceptance.json for the complete candidate results, performance-review.json for every initial comparison and graph-tail-summary.json for the predeclared longer tail review. Exact containing-commit CI and Ready state: https://github.com/Wolfiora/InnoEngine/pull/26#issuecomment-6516741338. Physical/manual limits remain unchanged. diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/performance-review.json b/records/architecture/acceptance/editor-ui-2026-10-30/performance-review.json new file mode 100644 index 000000000..0142ea651 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/performance-review.json @@ -0,0 +1,300 @@ +{ + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "status": "Passed", + "unresolved": [], + "reviewedComparisons": 68, + "initialTrials": 150, + "followupTrials": 10, + "initialSummarySha256": "bda0af282ada8860b38cf871ded6d7b9cc61bef7314da24cf306830984e0bb75", + "followupSummarySha256": "c34fff55878a619d7ba013e2d6aa31d00aa8f3002bae502ac55e7248e2d5cb41", + "followupEvidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\candidate-dd5979c6-graph-tail\\graph-tail-summary.json", + "decision": "The only initial flags concern the same graph tail, where ten measured frames make P95 the maximum. A predeclared additional five alternating process pairs used the unchanged existing fixture with its normal five repetitions (50 measured frames/process). No pair exceeds +10% for P95; median P95 ratio 0.976476, CPU ratio 0.921875, frame median ratio 1.010766 and allocation ratio 0.737942. The initial +19.98% observation remains in the report, not erased or called a passing initial trial. No persistent >10% CPU/P95 or hot allocation regression is established under this repeatability criterion.", + "initialFlags": [ + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "maximumMs", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "p95Ms", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + } + ], + "followupComparisons": [ + { + "metric": "medianMs", + "values": { + "original": [ + 32.14015, + 28.0138, + 23.884900000000002, + 28.468899999999998, + 33.80315 + ], + "current": [ + 29.2441, + 22.2517, + 28.7754, + 23.92, + 28.86425 + ] + }, + "medians": { + "original": 28.468899999999998, + "current": 28.7754 + }, + "medianRatio": 1.0107661342728382, + "pairedRatios": [ + 0.9098930776614297, + 0.7943120890418294, + 1.204752793606002, + 0.8402151119291579, + 0.8538923147694815 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "p95Ms", + "values": { + "original": [ + 38.1169, + 32.4755, + 32.8391, + 31.4734, + 46.1429 + ], + "current": [ + 32.9627, + 27.737, + 32.0666, + 32.2866, + 31.9508 + ] + }, + "medians": { + "original": 32.8391, + "current": 32.0666 + }, + "medianRatio": 0.9764762128072937, + "pairedRatios": [ + 0.8647791399615393, + 0.8540900063124509, + 0.9764762128072937, + 1.0258376915109266, + 0.6924315550171316 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "maximumMs", + "values": { + "original": [ + 44.106, + 37.801, + 37.4046, + 33.0214, + 63.8343 + ], + "current": [ + 33.2867, + 28.4866, + 35.6308, + 41.3669, + 34.8923 + ] + }, + "medians": { + "original": 37.801, + "current": 34.8923 + }, + "medianRatio": 0.9230523002036982, + "pairedRatios": [ + 0.7546977735455495, + 0.753593820269305, + 0.9525780251626805, + 1.2527300477871925, + 0.5466073881909882 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 45.3125, + 34.6875, + 32.5, + 40.0, + 50.0 + ], + "current": [ + 36.875, + 28.75, + 40.0, + 30.3125, + 36.875 + ] + }, + "medians": { + "original": 40.0, + "current": 36.875 + }, + "medianRatio": 0.921875, + "pairedRatios": [ + 0.8137931034482758, + 0.8288288288288288, + 1.2307692307692308, + 0.7578125, + 0.7375 + ], + "repeated": 1, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4506232.8, + 4469765.44, + 4469715.04, + 4469812.0, + 4543119.52 + ], + "current": [ + 3335401.28, + 3282931.68, + 3298463.84, + 3298407.52, + 3298463.84 + ] + }, + "medians": { + "original": 4469812.0, + "current": 3298463.84 + }, + "medianRatio": 0.7379424101058388, + "pairedRatios": [ + 0.7401750925961925, + 0.7344751584995923, + 0.7379584180382112, + 0.7379298100233298, + 0.7260350130520009 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + }, + { + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 4506881.12, + 4470309.92, + 4470363.2, + 4470461.28, + 4543768.32 + ], + "current": [ + 3335667.04, + 3283197.44, + 3298523.2, + 3298467.36, + 3298523.68 + ] + }, + "medians": { + "original": 4470461.28, + "current": 3298523.2 + }, + "medianRatio": 0.7378485112390908, + "pairedRatios": [ + 0.7401275851713612, + 0.7344451500579628, + 0.7378646996736193, + 0.7378360203580602, + 0.7259445129455896 + ], + "repeated": 0, + "reviewRequired": false, + "scope": "Maximum is retained as an outlier observation; acceptance specifies CPU/P95 and allocation, not an absolute maximum guarantee." + } + ], + "productionChangeForFollowup": false, + "otherReview": [ + "Every other initial comparison was reviewed, including all paired values and scope; no repeatability flag remains.", + "Process CPU zero for very short command batches is quantization, not a claimed 100% CPU gain.", + "Product first-application wall time, source-activation CPU-frame interval and complete-run main CPU have different scopes; child CPU/GPU completion/physical display are not inferred.", + "Current source-activation CPU-frame median is 56.65s versus 52.72s (+7.46%), within the stated repeatable >10% gate; retain the cost, not claim startup uniformly improved.", + "2k graph P95 around32ms is not a 60FPS guarantee; 10k asset search around73ms is an explicit search operation, not a steady-frame guarantee.", + "Ten absolute complete-inspection processes separately satisfy median<16ms/P95<25ms/<8MiB at the unchanged100ms policy; worst observedP95 24.5695ms has limited margin.", + "All initial outliers, GC counts, counts of work, raw timings and all followup outliers remain preserved. No best-run selection, fixture validation removal, timeout relaxation or source/API change was made." + ] +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/performance-summary.json b/records/architecture/acceptance/editor-ui-2026-10-30/performance-summary.json new file mode 100644 index 000000000..43fb46b8e --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/performance-summary.json @@ -0,0 +1,5010 @@ +{ + "scope": "Five alternating producer process pairs per workload. Command timing is batch means, not frame P95. First application frame uses official boot and launch wall timestamps. Source activation is separate and uses the second public module update with monotonic timestamps; neither is GPU completion or physical display. Main-process CPU excludes child processes. Raw trials and all outliers are retained. Flags require explicit review, not automatic waiver.", + "sourceCandidate": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "trials": [ + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 4387.3229, + "p95Ms": 4539.3549, + "maximumMs": 4539.3549, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709018286.4, + "cpuMsPerOperation": 4356.25, + "totalAllocatedBytesPerOperation": 709025148.8, + "gen0Collections": 825, + "gen1Collections": 107, + "gen2Collections": 12 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-0-original" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 2.0164, + "p95Ms": 19.7131, + "maximumMs": 19.7131, + "measuredOperations": 10, + "allocatedBytesPerOperation": 277432.0, + "cpuMsPerOperation": 3.125, + "totalAllocatedBytesPerOperation": 277497.6, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-0-current" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 2.1885000000000003, + "p95Ms": 19.5684, + "maximumMs": 19.5684, + "measuredOperations": 10, + "allocatedBytesPerOperation": 277432.0, + "cpuMsPerOperation": 3.125, + "totalAllocatedBytesPerOperation": 277497.6, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-1-current" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 4380.0702, + "p95Ms": 4506.4856, + "maximumMs": 4506.4856, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709016743.2, + "cpuMsPerOperation": 4396.875, + "totalAllocatedBytesPerOperation": 709023605.6, + "gen0Collections": 825, + "gen1Collections": 109, + "gen2Collections": 12 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-1-original" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 4228.7503, + "p95Ms": 4523.6256, + "maximumMs": 4523.6256, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709019120.8, + "cpuMsPerOperation": 4265.625, + "totalAllocatedBytesPerOperation": 709026549.6, + "gen0Collections": 826, + "gen1Collections": 114, + "gen2Collections": 13 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-2-original" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 2.2058999999999997, + "p95Ms": 2.3837, + "maximumMs": 2.3837, + "measuredOperations": 10, + "allocatedBytesPerOperation": 277432.0, + "cpuMsPerOperation": 3.125, + "totalAllocatedBytesPerOperation": 277497.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-2-current" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 2.02435, + "p95Ms": 19.252, + "maximumMs": 19.252, + "measuredOperations": 10, + "allocatedBytesPerOperation": 277432.0, + "cpuMsPerOperation": 4.6875, + "totalAllocatedBytesPerOperation": 277497.6, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-3-current" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 4441.211499999999, + "p95Ms": 4675.2377, + "maximumMs": 4675.2377, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709019076.8, + "cpuMsPerOperation": 4459.375, + "totalAllocatedBytesPerOperation": 709027652.0, + "gen0Collections": 828, + "gen1Collections": 115, + "gen2Collections": 15 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-3-original" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 4436.56365, + "p95Ms": 4651.0813, + "maximumMs": 4651.0813, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709019501.6, + "cpuMsPerOperation": 4440.625, + "totalAllocatedBytesPerOperation": 709030908.8, + "gen0Collections": 833, + "gen1Collections": 98, + "gen2Collections": 20 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-4-original" + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 1.9738, + "p95Ms": 19.093, + "maximumMs": 19.093, + "measuredOperations": 10, + "allocatedBytesPerOperation": 277474.4, + "cpuMsPerOperation": 3.125, + "totalAllocatedBytesPerOperation": 277540.0, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-4-current" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 3431.3217999999997, + "p95Ms": 3490.0804, + "maximumMs": 3490.0804, + "measuredOperations": 10, + "allocatedBytesPerOperation": 249231740.8, + "cpuMsPerOperation": 3445.3125, + "totalAllocatedBytesPerOperation": 249231806.4, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-0-original" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 16.5183, + "p95Ms": 26.502, + "maximumMs": 26.502, + "measuredOperations": 10, + "allocatedBytesPerOperation": 918679.2, + "cpuMsPerOperation": 18.75, + "totalAllocatedBytesPerOperation": 918778.4, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-0-current" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 15.38935, + "p95Ms": 24.6364, + "maximumMs": 24.6364, + "measuredOperations": 10, + "allocatedBytesPerOperation": 918679.2, + "cpuMsPerOperation": 17.1875, + "totalAllocatedBytesPerOperation": 918778.4, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-1-current" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 3409.38115, + "p95Ms": 3450.0804, + "maximumMs": 3450.0804, + "measuredOperations": 10, + "allocatedBytesPerOperation": 249231740.8, + "cpuMsPerOperation": 3412.5, + "totalAllocatedBytesPerOperation": 249231806.4, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-1-original" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 3451.2929, + "p95Ms": 3522.6453, + "maximumMs": 3522.6453, + "measuredOperations": 10, + "allocatedBytesPerOperation": 249231740.8, + "cpuMsPerOperation": 3448.4375, + "totalAllocatedBytesPerOperation": 249231806.4, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-2-original" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 15.244599999999998, + "p95Ms": 24.5067, + "maximumMs": 24.5067, + "measuredOperations": 10, + "allocatedBytesPerOperation": 918679.2, + "cpuMsPerOperation": 15.625, + "totalAllocatedBytesPerOperation": 918778.4, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-2-current" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 15.799199999999999, + "p95Ms": 26.7983, + "maximumMs": 26.7983, + "measuredOperations": 10, + "allocatedBytesPerOperation": 918679.2, + "cpuMsPerOperation": 17.1875, + "totalAllocatedBytesPerOperation": 918778.4, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-3-current" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 3428.9718, + "p95Ms": 3486.708, + "maximumMs": 3486.708, + "measuredOperations": 10, + "allocatedBytesPerOperation": 249231740.8, + "cpuMsPerOperation": 3431.25, + "totalAllocatedBytesPerOperation": 249231806.4, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-3-original" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 3441.705, + "p95Ms": 3507.2268, + "maximumMs": 3507.2268, + "measuredOperations": 10, + "allocatedBytesPerOperation": 249231740.8, + "cpuMsPerOperation": 3451.5625, + "totalAllocatedBytesPerOperation": 249231806.4, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-4-original" + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 15.1674, + "p95Ms": 24.9492, + "maximumMs": 24.9492, + "measuredOperations": 10, + "allocatedBytesPerOperation": 918679.2, + "cpuMsPerOperation": 17.1875, + "totalAllocatedBytesPerOperation": 918778.4, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-4-current" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 23.1096, + "p95Ms": 24.7994, + "maximumMs": 24.7994, + "measuredOperations": 10, + "allocatedBytesPerOperation": 4364820.0, + "cpuMsPerOperation": 25.0, + "totalAllocatedBytesPerOperation": 4365382.4, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-0-original" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 24.46985, + "p95Ms": 27.3035, + "maximumMs": 27.3035, + "measuredOperations": 10, + "allocatedBytesPerOperation": 3277376.0, + "cpuMsPerOperation": 35.9375, + "totalAllocatedBytesPerOperation": 3277441.6, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-0-current" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 29.15775, + "p95Ms": 39.7908, + "maximumMs": 39.7908, + "measuredOperations": 10, + "allocatedBytesPerOperation": 3277376.0, + "cpuMsPerOperation": 32.8125, + "totalAllocatedBytesPerOperation": 3277441.6, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-1-current" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 23.68225, + "p95Ms": 25.438, + "maximumMs": 25.438, + "measuredOperations": 10, + "allocatedBytesPerOperation": 4364780.0, + "cpuMsPerOperation": 25.0, + "totalAllocatedBytesPerOperation": 4365841.6, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-1-original" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 23.89115, + "p95Ms": 29.9459, + "maximumMs": 29.9459, + "measuredOperations": 10, + "allocatedBytesPerOperation": 4364723.2, + "cpuMsPerOperation": 28.125, + "totalAllocatedBytesPerOperation": 4365784.8, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-2-original" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 24.0661, + "p95Ms": 36.6837, + "maximumMs": 36.6837, + "measuredOperations": 10, + "allocatedBytesPerOperation": 3277376.0, + "cpuMsPerOperation": 26.5625, + "totalAllocatedBytesPerOperation": 3277441.6, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-2-current" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 25.95035, + "p95Ms": 35.9288, + "maximumMs": 35.9288, + "measuredOperations": 10, + "allocatedBytesPerOperation": 3277376.0, + "cpuMsPerOperation": 26.5625, + "totalAllocatedBytesPerOperation": 3277441.6, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-3-current" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 25.028750000000002, + "p95Ms": 35.0849, + "maximumMs": 35.0849, + "measuredOperations": 10, + "allocatedBytesPerOperation": 4364528.0, + "cpuMsPerOperation": 29.6875, + "totalAllocatedBytesPerOperation": 4365589.6, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-3-original" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 23.5582, + "p95Ms": 32.6808, + "maximumMs": 32.6808, + "measuredOperations": 10, + "allocatedBytesPerOperation": 4364701.6, + "cpuMsPerOperation": 26.5625, + "totalAllocatedBytesPerOperation": 4365763.2, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-4-original" + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 23.49785, + "p95Ms": 33.9387, + "maximumMs": 33.9387, + "measuredOperations": 10, + "allocatedBytesPerOperation": 3277376.0, + "cpuMsPerOperation": 26.5625, + "totalAllocatedBytesPerOperation": 3277441.6, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-4-current" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 10467.925599999999, + "p95Ms": 10649.9995, + "maximumMs": 10649.9995, + "measuredOperations": 10, + "allocatedBytesPerOperation": 754213838.4, + "cpuMsPerOperation": 10417.1875, + "totalAllocatedBytesPerOperation": 754215603.2, + "gen0Collections": 898, + "gen1Collections": 17, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-original" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 3527.7773500000003, + "p95Ms": 3786.8515, + "maximumMs": 3786.8515, + "measuredOperations": 10, + "allocatedBytesPerOperation": 246352774.4, + "cpuMsPerOperation": 3554.6875, + "totalAllocatedBytesPerOperation": 246353388.8, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-original" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 3935.62945, + "p95Ms": 4082.0511, + "maximumMs": 4082.0511, + "measuredOperations": 10, + "allocatedBytesPerOperation": 726426723.2, + "cpuMsPerOperation": 3935.9375, + "totalAllocatedBytesPerOperation": 726434374.4, + "gen0Collections": 831, + "gen1Collections": 109, + "gen2Collections": 12 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-original" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 4518.9755000000005, + "p95Ms": 4751.4065, + "maximumMs": 4751.4065, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709020653.6, + "cpuMsPerOperation": 4553.125, + "totalAllocatedBytesPerOperation": 709029572.0, + "gen0Collections": 826, + "gen1Collections": 111, + "gen2Collections": 14 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-original" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 73.1636, + "p95Ms": 79.7892, + "maximumMs": 79.7892, + "measuredOperations": 10, + "allocatedBytesPerOperation": 7873499.2, + "cpuMsPerOperation": 92.1875, + "totalAllocatedBytesPerOperation": 7875357.6, + "gen0Collections": 8, + "gen1Collections": 6, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-current" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 1.4435, + "p95Ms": 1.562, + "maximumMs": 1.562, + "measuredOperations": 10, + "allocatedBytesPerOperation": 300984.0, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 301032.0, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-current" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 1.4398, + "p95Ms": 1.5519, + "maximumMs": 1.5519, + "measuredOperations": 10, + "allocatedBytesPerOperation": 283193.6, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 283241.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-current" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 14.714649999999999, + "p95Ms": 16.4719, + "maximumMs": 16.4719, + "measuredOperations": 10, + "allocatedBytesPerOperation": 938392.8, + "cpuMsPerOperation": 15.625, + "totalAllocatedBytesPerOperation": 938440.8, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-current" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 1.4905, + "p95Ms": 1.6529, + "maximumMs": 1.6529, + "measuredOperations": 10, + "allocatedBytesPerOperation": 283193.6, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 283241.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-current" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 1.4158, + "p95Ms": 1.4659, + "maximumMs": 1.4659, + "measuredOperations": 10, + "allocatedBytesPerOperation": 300984.0, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 301032.0, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-current" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 68.55875, + "p95Ms": 77.1866, + "maximumMs": 77.1866, + "measuredOperations": 10, + "allocatedBytesPerOperation": 7873821.6, + "cpuMsPerOperation": 92.1875, + "totalAllocatedBytesPerOperation": 7875680.0, + "gen0Collections": 8, + "gen1Collections": 5, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-current" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 14.05985, + "p95Ms": 15.8653, + "maximumMs": 15.8653, + "measuredOperations": 10, + "allocatedBytesPerOperation": 938392.8, + "cpuMsPerOperation": 15.625, + "totalAllocatedBytesPerOperation": 938440.8, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-current" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 10187.652600000001, + "p95Ms": 10283.1874, + "maximumMs": 10283.1874, + "measuredOperations": 10, + "allocatedBytesPerOperation": 754213674.4, + "cpuMsPerOperation": 10187.5, + "totalAllocatedBytesPerOperation": 754215439.2, + "gen0Collections": 898, + "gen1Collections": 16, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-original" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 3366.68745, + "p95Ms": 3396.2747, + "maximumMs": 3396.2747, + "measuredOperations": 10, + "allocatedBytesPerOperation": 246353179.2, + "cpuMsPerOperation": 3357.8125, + "totalAllocatedBytesPerOperation": 246353793.6, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-original" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 4060.34005, + "p95Ms": 4425.4523, + "maximumMs": 4425.4523, + "measuredOperations": 10, + "allocatedBytesPerOperation": 726426868.8, + "cpuMsPerOperation": 4087.5, + "totalAllocatedBytesPerOperation": 726435787.2, + "gen0Collections": 833, + "gen1Collections": 107, + "gen2Collections": 14 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-original" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 4469.9085, + "p95Ms": 4741.383, + "maximumMs": 4741.383, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709021073.6, + "cpuMsPerOperation": 4479.6875, + "totalAllocatedBytesPerOperation": 709029992.0, + "gen0Collections": 826, + "gen1Collections": 111, + "gen2Collections": 14 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-original" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 4538.4312, + "p95Ms": 4606.4219, + "maximumMs": 4606.4219, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709016406.4, + "cpuMsPerOperation": 4528.125, + "totalAllocatedBytesPerOperation": 709024057.6, + "gen0Collections": 824, + "gen1Collections": 114, + "gen2Collections": 12 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-original" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 10344.95765, + "p95Ms": 11417.2039, + "maximumMs": 11417.2039, + "measuredOperations": 10, + "allocatedBytesPerOperation": 754213033.6, + "cpuMsPerOperation": 10432.8125, + "totalAllocatedBytesPerOperation": 754214796.0, + "gen0Collections": 899, + "gen1Collections": 17, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-original" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 3426.88195, + "p95Ms": 3624.0777, + "maximumMs": 3624.0777, + "measuredOperations": 10, + "allocatedBytesPerOperation": 246353407.2, + "cpuMsPerOperation": 3464.0625, + "totalAllocatedBytesPerOperation": 246354021.6, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-original" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 3585.1115, + "p95Ms": 3628.8166, + "maximumMs": 3628.8166, + "measuredOperations": 10, + "allocatedBytesPerOperation": 726428257.6, + "cpuMsPerOperation": 3590.625, + "totalAllocatedBytesPerOperation": 726434620.0, + "gen0Collections": 829, + "gen1Collections": 127, + "gen2Collections": 10 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-original" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 91.09275, + "p95Ms": 123.7802, + "maximumMs": 123.7802, + "measuredOperations": 10, + "allocatedBytesPerOperation": 7912272.0, + "cpuMsPerOperation": 121.875, + "totalAllocatedBytesPerOperation": 7914125.6, + "gen0Collections": 8, + "gen1Collections": 4, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-current" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 15.3498, + "p95Ms": 18.1181, + "maximumMs": 18.1181, + "measuredOperations": 10, + "allocatedBytesPerOperation": 938528.0, + "cpuMsPerOperation": 18.75, + "totalAllocatedBytesPerOperation": 938576.0, + "gen0Collections": 2, + "gen1Collections": 1, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-current" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 1.5540500000000002, + "p95Ms": 1.9986, + "maximumMs": 1.9986, + "measuredOperations": 10, + "allocatedBytesPerOperation": 300984.0, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 301032.0, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-current" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 1.77805, + "p95Ms": 1.8761, + "maximumMs": 1.8761, + "measuredOperations": 10, + "allocatedBytesPerOperation": 283193.6, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 283241.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-current" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 20.20265, + "p95Ms": 21.9723, + "maximumMs": 21.9723, + "measuredOperations": 10, + "allocatedBytesPerOperation": 938427.2, + "cpuMsPerOperation": 23.4375, + "totalAllocatedBytesPerOperation": 938475.2, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-current" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 1.9068, + "p95Ms": 2.1292, + "maximumMs": 2.1292, + "measuredOperations": 10, + "allocatedBytesPerOperation": 300984.0, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 301032.0, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-current" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 1.93125, + "p95Ms": 2.4219, + "maximumMs": 2.4219, + "measuredOperations": 10, + "allocatedBytesPerOperation": 283193.6, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 283241.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-current" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 72.15729999999999, + "p95Ms": 90.7394, + "maximumMs": 90.7394, + "measuredOperations": 10, + "allocatedBytesPerOperation": 7911923.2, + "cpuMsPerOperation": 96.875, + "totalAllocatedBytesPerOperation": 7913781.6, + "gen0Collections": 9, + "gen1Collections": 3, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-current" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 5904.7548, + "p95Ms": 6853.4044, + "maximumMs": 6853.4044, + "measuredOperations": 10, + "allocatedBytesPerOperation": 726670890.4, + "cpuMsPerOperation": 5992.1875, + "totalAllocatedBytesPerOperation": 726695003.2, + "gen0Collections": 857, + "gen1Collections": 141, + "gen2Collections": 38 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-original" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 6197.7714, + "p95Ms": 8356.4387, + "maximumMs": 8356.4387, + "measuredOperations": 10, + "allocatedBytesPerOperation": 246366254.4, + "cpuMsPerOperation": 6356.25, + "totalAllocatedBytesPerOperation": 246366868.8, + "gen0Collections": 297, + "gen1Collections": 11, + "gen2Collections": 1 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-original" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 5902.0731, + "p95Ms": 6654.2834, + "maximumMs": 6654.2834, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709287042.4, + "cpuMsPerOperation": 5848.4375, + "totalAllocatedBytesPerOperation": 709309252.0, + "gen0Collections": 849, + "gen1Collections": 128, + "gen2Collections": 35 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-original" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 14733.090049999999, + "p95Ms": 16053.4, + "maximumMs": 16053.4, + "measuredOperations": 10, + "allocatedBytesPerOperation": 754282373.6, + "cpuMsPerOperation": 14735.9375, + "totalAllocatedBytesPerOperation": 754285259.2, + "gen0Collections": 902, + "gen1Collections": 21, + "gen2Collections": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-original" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 4880.64335, + "p95Ms": 5163.1566, + "maximumMs": 5163.1566, + "measuredOperations": 10, + "allocatedBytesPerOperation": 246352763.2, + "cpuMsPerOperation": 4790.625, + "totalAllocatedBytesPerOperation": 246352811.2, + "gen0Collections": 294, + "gen1Collections": 6, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-original" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 14610.683, + "p95Ms": 19866.71, + "maximumMs": 19866.71, + "measuredOperations": 10, + "allocatedBytesPerOperation": 754281620.0, + "cpuMsPerOperation": 14937.5, + "totalAllocatedBytesPerOperation": 754284500.8, + "gen0Collections": 904, + "gen1Collections": 20, + "gen2Collections": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-original" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 4591.67915, + "p95Ms": 5687.0343, + "maximumMs": 5687.0343, + "measuredOperations": 10, + "allocatedBytesPerOperation": 726697597.6, + "cpuMsPerOperation": 4651.5625, + "totalAllocatedBytesPerOperation": 726726152.8, + "gen0Collections": 864, + "gen1Collections": 138, + "gen2Collections": 45 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-original" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 4497.09725, + "p95Ms": 4749.9316, + "maximumMs": 4749.9316, + "measuredOperations": 10, + "allocatedBytesPerOperation": 709291708.0, + "cpuMsPerOperation": 4503.125, + "totalAllocatedBytesPerOperation": 709315172.8, + "gen0Collections": 850, + "gen1Collections": 136, + "gen2Collections": 37 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-original" + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 1.5859, + "p95Ms": 1.7238, + "maximumMs": 1.7238, + "measuredOperations": 10, + "allocatedBytesPerOperation": 300984.0, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 301032.0, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-current" + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 1.7662, + "p95Ms": 1.9979, + "maximumMs": 1.9979, + "measuredOperations": 10, + "allocatedBytesPerOperation": 283193.6, + "cpuMsPerOperation": 1.5625, + "totalAllocatedBytesPerOperation": 283241.6, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-current" + }, + { + "kind": "input", + "workload": "assets-search", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 76.14715000000001, + "p95Ms": 80.7782, + "maximumMs": 80.7782, + "measuredOperations": 10, + "allocatedBytesPerOperation": 7912856.8, + "cpuMsPerOperation": 81.25, + "totalAllocatedBytesPerOperation": 7914708.0, + "gen0Collections": 9, + "gen1Collections": 4, + "gen2Collections": 3 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-current" + }, + { + "kind": "input", + "workload": "assets-scroll", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 18.65335, + "p95Ms": 19.5928, + "maximumMs": 19.5928, + "measuredOperations": 10, + "allocatedBytesPerOperation": 938392.8, + "cpuMsPerOperation": 20.3125, + "totalAllocatedBytesPerOperation": 938440.8, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-current" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "original", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.01352185, + "cpuMsPerOperation": 0.015625, + "allocatedBytesPerOperation": 8544.288, + "gen0": 10, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "original", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.0041922, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1608.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "original", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.0036932, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 1424.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-original.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "original", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.02743575, + "cpuMsPerOperation": 0.03125, + "allocatedBytesPerOperation": 16792.0, + "gen0": 20, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-original.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "current", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.00594675, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 4240.144, + "gen0": 5, + "gen1": 1, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "current", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.0012994, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 352.0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "current", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.00031995, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 48.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-current.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "current", + "pair": 0, + "metrics": { + "batchMeanMsPerOperation": 0.00036525, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 400.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-current.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "current", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.00499185, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 4240.144, + "gen0": 5, + "gen1": 1, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "current", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.0010549, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 352.0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "current", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.00026555000000000003, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 48.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-current.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "current", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.00019894999999999998, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 400.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-current.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "original", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.009671049999999999, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 8544.288, + "gen0": 10, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "original", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.00488945, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1608.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "original", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.0040687499999999995, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1424.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-original.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "original", + "pair": 1, + "metrics": { + "batchMeanMsPerOperation": 0.025300049999999998, + "cpuMsPerOperation": 0.03125, + "allocatedBytesPerOperation": 16792.0, + "gen0": 20, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-original.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "original", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.008250400000000001, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 8544.288, + "gen0": 10, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "original", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.0040537, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1608.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "original", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.0035343, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1424.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-original.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "original", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.024111999999999998, + "cpuMsPerOperation": 0.0234375, + "allocatedBytesPerOperation": 16792.0, + "gen0": 20, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-original.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "current", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.0054373, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 4240.144, + "gen0": 5, + "gen1": 1, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "current", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.0010749, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 352.0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "current", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.00027529999999999997, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 48.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-current.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "current", + "pair": 2, + "metrics": { + "batchMeanMsPerOperation": 0.00023035, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 400.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-current.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "current", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.0057007, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 4240.144, + "gen0": 5, + "gen1": 1, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "current", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.0011759, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 352.0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "current", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.0002673, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 48.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-current.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "current", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.00020055, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 400.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-current.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "original", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.0096234, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 8544.288, + "gen0": 10, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "original", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.004262800000000001, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1608.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "original", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.00345585, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 1424.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-original.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "original", + "pair": 3, + "metrics": { + "batchMeanMsPerOperation": 0.0243715, + "cpuMsPerOperation": 0.03125, + "allocatedBytesPerOperation": 16792.0, + "gen0": 20, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-original.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "original", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.0085225, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 8544.288, + "gen0": 10, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "original", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.0040953, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 1608.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-original.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "original", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.0037190500000000002, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 1424.0, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-original.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "original", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.0276752, + "cpuMsPerOperation": 0.03125, + "allocatedBytesPerOperation": 16792.0, + "gen0": 20, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-original.log" + }, + { + "kind": "commands", + "workload": "menu-open", + "source": "current", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.00558785, + "cpuMsPerOperation": 0.0078125, + "allocatedBytesPerOperation": 4240.144, + "gen0": 5, + "gen1": 1, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "source": "current", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.00099065, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 352.0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-current.log" + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "source": "current", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.00025795, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 48.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-current.log" + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "source": "current", + "pair": 4, + "metrics": { + "batchMeanMsPerOperation": 0.00020085, + "cpuMsPerOperation": 0.0, + "allocatedBytesPerOperation": 400.0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-current.log" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 9.67915, + "p95Ms": 29.4194, + "allocatedBytesPerOperation": 4743584.76, + "measuredOperations": 200, + "gen0": 106, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-original" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 9.63265, + "p95Ms": 30.2297, + "allocatedBytesPerOperation": 4906128.6, + "measuredOperations": 200, + "gen0": 109, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-original" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "original", + "pair": 0, + "metrics": { + "medianMs": 11.9108, + "p95Ms": 17.983, + "allocatedBytesPerOperation": 1856208.9246231157, + "measuredOperations": 199, + "gen0": 36, + "gen1": 12, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-original" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "original", + "pair": 0, + "metrics": { + "firstApplicationFrameWallMs": 24200.9671, + "sourceActivatedCpuFrameMs": 60460.0851, + "mainProcessCpuAtSourceReadyMs": 101234.375, + "completeRunWallMs": 320830.5255, + "completeRunMainCpuMs": 455812.5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-original" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 9.37575, + "p95Ms": 24.8848, + "allocatedBytesPerOperation": 1936556.2, + "measuredOperations": 200, + "gen0": 38, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-current" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 9.617650000000001, + "p95Ms": 25.6659, + "allocatedBytesPerOperation": 1954005.88, + "measuredOperations": 200, + "gen0": 40, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-current" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "current", + "pair": 0, + "metrics": { + "medianMs": 12.7545, + "p95Ms": 15.4336, + "allocatedBytesPerOperation": 1834145.3668341709, + "measuredOperations": 199, + "gen0": 36, + "gen1": 10, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-current" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "current", + "pair": 0, + "metrics": { + "firstApplicationFrameWallMs": 19943.63, + "sourceActivatedCpuFrameMs": 66056.5238, + "mainProcessCpuAtSourceReadyMs": 116093.75, + "completeRunWallMs": 304202.4103, + "completeRunMainCpuMs": 420875.0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-current" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 9.7269, + "p95Ms": 25.3644, + "allocatedBytesPerOperation": 1936436.52, + "measuredOperations": 200, + "gen0": 40, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-current" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 9.712399999999999, + "p95Ms": 25.5731, + "allocatedBytesPerOperation": 1957503.96, + "measuredOperations": 200, + "gen0": 39, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-current" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "current", + "pair": 1, + "metrics": { + "medianMs": 11.7569, + "p95Ms": 13.5499, + "allocatedBytesPerOperation": 1834009.2864321608, + "measuredOperations": 199, + "gen0": 35, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-current" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "current", + "pair": 1, + "metrics": { + "firstApplicationFrameWallMs": 8598.3295, + "sourceActivatedCpuFrameMs": 52733.859, + "mainProcessCpuAtSourceReadyMs": 115046.875, + "completeRunWallMs": 268821.8483, + "completeRunMainCpuMs": 403546.875 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-current" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 9.6667, + "p95Ms": 29.7502, + "allocatedBytesPerOperation": 4747774.16, + "measuredOperations": 200, + "gen0": 105, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-original" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 9.699649999999998, + "p95Ms": 29.7035, + "allocatedBytesPerOperation": 4774186.16, + "measuredOperations": 200, + "gen0": 107, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-original" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "original", + "pair": 1, + "metrics": { + "medianMs": 11.3259, + "p95Ms": 14.051, + "allocatedBytesPerOperation": 1856485.5879396985, + "measuredOperations": 199, + "gen0": 36, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-original" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "original", + "pair": 1, + "metrics": { + "firstApplicationFrameWallMs": 8705.0177, + "sourceActivatedCpuFrameMs": 50443.5293, + "mainProcessCpuAtSourceReadyMs": 114437.5, + "completeRunWallMs": 282493.8291, + "completeRunMainCpuMs": 437296.875 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-original" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 10.732949999999999, + "p95Ms": 33.5956, + "allocatedBytesPerOperation": 5139818.52, + "measuredOperations": 200, + "gen0": 125, + "gen1": 15, + "gen2": 15 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-original" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 9.79795, + "p95Ms": 30.5176, + "allocatedBytesPerOperation": 4775486.96, + "measuredOperations": 200, + "gen0": 115, + "gen1": 14, + "gen2": 14 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-original" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "original", + "pair": 2, + "metrics": { + "medianMs": 12.1663, + "p95Ms": 17.1537, + "allocatedBytesPerOperation": 1856907.336683417, + "measuredOperations": 199, + "gen0": 44, + "gen1": 13, + "gen2": 13 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-original" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "original", + "pair": 2, + "metrics": { + "firstApplicationFrameWallMs": 8451.0554, + "sourceActivatedCpuFrameMs": 50087.4516, + "mainProcessCpuAtSourceReadyMs": 115750.0, + "completeRunWallMs": 299000.016, + "completeRunMainCpuMs": 477906.25 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-original" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 9.58705, + "p95Ms": 25.5172, + "allocatedBytesPerOperation": 1940349.32, + "measuredOperations": 200, + "gen0": 40, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-current" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 9.6327, + "p95Ms": 25.8475, + "allocatedBytesPerOperation": 1962944.08, + "measuredOperations": 200, + "gen0": 39, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-current" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "current", + "pair": 2, + "metrics": { + "medianMs": 11.7992, + "p95Ms": 15.6227, + "allocatedBytesPerOperation": 1834056.8040201005, + "measuredOperations": 199, + "gen0": 36, + "gen1": 11, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-current" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "current", + "pair": 2, + "metrics": { + "firstApplicationFrameWallMs": 8298.5185, + "sourceActivatedCpuFrameMs": 52945.6862, + "mainProcessCpuAtSourceReadyMs": 115265.625, + "completeRunWallMs": 287553.2501, + "completeRunMainCpuMs": 427187.5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-current" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 9.6922, + "p95Ms": 25.5423, + "allocatedBytesPerOperation": 1940453.28, + "measuredOperations": 200, + "gen0": 40, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-current" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 9.70125, + "p95Ms": 25.6267, + "allocatedBytesPerOperation": 1962768.84, + "measuredOperations": 200, + "gen0": 40, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-current" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "current", + "pair": 3, + "metrics": { + "medianMs": 11.8473, + "p95Ms": 16.0398, + "allocatedBytesPerOperation": 1834040.5628140704, + "measuredOperations": 199, + "gen0": 36, + "gen1": 11, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-current" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "current", + "pair": 3, + "metrics": { + "firstApplicationFrameWallMs": 10370.7248, + "sourceActivatedCpuFrameMs": 56648.4576, + "mainProcessCpuAtSourceReadyMs": 123078.125, + "completeRunWallMs": 290968.5551, + "completeRunMainCpuMs": 436437.5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-current" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 9.88215, + "p95Ms": 30.8648, + "allocatedBytesPerOperation": 4875279.12, + "measuredOperations": 200, + "gen0": 109, + "gen1": 9, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-original" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 9.90825, + "p95Ms": 29.94, + "allocatedBytesPerOperation": 4906543.88, + "measuredOperations": 200, + "gen0": 107, + "gen1": 14, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-original" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "original", + "pair": 3, + "metrics": { + "medianMs": 11.9443, + "p95Ms": 16.2577, + "allocatedBytesPerOperation": 1856203.216080402, + "measuredOperations": 199, + "gen0": 36, + "gen1": 12, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-original" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "original", + "pair": 3, + "metrics": { + "firstApplicationFrameWallMs": 9976.6375, + "sourceActivatedCpuFrameMs": 58998.4098, + "mainProcessCpuAtSourceReadyMs": 133843.75, + "completeRunWallMs": 327586.5809, + "completeRunMainCpuMs": 500921.875 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-original" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 10.023399999999999, + "p95Ms": 31.1487, + "allocatedBytesPerOperation": 5007234.2, + "measuredOperations": 200, + "gen0": 111, + "gen1": 16, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-original" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 10.412949999999999, + "p95Ms": 31.4659, + "allocatedBytesPerOperation": 5037921.08, + "measuredOperations": 200, + "gen0": 113, + "gen1": 15, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-original" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "original", + "pair": 4, + "metrics": { + "medianMs": 14.7406, + "p95Ms": 21.6769, + "allocatedBytesPerOperation": 1856437.6683417086, + "measuredOperations": 199, + "gen0": 35, + "gen1": 9, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-original" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "original", + "pair": 4, + "metrics": { + "firstApplicationFrameWallMs": 8934.5503, + "sourceActivatedCpuFrameMs": 52717.7551, + "mainProcessCpuAtSourceReadyMs": 121609.375, + "completeRunWallMs": 311802.565, + "completeRunMainCpuMs": 478125.0 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-original" + }, + { + "kind": "product-frame", + "workload": "idle", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 9.63455, + "p95Ms": 25.6117, + "allocatedBytesPerOperation": 1936929.32, + "measuredOperations": 200, + "gen0": 39, + "gen1": 6, + "gen2": 6 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-current" + }, + { + "kind": "product-frame", + "workload": "menus", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 9.9705, + "p95Ms": 26.9715, + "allocatedBytesPerOperation": 1962888.56, + "measuredOperations": 200, + "gen0": 39, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-current" + }, + { + "kind": "product-frame", + "workload": "play", + "source": "current", + "pair": 4, + "metrics": { + "medianMs": 11.8801, + "p95Ms": 15.3872, + "allocatedBytesPerOperation": 1833919.6783919598, + "measuredOperations": 199, + "gen0": 35, + "gen1": 5, + "gen2": 5 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-current" + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "source": "current", + "pair": 4, + "metrics": { + "firstApplicationFrameWallMs": 8929.0579, + "sourceActivatedCpuFrameMs": 58373.1177, + "mainProcessCpuAtSourceReadyMs": 127578.125, + "completeRunWallMs": 306272.7565, + "completeRunMainCpuMs": 458953.125 + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-current" + } + ], + "comparisons": [ + { + "kind": "commands", + "workload": "menu-open", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 8544.288, + 8544.288, + 8544.288, + 8544.288, + 8544.288 + ], + "current": [ + 4240.144, + 4240.144, + 4240.144, + 4240.144, + 4240.144 + ] + }, + "medians": { + "original": 8544.288, + "current": 4240.144 + }, + "medianRatio": 0.49625480789037074, + "pairedRatios": [ + 0.49625480789037074, + 0.49625480789037074, + 0.49625480789037074, + 0.49625480789037074, + 0.49625480789037074 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "menu-open", + "metric": "batchMeanMsPerOperation", + "values": { + "original": [ + 0.01352185, + 0.009671049999999999, + 0.008250400000000001, + 0.0096234, + 0.0085225 + ], + "current": [ + 0.00594675, + 0.00499185, + 0.0054373, + 0.0057007, + 0.00558785 + ] + }, + "medians": { + "original": 0.0096234, + "current": 0.00558785 + }, + "medianRatio": 0.5806523681858802, + "pairedRatios": [ + 0.4397881946627126, + 0.5161642220855027, + 0.6590347134684378, + 0.5923789928715423, + 0.6556585508946905 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "menu-open", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 0.015625, + 0.0078125, + 0.0078125, + 0.0078125, + 0.0078125 + ], + "current": [ + 0.0078125, + 0.0078125, + 0.0078125, + 0.0078125, + 0.0078125 + ] + }, + "medians": { + "original": 0.0078125, + "current": 0.0078125 + }, + "medianRatio": 1.0, + "pairedRatios": [ + 0.5, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 1608.0, + 1608.0, + 1608.0, + 1608.0, + 1608.0 + ], + "current": [ + 352.0, + 352.0, + 352.0, + 352.0, + 352.0 + ] + }, + "medians": { + "original": 1608.0, + "current": 352.0 + }, + "medianRatio": 0.21890547263681592, + "pairedRatios": [ + 0.21890547263681592, + 0.21890547263681592, + 0.21890547263681592, + 0.21890547263681592, + 0.21890547263681592 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "metric": "batchMeanMsPerOperation", + "values": { + "original": [ + 0.0041922, + 0.00488945, + 0.0040537, + 0.004262800000000001, + 0.0040953 + ], + "current": [ + 0.0012994, + 0.0010549, + 0.0010749, + 0.0011759, + 0.00099065 + ] + }, + "medians": { + "original": 0.0041922, + "current": 0.0010749 + }, + "medianRatio": 0.2564047516816946, + "pairedRatios": [ + 0.30995658604074233, + 0.21575023775680288, + 0.26516515775711075, + 0.2758515529698789, + 0.24189925036016902 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-dispatch", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 0.0078125, + 0.0078125, + 0.0078125, + 0.0078125, + 0.0 + ], + "current": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "medians": { + "original": 0.0078125, + "current": 0.0 + }, + "medianRatio": 0.0, + "pairedRatios": [ + 0.0, + 0.0, + 0.0, + 0.0, + null + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 1424.0, + 1424.0, + 1424.0, + 1424.0, + 1424.0 + ], + "current": [ + 48.0, + 48.0, + 48.0, + 48.0, + 48.0 + ] + }, + "medians": { + "original": 1424.0, + "current": 48.0 + }, + "medianRatio": 0.033707865168539325, + "pairedRatios": [ + 0.033707865168539325, + 0.033707865168539325, + 0.033707865168539325, + 0.033707865168539325, + 0.033707865168539325 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "metric": "batchMeanMsPerOperation", + "values": { + "original": [ + 0.0036932, + 0.0040687499999999995, + 0.0035343, + 0.00345585, + 0.0037190500000000002 + ], + "current": [ + 0.00031995, + 0.00026555000000000003, + 0.00027529999999999997, + 0.0002673, + 0.00025795 + ] + }, + "medians": { + "original": 0.0036932, + "current": 0.0002673 + }, + "medianRatio": 0.07237625907072456, + "pairedRatios": [ + 0.08663218888768547, + 0.06526574500768051, + 0.0778937837761367, + 0.07734710707930031, + 0.06935911052553743 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "shortcut-unmatched", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 0.0, + 0.0078125, + 0.0078125, + 0.0078125, + 0.0 + ], + "current": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "medians": { + "original": 0.0078125, + "current": 0.0 + }, + "medianRatio": 0.0, + "pairedRatios": [ + null, + 0.0, + 0.0, + 0.0, + null + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 16792.0, + 16792.0, + 16792.0, + 16792.0, + 16792.0 + ], + "current": [ + 400.0, + 400.0, + 400.0, + 400.0, + 400.0 + ] + }, + "medians": { + "original": 16792.0, + "current": 400.0 + }, + "medianRatio": 0.023820867079561697, + "pairedRatios": [ + 0.023820867079561697, + 0.023820867079561697, + 0.023820867079561697, + 0.023820867079561697, + 0.023820867079561697 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "metric": "batchMeanMsPerOperation", + "values": { + "original": [ + 0.02743575, + 0.025300049999999998, + 0.024111999999999998, + 0.0243715, + 0.0276752 + ], + "current": [ + 0.00036525, + 0.00019894999999999998, + 0.00023035, + 0.00020055, + 0.00020085 + ] + }, + "medians": { + "original": 0.025300049999999998, + "current": 0.00020085 + }, + "medianRatio": 0.007938719488696663, + "pairedRatios": [ + 0.013312922008693039, + 0.007863620822883749, + 0.009553334439283344, + 0.008228873889584145, + 0.007257400127189686 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "commands", + "workload": "statistics-unchanged", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 0.03125, + 0.03125, + 0.0234375, + 0.03125, + 0.03125 + ], + "current": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "medians": { + "original": 0.03125, + "current": 0.0 + }, + "medianRatio": 0.0, + "pairedRatios": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 246352774.4, + 246353179.2, + 246353407.2, + 246366254.4, + 246352763.2 + ], + "current": [ + 938392.8, + 938392.8, + 938528.0, + 938427.2, + 938392.8 + ] + }, + "medians": { + "original": 246353179.2, + "current": 938392.8 + }, + "medianRatio": 0.0038091361477343585, + "pairedRatios": [ + 0.003809142406800514, + 0.0038091361477343585, + 0.0038096814274545987, + 0.0038090736179979038, + 0.00380914257997655 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 3554.6875, + 3357.8125, + 3464.0625, + 6356.25, + 4790.625 + ], + "current": [ + 15.625, + 15.625, + 18.75, + 23.4375, + 20.3125 + ] + }, + "medians": { + "original": 3554.6875, + "current": 18.75 + }, + "medianRatio": 0.005274725274725275, + "pairedRatios": [ + 0.004395604395604396, + 0.004653327128897162, + 0.005412719891745603, + 0.003687315634218289, + 0.0042400521852576645 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "maximumMs", + "values": { + "original": [ + 3786.8515, + 3396.2747, + 3624.0777, + 8356.4387, + 5163.1566 + ], + "current": [ + 16.4719, + 15.8653, + 18.1181, + 21.9723, + 19.5928 + ] + }, + "medians": { + "original": 3786.8515, + "current": 18.1181 + }, + "medianRatio": 0.004784475969020701, + "pairedRatios": [ + 0.00434976127265619, + 0.004671383030353817, + 0.004999368528991527, + 0.0026293856496547984, + 0.003794732857802531 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "medianMs", + "values": { + "original": [ + 3527.7773500000003, + 3366.68745, + 3426.88195, + 6197.7714, + 4880.64335 + ], + "current": [ + 14.714649999999999, + 14.05985, + 15.3498, + 20.20265, + 18.65335 + ] + }, + "medians": { + "original": 3527.7773500000003, + "current": 15.3498 + }, + "medianRatio": 0.004351124937065543, + "pairedRatios": [ + 0.0041710823955485735, + 0.004176167288709857, + 0.004479232206992131, + 0.0032596636268320575, + 0.0038219039299398918 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "p95Ms", + "values": { + "original": [ + 3786.8515, + 3396.2747, + 3624.0777, + 8356.4387, + 5163.1566 + ], + "current": [ + 16.4719, + 15.8653, + 18.1181, + 21.9723, + 19.5928 + ] + }, + "medians": { + "original": 3786.8515, + "current": 18.1181 + }, + "medianRatio": 0.004784475969020701, + "pairedRatios": [ + 0.00434976127265619, + 0.004671383030353817, + 0.004999368528991527, + 0.0026293856496547984, + 0.003794732857802531 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-scroll", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 246353388.8, + 246353793.6, + 246354021.6, + 246366868.8, + 246352811.2 + ], + "current": [ + 938440.8, + 938440.8, + 938576.0, + 938475.2, + 938440.8 + ] + }, + "medians": { + "original": 246353793.6, + "current": 938440.8 + }, + "medianRatio": 0.0038093214895798546, + "pairedRatios": [ + 0.003809327748934948, + 0.0038093214895798546, + 0.0038098667677686493, + 0.0038092589501628636, + 0.0038093366803033262 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 754213838.4, + 754213674.4, + 754213033.6, + 754282373.6, + 754281620.0 + ], + "current": [ + 7873499.2, + 7873821.6, + 7912272.0, + 7911923.2, + 7912856.8 + ] + }, + "medians": { + "original": 754213838.4, + "current": 7911923.2 + }, + "medianRatio": 0.010490291741112132, + "pairedRatios": [ + 0.010439345977399398, + 0.010439775712451601, + 0.010490765403818658, + 0.010489338577856964, + 0.01049058679170785 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 10417.1875, + 10187.5, + 10432.8125, + 14735.9375, + 14937.5 + ], + "current": [ + 92.1875, + 92.1875, + 121.875, + 96.875, + 81.25 + ] + }, + "medians": { + "original": 10432.8125, + "current": 92.1875 + }, + "medianRatio": 0.00883630372921971, + "pairedRatios": [ + 0.008849557522123894, + 0.009049079754601227, + 0.01168189306574809, + 0.0065740642561764396, + 0.005439330543933054 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "maximumMs", + "values": { + "original": [ + 10649.9995, + 10283.1874, + 11417.2039, + 16053.4, + 19866.71 + ], + "current": [ + 79.7892, + 77.1866, + 123.7802, + 90.7394, + 80.7782 + ] + }, + "medians": { + "original": 11417.2039, + "current": 80.7782 + }, + "medianRatio": 0.0070751298398025455, + "pairedRatios": [ + 0.007491944013706291, + 0.007506096796407697, + 0.010841551143708661, + 0.005652347789253367, + 0.004066007909714291 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "medianMs", + "values": { + "original": [ + 10467.925599999999, + 10187.652600000001, + 10344.95765, + 14733.090049999999, + 14610.683 + ], + "current": [ + 73.1636, + 68.55875, + 91.09275, + 72.15729999999999, + 76.14715000000001 + ] + }, + "medians": { + "original": 10467.925599999999, + "current": 73.1636 + }, + "medianRatio": 0.006989312189991111, + "pairedRatios": [ + 0.006989312189991111, + 0.006729592448018889, + 0.008805521789642124, + 0.004897635170566272, + 0.005211744721311112 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "p95Ms", + "values": { + "original": [ + 10649.9995, + 10283.1874, + 11417.2039, + 16053.4, + 19866.71 + ], + "current": [ + 79.7892, + 77.1866, + 123.7802, + 90.7394, + 80.7782 + ] + }, + "medians": { + "original": 11417.2039, + "current": 80.7782 + }, + "medianRatio": 0.0070751298398025455, + "pairedRatios": [ + 0.007491944013706291, + 0.007506096796407697, + 0.010841551143708661, + 0.005652347789253367, + 0.004066007909714291 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "assets-search", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 754215603.2, + 754215439.2, + 754214796.0, + 754285259.2, + 754284500.8 + ], + "current": [ + 7875357.6, + 7875680.0, + 7914125.6, + 7913781.6, + 7914708.0 + ] + }, + "medians": { + "original": 754215603.2, + "current": 7913781.6 + }, + "medianRatio": 0.010492731211636646, + "pairedRatios": [ + 0.010441785567132641, + 0.010442215301709776, + 0.010493198544993806, + 0.010491762239120793, + 0.010493000971921866 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 726426723.2, + 726426868.8, + 726428257.6, + 726670890.4, + 726697597.6 + ], + "current": [ + 300984.0, + 300984.0, + 300984.0, + 300984.0, + 300984.0 + ] + }, + "medians": { + "original": 726428257.6, + "current": 300984.0 + }, + "medianRatio": 0.0004143341023026855, + "pairedRatios": [ + 0.0004143349774828328, + 0.0004143348944363827, + 0.0004143341023026855, + 0.00041419575763427337, + 0.00041418053533413796 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 3935.9375, + 4087.5, + 3590.625, + 5992.1875, + 4651.5625 + ], + "current": [ + 1.5625, + 1.5625, + 1.5625, + 1.5625, + 1.5625 + ] + }, + "medians": { + "original": 4087.5, + "current": 1.5625 + }, + "medianRatio": 0.00038226299694189603, + "pairedRatios": [ + 0.0003969829297340214, + 0.00038226299694189603, + 0.0004351610095735422, + 0.0002607561929595828, + 0.0003359086328518643 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "maximumMs", + "values": { + "original": [ + 4082.0511, + 4425.4523, + 3628.8166, + 6853.4044, + 5687.0343 + ], + "current": [ + 1.562, + 1.4659, + 1.9986, + 2.1292, + 1.7238 + ] + }, + "medians": { + "original": 4425.4523, + "current": 1.7238 + }, + "medianRatio": 0.0003895195074184847, + "pairedRatios": [ + 0.00038265077083429944, + 0.00033124297826009786, + 0.0005507580625595682, + 0.00031067771223306184, + 0.0003031105333758933 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "medianMs", + "values": { + "original": [ + 3935.62945, + 4060.34005, + 3585.1115, + 5904.7548, + 4591.67915 + ], + "current": [ + 1.4435, + 1.4158, + 1.5540500000000002, + 1.9068, + 1.5859 + ] + }, + "medians": { + "original": 4060.34005, + "current": 1.5540500000000002 + }, + "medianRatio": 0.0003827388792226898, + "pairedRatios": [ + 0.0003667774159988563, + 0.0003486900068874773, + 0.0004334732685440886, + 0.00032292619500474436, + 0.0003453856308753629 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "p95Ms", + "values": { + "original": [ + 4082.0511, + 4425.4523, + 3628.8166, + 6853.4044, + 5687.0343 + ], + "current": [ + 1.562, + 1.4659, + 1.9986, + 2.1292, + 1.7238 + ] + }, + "medians": { + "original": 4425.4523, + "current": 1.7238 + }, + "medianRatio": 0.0003895195074184847, + "pairedRatios": [ + 0.00038265077083429944, + 0.00033124297826009786, + 0.0005507580625595682, + 0.00031067771223306184, + 0.0003031105333758933 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-edit", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 726434374.4, + 726435787.2, + 726434620.0, + 726695003.2, + 726726152.8 + ], + "current": [ + 301032.0, + 301032.0, + 301032.0, + 301032.0, + 301032.0 + ] + }, + "medians": { + "original": 726435787.2, + "current": 301032.0 + }, + "medianRatio": 0.00041439588371645133, + "pairedRatios": [ + 0.00041439668965092413, + 0.00041439588371645133, + 0.0004143965495477074, + 0.0004142480664851226, + 0.00041423031060621 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 709020653.6, + 709021073.6, + 709016406.4, + 709287042.4, + 709291708.0 + ], + "current": [ + 283193.6, + 283193.6, + 283193.6, + 283193.6, + 283193.6 + ] + }, + "medians": { + "original": 709021073.6, + "current": 283193.6 + }, + "medianRatio": 0.0003994149265015583, + "pairedRatios": [ + 0.0003994151631015336, + 0.0003994149265015583, + 0.0003994175557063668, + 0.00039926515369823144, + 0.00039926252740008064 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 4553.125, + 4479.6875, + 4528.125, + 5848.4375, + 4503.125 + ], + "current": [ + 1.5625, + 1.5625, + 1.5625, + 1.5625, + 1.5625 + ] + }, + "medians": { + "original": 4528.125, + "current": 1.5625 + }, + "medianRatio": 0.0003450655624568668, + "pairedRatios": [ + 0.00034317089910775565, + 0.0003487966515521451, + 0.0003450655624568668, + 0.0002671653753673524, + 0.00034698126301179735 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "maximumMs", + "values": { + "original": [ + 4751.4065, + 4741.383, + 4606.4219, + 6654.2834, + 4749.9316 + ], + "current": [ + 1.5519, + 1.6529, + 1.8761, + 2.4219, + 1.9979 + ] + }, + "medians": { + "original": 4749.9316, + "current": 1.8761 + }, + "medianRatio": 0.0003949741086797966, + "pairedRatios": [ + 0.00032661907584627837, + 0.00034861136508061046, + 0.00040727923770942476, + 0.0003639610540182283, + 0.00042061658319458747 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "medianMs", + "values": { + "original": [ + 4518.9755000000005, + 4469.9085, + 4538.4312, + 5902.0731, + 4497.09725 + ], + "current": [ + 1.4398, + 1.4905, + 1.77805, + 1.93125, + 1.7662 + ] + }, + "medians": { + "original": 4518.9755000000005, + "current": 1.7662 + }, + "medianRatio": 0.0003908408000884271, + "pairedRatios": [ + 0.0003186120393881312, + 0.0003334520158522261, + 0.00039177634773883977, + 0.0003272155338096372, + 0.00039274222944589427 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "p95Ms", + "values": { + "original": [ + 4751.4065, + 4741.383, + 4606.4219, + 6654.2834, + 4749.9316 + ], + "current": [ + 1.5519, + 1.6529, + 1.8761, + 2.4219, + 1.9979 + ] + }, + "medians": { + "original": 4749.9316, + "current": 1.8761 + }, + "medianRatio": 0.0003949741086797966, + "pairedRatios": [ + 0.00032661907584627837, + 0.00034861136508061046, + 0.00040727923770942476, + 0.0003639610540182283, + 0.00042061658319458747 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "input", + "workload": "hierarchy-scroll", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 709029572.0, + 709029992.0, + 709024057.6, + 709309252.0, + 709315172.8 + ], + "current": [ + 283241.6, + 283241.6, + 283241.6, + 283241.6, + 283241.6 + ] + }, + "medians": { + "original": 709029992.0, + "current": 283241.6 + }, + "medianRatio": 0.00039947760065980393, + "pairedRatios": [ + 0.0003994778372939288, + 0.00039947760065980393, + 0.00039948094421330955, + 0.00039932032354203663, + 0.00039931699033296075 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "idle", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4743584.76, + 4747774.16, + 5139818.52, + 4875279.12, + 5007234.2 + ], + "current": [ + 1936556.2, + 1936436.52, + 1940349.32, + 1940453.28, + 1936929.32 + ] + }, + "medians": { + "original": 4875279.12, + "current": 1936929.32 + }, + "medianRatio": 0.3972960875314971, + "pairedRatios": [ + 0.4082474116052266, + 0.407861969576076, + 0.3775131967110777, + 0.39801890973577736, + 0.3868261883975788 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "idle", + "metric": "medianMs", + "values": { + "original": [ + 9.67915, + 9.6667, + 10.732949999999999, + 9.88215, + 10.023399999999999 + ], + "current": [ + 9.37575, + 9.7269, + 9.58705, + 9.6922, + 9.63455 + ] + }, + "medians": { + "original": 9.88215, + "current": 9.63455 + }, + "medianRatio": 0.97494472356724, + "pairedRatios": [ + 0.9686542723276321, + 1.0062275647325354, + 0.8932353174104045, + 0.9807784743198595, + 0.9612057784783609 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "idle", + "metric": "p95Ms", + "values": { + "original": [ + 29.4194, + 29.7502, + 33.5956, + 30.8648, + 31.1487 + ], + "current": [ + 24.8848, + 25.3644, + 25.5172, + 25.5423, + 25.6117 + ] + }, + "medians": { + "original": 30.8648, + "current": 25.5172 + }, + "medianRatio": 0.8267411420129079, + "pairedRatios": [ + 0.845863613805856, + 0.8525791423250936, + 0.7595399397540155, + 0.827554366138773, + 0.822239772446362 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "menus", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4906128.6, + 4774186.16, + 4775486.96, + 4906543.88, + 5037921.08 + ], + "current": [ + 1954005.88, + 1957503.96, + 1962944.08, + 1962768.84, + 1962888.56 + ] + }, + "medians": { + "original": 4906128.6, + "current": 1962768.84 + }, + "medianRatio": 0.4000646945944304, + "pairedRatios": [ + 0.39827856937953077, + 0.410018355882461, + 0.4110458465161425, + 0.40003083392377614, + 0.38962272906426715 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "menus", + "metric": "medianMs", + "values": { + "original": [ + 9.63265, + 9.699649999999998, + 9.79795, + 9.90825, + 10.412949999999999 + ], + "current": [ + 9.617650000000001, + 9.712399999999999, + 9.6327, + 9.70125, + 9.9705 + ] + }, + "medians": { + "original": 9.79795, + "current": 9.70125 + }, + "medianRatio": 0.9901305885414806, + "pairedRatios": [ + 0.9984427961152955, + 1.0013144804193965, + 0.9831342270577008, + 0.9791083188252213, + 0.9575096394393521 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "menus", + "metric": "p95Ms", + "values": { + "original": [ + 30.2297, + 29.7035, + 30.5176, + 29.94, + 31.4659 + ], + "current": [ + 25.6659, + 25.5731, + 25.8475, + 25.6267, + 26.9715 + ] + }, + "medians": { + "original": 30.2297, + "current": 25.6659 + }, + "medianRatio": 0.8490292659206012, + "pairedRatios": [ + 0.8490292659206012, + 0.8609456798020436, + 0.8469702728917083, + 0.8559352037408149, + 0.8571660114600249 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "play", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 1856208.9246231157, + 1856485.5879396985, + 1856907.336683417, + 1856203.216080402, + 1856437.6683417086 + ], + "current": [ + 1834145.3668341709, + 1834009.2864321608, + 1834056.8040201005, + 1834040.5628140704, + 1833919.6783919598 + ] + }, + "medians": { + "original": 1856437.6683417086, + "current": 1834040.5628140704 + }, + "medianRatio": 0.9879354389810218, + "pairedRatios": [ + 0.9881136452388167, + 0.9878930913045861, + 0.9876943064352747, + 0.9880602225692019, + 0.9878703226433326 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "play", + "metric": "medianMs", + "values": { + "original": [ + 11.9108, + 11.3259, + 12.1663, + 11.9443, + 14.7406 + ], + "current": [ + 12.7545, + 11.7569, + 11.7992, + 11.8473, + 11.8801 + ] + }, + "medians": { + "original": 11.9443, + "current": 11.8473 + }, + "medianRatio": 0.9918789715596561, + "pairedRatios": [ + 1.0708348725526413, + 1.0380543709550676, + 0.9698264879215539, + 0.9918789715596561, + 0.8059441271047312 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-frame", + "workload": "play", + "metric": "p95Ms", + "values": { + "original": [ + 17.983, + 14.051, + 17.1537, + 16.2577, + 21.6769 + ], + "current": [ + 15.4336, + 13.5499, + 15.6227, + 16.0398, + 15.3872 + ] + }, + "medians": { + "original": 17.1537, + "current": 15.4336 + }, + "medianRatio": 0.8997242577403126, + "pairedRatios": [ + 0.8582327753989879, + 0.9643370578606504, + 0.910748118481727, + 0.9865971201338443, + 0.7098431971361219 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "metric": "completeRunMainCpuMs", + "values": { + "original": [ + 455812.5, + 437296.875, + 477906.25, + 500921.875, + 478125.0 + ], + "current": [ + 420875.0, + 403546.875, + 427187.5, + 436437.5, + 458953.125 + ] + }, + "medians": { + "original": 477906.25, + "current": 427187.5 + }, + "medianRatio": 0.8938730137971621, + "pairedRatios": [ + 0.9233511586452763, + 0.9228213098938793, + 0.8938730137971621, + 0.871268598521476, + 0.9599019607843138 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "metric": "completeRunWallMs", + "values": { + "original": [ + 320830.5255, + 282493.8291, + 299000.016, + 327586.5809, + 311802.565 + ], + "current": [ + 304202.4103, + 268821.8483, + 287553.2501, + 290968.5551, + 306272.7565 + ] + }, + "medians": { + "original": 311802.565, + "current": 290968.5551 + }, + "medianRatio": 0.9331820445415515, + "pairedRatios": [ + 0.9481716548820103, + 0.9516025505988656, + 0.9617165040553042, + 0.8882187857042345, + 0.9822650320403875 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "metric": "firstApplicationFrameWallMs", + "values": { + "original": [ + 24200.9671, + 8705.0177, + 8451.0554, + 9976.6375, + 8934.5503 + ], + "current": [ + 19943.63, + 8598.3295, + 8298.5185, + 10370.7248, + 8929.0579 + ] + }, + "medians": { + "original": 8934.5503, + "current": 8929.0579 + }, + "medianRatio": 0.9993852628486516, + "pairedRatios": [ + 0.8240840094361352, + 0.9877440570856048, + 0.9819505502235852, + 1.0395010142445287, + 0.9993852628486516 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "metric": "mainProcessCpuAtSourceReadyMs", + "values": { + "original": [ + 101234.375, + 114437.5, + 115750.0, + 133843.75, + 121609.375 + ], + "current": [ + 116093.75, + 115046.875, + 115265.625, + 123078.125, + 127578.125 + ] + }, + "medians": { + "original": 115750.0, + "current": 116093.75 + }, + "medianRatio": 1.0029697624190064, + "pairedRatios": [ + 1.146781910788702, + 1.0053249590387767, + 0.9958153347732182, + 0.9195657249591408, + 1.0490813311062572 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "product-process", + "workload": "cold-start-and-complete-run", + "metric": "sourceActivatedCpuFrameMs", + "values": { + "original": [ + 60460.0851, + 50443.5293, + 50087.4516, + 58998.4098, + 52717.7551 + ], + "current": [ + 66056.5238, + 52733.859, + 52945.6862, + 56648.4576, + 58373.1177 + ] + }, + "medians": { + "original": 52717.7551, + "current": 56648.4576 + }, + "medianRatio": 1.0745612648441474, + "pairedRatios": [ + 1.092564188269725, + 1.0454038353735886, + 1.0570648836923457, + 0.9601692281543494, + 1.1072762409793888 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 249231740.8, + 249231740.8, + 249231740.8, + 249231740.8, + 249231740.8 + ], + "current": [ + 918679.2, + 918679.2, + 918679.2, + 918679.2, + 918679.2 + ] + }, + "medians": { + "original": 249231740.8, + "current": 918679.2 + }, + "medianRatio": 0.0036860441493172763, + "pairedRatios": [ + 0.0036860441493172763, + 0.0036860441493172763, + 0.0036860441493172763, + 0.0036860441493172763, + 0.0036860441493172763 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 3445.3125, + 3412.5, + 3448.4375, + 3431.25, + 3451.5625 + ], + "current": [ + 18.75, + 17.1875, + 15.625, + 17.1875, + 17.1875 + ] + }, + "medians": { + "original": 3445.3125, + "current": 17.1875 + }, + "medianRatio": 0.004988662131519274, + "pairedRatios": [ + 0.005442176870748299, + 0.005036630036630037, + 0.004531037607612143, + 0.005009107468123861, + 0.004979628791308284 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "maximumMs", + "values": { + "original": [ + 3490.0804, + 3450.0804, + 3522.6453, + 3486.708, + 3507.2268 + ], + "current": [ + 26.502, + 24.6364, + 24.5067, + 26.7983, + 24.9492 + ] + }, + "medians": { + "original": 3490.0804, + "current": 24.9492 + }, + "medianRatio": 0.0071486032241549514, + "pairedRatios": [ + 0.007593521341227555, + 0.007140819095114421, + 0.0069569025300390015, + 0.007685845789208618, + 0.00711365458315955 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "medianMs", + "values": { + "original": [ + 3431.3217999999997, + 3409.38115, + 3451.2929, + 3428.9718, + 3441.705 + ], + "current": [ + 16.5183, + 15.38935, + 15.244599999999998, + 15.799199999999999, + 15.1674 + ] + }, + "medians": { + "original": 3431.3217999999997, + "current": 15.38935 + }, + "medianRatio": 0.004484962617029974, + "pairedRatios": [ + 0.00481397576875477, + 0.004513825038306439, + 0.004417069324947761, + 0.00460756195195306, + 0.004406943651475068 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "p95Ms", + "values": { + "original": [ + 3490.0804, + 3450.0804, + 3522.6453, + 3486.708, + 3507.2268 + ], + "current": [ + 26.502, + 24.6364, + 24.5067, + 26.7983, + 24.9492 + ] + }, + "medians": { + "original": 3490.0804, + "current": 24.9492 + }, + "medianRatio": 0.0071486032241549514, + "pairedRatios": [ + 0.007593521341227555, + 0.007140819095114421, + 0.0069569025300390015, + 0.007685845789208618, + 0.00711365458315955 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-asset-directory-rows", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 249231806.4, + 249231806.4, + 249231806.4, + 249231806.4, + 249231806.4 + ], + "current": [ + 918778.4, + 918778.4, + 918778.4, + 918778.4, + 918778.4 + ] + }, + "medians": { + "original": 249231806.4, + "current": 918778.4 + }, + "medianRatio": 0.003686441202153081, + "pairedRatios": [ + 0.003686441202153081, + 0.003686441202153081, + 0.003686441202153081, + 0.003686441202153081, + 0.003686441202153081 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 709018286.4, + 709016743.2, + 709019120.8, + 709019076.8, + 709019501.6 + ], + "current": [ + 277432.0, + 277432.0, + 277432.0, + 277432.0, + 277474.4 + ] + }, + "medians": { + "original": 709019076.8, + "current": 277432.0 + }, + "medianRatio": 0.00039128989483911726, + "pairedRatios": [ + 0.00039129033104159445, + 0.0003912911826988291, + 0.0003912898705566193, + 0.00039128989483911726, + 0.0003913494612966793 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 4356.25, + 4396.875, + 4265.625, + 4459.375, + 4440.625 + ], + "current": [ + 3.125, + 3.125, + 3.125, + 4.6875, + 3.125 + ] + }, + "medians": { + "original": 4396.875, + "current": 3.125 + }, + "medianRatio": 0.0007107320540156361, + "pairedRatios": [ + 0.0007173601147776184, + 0.0007107320540156361, + 0.0007326007326007326, + 0.001051156271899089, + 0.0007037297677691766 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "maximumMs", + "values": { + "original": [ + 4539.3549, + 4506.4856, + 4523.6256, + 4675.2377, + 4651.0813 + ], + "current": [ + 19.7131, + 19.5684, + 2.3837, + 19.252, + 19.093 + ] + }, + "medians": { + "original": 4539.3549, + "current": 19.252 + }, + "medianRatio": 0.004241131267352548, + "pairedRatios": [ + 0.004342709577521687, + 0.004342275053536175, + 0.0005269445817973972, + 0.0041178654937694395, + 0.004105066922825021 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "medianMs", + "values": { + "original": [ + 4387.3229, + 4380.0702, + 4228.7503, + 4441.211499999999, + 4436.56365 + ], + "current": [ + 2.0164, + 2.1885000000000003, + 2.2058999999999997, + 2.02435, + 1.9738 + ] + }, + "medians": { + "original": 4387.3229, + "current": 2.02435 + }, + "medianRatio": 0.00046140893801092236, + "pairedRatios": [ + 0.0004595968990565978, + 0.0004996495261651286, + 0.0005216434746691002, + 0.0004558103121186641, + 0.0004448938763675801 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "p95Ms", + "values": { + "original": [ + 4539.3549, + 4506.4856, + 4523.6256, + 4675.2377, + 4651.0813 + ], + "current": [ + 19.7131, + 19.5684, + 2.3837, + 19.252, + 19.093 + ] + }, + "medians": { + "original": 4539.3549, + "current": 19.252 + }, + "medianRatio": 0.004241131267352548, + "pairedRatios": [ + 0.004342709577521687, + 0.004342275053536175, + 0.0005269445817973972, + 0.0041178654937694395, + 0.004105066922825021 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "10000-hierarchy-rows", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 709025148.8, + 709023605.6, + 709026549.6, + 709027652.0, + 709030908.8 + ], + "current": [ + 277497.6, + 277497.6, + 277497.6, + 277497.6, + 277540.0 + ] + }, + "medians": { + "original": 709026549.6, + "current": 277497.6 + }, + "medianRatio": 0.00039137829204922053, + "pairedRatios": [ + 0.00039137906528372777, + 0.00039137991712585087, + 0.00039137829204922053, + 0.00039137768353215085, + 0.00039143568574425455 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "allocatedBytesPerOperation", + "values": { + "original": [ + 4364820.0, + 4364780.0, + 4364723.2, + 4364528.0, + 4364701.6 + ], + "current": [ + 3277376.0, + 3277376.0, + 3277376.0, + 3277376.0, + 3277376.0 + ] + }, + "medians": { + "original": 4364723.2, + "current": 3277376.0 + }, + "medianRatio": 0.75087831457445, + "pairedRatios": [ + 0.7508616621074867, + 0.7508685432026356, + 0.75087831457445, + 0.7509118970023791, + 0.7508820305149841 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "cpuMsPerOperation", + "values": { + "original": [ + 25.0, + 25.0, + 28.125, + 29.6875, + 26.5625 + ], + "current": [ + 35.9375, + 32.8125, + 26.5625, + 26.5625, + 26.5625 + ] + }, + "medians": { + "original": 26.5625, + "current": 26.5625 + }, + "medianRatio": 1.0, + "pairedRatios": [ + 1.4375, + 1.3125, + 0.9444444444444444, + 0.8947368421052632, + 1.0 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "maximumMs", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "medianMs", + "values": { + "original": [ + 23.1096, + 23.68225, + 23.89115, + 25.028750000000002, + 23.5582 + ], + "current": [ + 24.46985, + 29.15775, + 24.0661, + 25.95035, + 23.49785 + ] + }, + "medians": { + "original": 23.68225, + "current": 24.46985 + }, + "medianRatio": 1.0332569751607217, + "pairedRatios": [ + 1.0588608197459064, + 1.2312069165725386, + 1.0073227952610067, + 1.0368216550966387, + 0.997438259289759 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "p95Ms", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "totalAllocatedBytesPerOperation", + "values": { + "original": [ + 4365382.4, + 4365841.6, + 4365784.8, + 4365589.6, + 4365763.2 + ], + "current": [ + 3277441.6, + 3277441.6, + 3277441.6, + 3277441.6, + 3277441.6 + ] + }, + "medians": { + "original": 4365763.2, + "current": 3277441.6 + }, + "medianRatio": 0.7507144684347515, + "pairedRatios": [ + 0.7507799545808403, + 0.7507009874109954, + 0.7507107542268231, + 0.7507443209961835, + 0.7507144684347515 + ], + "reviewRequired": false, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + } + ], + "flags": [ + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "maximumMs", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + }, + { + "kind": "static", + "workload": "2000-shader-nodes", + "metric": "p95Ms", + "values": { + "original": [ + 24.7994, + 25.438, + 29.9459, + 35.0849, + 32.6808 + ], + "current": [ + 27.3035, + 39.7908, + 36.6837, + 35.9288, + 33.9387 + ] + }, + "medians": { + "original": 29.9459, + "current": 35.9288 + }, + "medianRatio": 1.1997902884869047, + "pairedRatios": [ + 1.1009742171181562, + 1.5642267473858007, + 1.2249990816772913, + 1.0240530826651923, + 1.0384904898288903 + ], + "reviewRequired": true, + "note": "Review all values and scope; timing quantization, startup tradeoffs and noise are not silently accepted." + } + ] +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/raw-commands-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/raw-commands-measurements.json new file mode 100644 index 000000000..c63749fab --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/raw-commands-measurements.json @@ -0,0 +1,2292 @@ +[ + { + "source": "original", + "pair": 0, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-original\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "76F1454EB2A11F7E145D56D10FFB8A7735DE7D5839082FB29C816B774968A69B", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 22.8391, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 27.0437, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 28.6382, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 28.6484, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 26.3206, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 8.7406, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 8.0978, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 8.3844, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 9.0382, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 8.3003, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 7.6667, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 7.2279, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 7.3809, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 7.796, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 7.3864, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 54.1844, + "bytes": 33584000, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 55.9827, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 61.9795, + "bytes": 33584000, + "cpuMilliseconds": 125, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 53.7556, + "bytes": 33584000, + "cpuMilliseconds": 125, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 54.8715, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-original.log" + }, + { + "source": "current", + "pair": 0, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-current\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "5D1A12D4922B22D76F51B00F95A6458CD173405462F637C71C078089AE7B228A", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 11.8147, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 1, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 11.8935, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 12.0833, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 11.7603, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 12.0389, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 2.4908, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 2.5462, + "bytes": 704000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 2.5988, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 2.6072, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 2.923, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.6821, + "bytes": 96000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.654, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.635, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.6399, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.6311, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.8668, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.7709, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.718, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.7305, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.6734, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\0-current.log" + }, + { + "source": "current", + "pair": 1, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-current\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "5D1A12D4922B22D76F51B00F95A6458CD173405462F637C71C078089AE7B228A", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 13.9058, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 1, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 11.4175, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 9.6322, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 9.8677, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 9.9837, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 1.9724, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 2.0434, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 2.1098, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 2.1635, + "bytes": 704000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 2.3914, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.5638, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.5407, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.5309, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.5278, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.5311, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.3979, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.391, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.3946, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.4038, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.4559, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-current.log" + }, + { + "source": "original", + "pair": 1, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-original\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "76F1454EB2A11F7E145D56D10FFB8A7735DE7D5839082FB29C816B774968A69B", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 21.241, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 18.2291, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 18.1396, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 21.0944, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 19.3421, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 9.3057, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 9.7789, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 10.2496, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 11.1838, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 9.7766, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 8.7347, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 8.5544, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 8.1375, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 7.2789, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 6.6165, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 51.4464, + "bytes": 33584000, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 50.6001, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 76.4628, + "bytes": 33584000, + "cpuMilliseconds": 156.25, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 28.9779, + "bytes": 33457856, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 26.2234, + "bytes": 33152000, + "cpuMilliseconds": 15.625, + "gen0": 4, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\1-original.log" + }, + { + "source": "original", + "pair": 2, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-original\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "76F1454EB2A11F7E145D56D10FFB8A7735DE7D5839082FB29C816B774968A69B", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 16.5008, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 16.6558, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 16.4903, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 16.8177, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 16.4738, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 8.3423, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 8.1027, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 8.1074, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 8.4159, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 7.9758, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 7.2436, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 7.0468, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 7.0686, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 7.3176, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 7.0517, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 49.7557, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 48.224, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 47.2, + "bytes": 33584000, + "cpuMilliseconds": 31.25, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 69.5497, + "bytes": 33584000, + "cpuMilliseconds": 125, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 31.9894, + "bytes": 33542960, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-original.log" + }, + { + "source": "current", + "pair": 2, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-current\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "5D1A12D4922B22D76F51B00F95A6458CD173405462F637C71C078089AE7B228A", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 11.2393, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 1, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 10.9043, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 10.8746, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 10.3443, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 10.4015, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 2.0726, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 2.0535, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 2.1498, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 2.1971, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 2.5379, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.5607, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.5524, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.5454, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.5274, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.5506, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.4685, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.4577, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.4551, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.4607, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.47, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\2-current.log" + }, + { + "source": "current", + "pair": 3, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-current\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "5D1A12D4922B22D76F51B00F95A6458CD173405462F637C71C078089AE7B228A", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 12.809, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 1, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 11.4014, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 10.4786, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 10.6329, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 11.8085, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 2.3518, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 2.3597, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 2.343, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 2.2558, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 2.3996, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.5237, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.5157, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.5394, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.5346, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.5375, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.3958, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.3945, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.4011, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.4093, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.4344, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-current.log" + }, + { + "source": "original", + "pair": 3, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-original\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "76F1454EB2A11F7E145D56D10FFB8A7735DE7D5839082FB29C816B774968A69B", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 20.1819, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 18.653, + "bytes": 17088576, + "cpuMilliseconds": 31.25, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 19.2468, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 19.2867, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 18.597, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 8.6584, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 8.8257, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 7.7582, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 8.5256, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 7.9808, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 7.0897, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 6.5842, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 6.7598, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 7.0561, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 6.9117, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 48.9872, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 47.9608, + "bytes": 33584000, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 48.743, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 72.3464, + "bytes": 33584000, + "cpuMilliseconds": 109.375, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 31.3689, + "bytes": 33495176, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\3-original.log" + }, + { + "source": "original", + "pair": 4, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-original\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "76F1454EB2A11F7E145D56D10FFB8A7735DE7D5839082FB29C816B774968A69B", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 16.7788, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 17.045, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 16.8158, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 17.3209, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 17.832, + "bytes": 17088576, + "cpuMilliseconds": 15.625, + "gen0": 2, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 8.1609, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 8.1906, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 8.3097, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 8.4543, + "bytes": 3216000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 8.0223, + "bytes": 3216000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 7.369, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 7.4381, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 8.2864, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 7.6618, + "bytes": 2848000, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 6.6159, + "bytes": 2848000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 55.3504, + "bytes": 33584000, + "cpuMilliseconds": 46.875, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 56.347, + "bytes": 33584000, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 74.0208, + "bytes": 33584000, + "cpuMilliseconds": 140.625, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 31.4603, + "bytes": 33371192, + "cpuMilliseconds": 62.5, + "gen0": 4, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 25.7928, + "bytes": 33152000, + "cpuMilliseconds": 15.625, + "gen0": 4, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-original.log" + }, + { + "source": "current", + "pair": 4, + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-harness-current\\bin\\Release\\net10.0\\MenuHarness.dll", + "sha256": "5D1A12D4922B22D76F51B00F95A6458CD173405462F637C71C078089AE7B228A", + "samples": [ + { + "workload": "menu-open", + "sample": 0, + "iterations": 2000, + "milliseconds": 10.6865, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 1, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 1, + "iterations": 2000, + "milliseconds": 10.6641, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 2, + "iterations": 2000, + "milliseconds": 11.4204, + "bytes": 8480288, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 3, + "iterations": 2000, + "milliseconds": 11.3523, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "menu-open", + "sample": 4, + "iterations": 2000, + "milliseconds": 11.1757, + "bytes": 8480288, + "cpuMilliseconds": 15.625, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 0, + "iterations": 2000, + "milliseconds": 1.981, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 1, + "iterations": 2000, + "milliseconds": 1.979, + "bytes": 704000, + "cpuMilliseconds": 15.625, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 2, + "iterations": 2000, + "milliseconds": 1.9814, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 3, + "iterations": 2000, + "milliseconds": 1.9813, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-dispatch", + "sample": 4, + "iterations": 2000, + "milliseconds": 2.2184, + "bytes": 704000, + "cpuMilliseconds": 0, + "gen0": 1, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.5269, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.5221, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.5159, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.5149, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "shortcut-unmatched", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.5142, + "bytes": 96000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 0, + "iterations": 2000, + "milliseconds": 0.3984, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 1, + "iterations": 2000, + "milliseconds": 0.3975, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 2, + "iterations": 2000, + "milliseconds": 0.4017, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 3, + "iterations": 2000, + "milliseconds": 0.4111, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + }, + { + "workload": "statistics-unchanged", + "sample": 4, + "iterations": 2000, + "milliseconds": 0.4107, + "bytes": 800000, + "cpuMilliseconds": 0, + "gen0": 0, + "gen1": 0, + "gen2": 0 + } + ], + "log": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\menu-pairs-f5b1d2d01c67474ba44f3fd9147d6482\\4-current.log" + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/raw-graph-tail-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/raw-graph-tail-measurements.json new file mode 100644 index 000000000..8a3188f90 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/raw-graph-tail-measurements.json @@ -0,0 +1,2602 @@ +[ + { + "source": "original", + "workload": "shader", + "pair": 0, + "started": "2026-10-30T14:39:49.0802297+08:00", + "finished": "2026-10-30T14:39:55.6293937+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 32.3808, + 31.2086, + 32.1001, + 34.4778, + 30.672, + 31.2096, + 36.0926, + 31.9623, + 36.2038, + 31.2386 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 32.0312, + "p95": 36.2038, + "allocatedBytes": 44694008, + "totalAllocatedBytes": 44699632, + "cpuMilliseconds": 375, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 35.1619, + 36.1155, + 30.8223, + 33.326, + 30.6115, + 34.3519, + 31.9447, + 33.8309, + 32.107, + 39.3422 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 33.578450000000004, + "p95": 39.3422, + "allocatedBytes": 46531376, + "totalAllocatedBytes": 46541792, + "cpuMilliseconds": 328.125, + "gen0Collections": 7, + "gen1Collections": 2, + "gen2Collections": 2 + }, + { + "repetition": 2, + "milliseconds": [ + 30.5177, + 31.8864, + 29.6454, + 28.9866, + 29.3551, + 29.0038, + 31.0249, + 30.0106, + 38.014, + 33.1875 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 30.26415, + "p95": 38.014, + "allocatedBytes": 44694920, + "totalAllocatedBytes": 44700344, + "cpuMilliseconds": 437.5, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 3, + "milliseconds": [ + 30.8046, + 33.1602, + 31.4991, + 31.6717, + 29.584, + 32.8912, + 36.4979, + 32.2958, + 44.106, + 38.1169 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 32.5935, + "p95": 44.106, + "allocatedBytes": 44695880, + "totalAllocatedBytes": 44701328, + "cpuMilliseconds": 531.25, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 35.1323, + 31.9603, + 33.4916, + 36.1293, + 36.0825, + 32.1733, + 37.8276, + 36.8213, + 31.5635, + 31.3254 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 34.311949999999996, + "p95": 37.8276, + "allocatedBytes": 44695456, + "totalAllocatedBytes": 44700960, + "cpuMilliseconds": 593.75, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-0-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 0, + "started": "2026-10-30T14:39:55.6563746+08:00", + "finished": "2026-10-30T14:40:02.0101159+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 29.3347, + 29.0949, + 28.6393, + 33.2867, + 29.9664, + 29.436, + 29.2384, + 28.8074, + 28.5809, + 29.2498 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.2441, + "p95": 33.2867, + "allocatedBytes": 32779824, + "totalAllocatedBytes": 32785808, + "cpuMilliseconds": 296.875, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 28.0212, + 29.3997, + 30.3507, + 29.8386, + 30.6426, + 33.2507, + 29.5614, + 28.6952, + 28.1127, + 29.4565 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.50895, + "p95": 33.2507, + "allocatedBytes": 33826856, + "totalAllocatedBytes": 33827336, + "cpuMilliseconds": 328.125, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 28.4145, + 29.3555, + 27.6492, + 26.6958, + 26.8271, + 28.2735, + 27.7138, + 29.5996, + 27.9534, + 27.6243 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 27.833599999999997, + "p95": 29.5996, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774968, + "cpuMilliseconds": 421.875, + "gen0Collections": 3, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 30.0504, + 29.0223, + 27.885, + 28.6725, + 25.9009, + 28.5725, + 27.631, + 30.0733, + 31.0763, + 32.9627 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 28.8474, + "p95": 32.9627, + "allocatedBytes": 33562120, + "totalAllocatedBytes": 33567904, + "cpuMilliseconds": 453.125, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 28.7164, + 29.2206, + 28.9766, + 30.2021, + 30.4156, + 31.5813, + 32.2839, + 31.1896, + 32.3601, + 32.0204 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 30.802599999999998, + "p95": 32.3601, + "allocatedBytes": 33826856, + "totalAllocatedBytes": 33827336, + "cpuMilliseconds": 343.75, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-0-current" + }, + { + "source": "current", + "workload": "shader", + "pair": 1, + "started": "2026-10-30T14:40:02.0155280+08:00", + "finished": "2026-10-30T14:40:06.5013484+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 26.8662, + 27.6494, + 25.1943, + 27.737, + 28.4866, + 25.2242, + 25.3548, + 24.4203, + 27.8285, + 23.4358 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 26.110500000000002, + "p95": 28.4866, + "allocatedBytes": 32779192, + "totalAllocatedBytes": 32785152, + "cpuMilliseconds": 312.5, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 22.1251, + 23.9308, + 22.3103, + 22.4448, + 23.3741, + 23.5319, + 22.7705, + 21.3677, + 20.5628, + 22.9964 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 22.60765, + "p95": 23.9308, + "allocatedBytes": 33826208, + "totalAllocatedBytes": 33826688, + "cpuMilliseconds": 250, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 21.8082, + 21.0362, + 22.7297, + 21.4413, + 21.1559, + 22.3552, + 21.7495, + 26.2316, + 22.1931, + 23.3477 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 22.00065, + "p95": 26.2316, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774968, + "cpuMilliseconds": 359.375, + "gen0Collections": 3, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 24.1508, + 23.2065, + 21.69, + 20.6974, + 19.8831, + 21.0965, + 20.2759, + 19.7601, + 20.7428, + 21.2197 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 20.919649999999997, + "p95": 24.1508, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 234.375, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 20.134, + 21.4646, + 20.3451, + 22.7221, + 20.1372, + 20.4109, + 21.8696, + 23.534, + 18.7871, + 20.3639 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 20.3874, + "p95": 23.534, + "allocatedBytes": 31992368, + "totalAllocatedBytes": 31998176, + "cpuMilliseconds": 281.25, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-1-current" + }, + { + "source": "original", + "workload": "shader", + "pair": 1, + "started": "2026-10-30T14:40:06.5058536+08:00", + "finished": "2026-10-30T14:40:11.3474127+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 28.1252, + 28.1147, + 27.9129, + 26.8567, + 30.952, + 25.6033, + 27.5875, + 26.7077, + 27.1381, + 25.6941 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 27.3628, + "p95": 30.952, + "allocatedBytes": 44700824, + "totalAllocatedBytes": 44706400, + "cpuMilliseconds": 281.25, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 25.6076, + 27.1151, + 25.5607, + 26.9588, + 32.4755, + 26.9488, + 25.3813, + 26.0964, + 24.4031, + 26.4736 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 26.285, + "p95": 32.4755, + "allocatedBytes": 44700624, + "totalAllocatedBytes": 44706048, + "cpuMilliseconds": 296.875, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 2, + "milliseconds": [ + 27.5722, + 26.3184, + 26.8587, + 25.8487, + 29.5455, + 30.1361, + 37.801, + 28.5835, + 27.0775, + 28.3416 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 27.956899999999997, + "p95": 37.801, + "allocatedBytes": 44692416, + "totalAllocatedBytes": 44697792, + "cpuMilliseconds": 375, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 3, + "milliseconds": [ + 28.7835, + 29.4589, + 28.3771, + 32.3186, + 28.7531, + 29.8317, + 28.2814, + 29.0251, + 29.429, + 25.8274 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.9043, + "p95": 32.3186, + "allocatedBytes": 44699280, + "totalAllocatedBytes": 44704704, + "cpuMilliseconds": 328.125, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 28.4564, + 27.8626, + 32.9155, + 29.3801, + 31.9465, + 27.5722, + 27.6703, + 29.6117, + 30.5181, + 28.3845 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.91825, + "p95": 32.9155, + "allocatedBytes": 44695128, + "totalAllocatedBytes": 44700552, + "cpuMilliseconds": 453.125, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-1-original" + }, + { + "source": "original", + "workload": "shader", + "pair": 2, + "started": "2026-10-30T14:40:11.3511323+08:00", + "finished": "2026-10-30T14:40:15.9541434+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 37.4046, + 32.5144, + 29.3895, + 31.939, + 32.3863, + 30.2538, + 30.0768, + 32.7451, + 33.281, + 32.8391 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 32.45035, + "p95": 37.4046, + "allocatedBytes": 44694256, + "totalAllocatedBytes": 44699856, + "cpuMilliseconds": 375, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 26.5759, + 31.0613, + 30.0147, + 32.056, + 25.8572, + 27.255, + 25.6032, + 27.9934, + 28.0668, + 28.7632 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.0301, + "p95": 32.056, + "allocatedBytes": 44698328, + "totalAllocatedBytes": 44703776, + "cpuMilliseconds": 343.75, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 2, + "milliseconds": [ + 31.9593, + 28.7838, + 26.3299, + 23.8165, + 24.8275, + 22.9697, + 23.9533, + 22.4026, + 22.2803, + 22.4811 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 23.884900000000002, + "p95": 31.9593, + "allocatedBytes": 44699536, + "totalAllocatedBytes": 44705008, + "cpuMilliseconds": 453.125, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 3, + "milliseconds": [ + 20.9032, + 19.7395, + 18.6261, + 20.5612, + 19.5961, + 20.7256, + 20.8869, + 18.9525, + 20.4178, + 19.4674 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 20.07865, + "p95": 20.9032, + "allocatedBytes": 44696528, + "totalAllocatedBytes": 44701976, + "cpuMilliseconds": 203.125, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 22.3339, + 19.067, + 19.9808, + 19.1785, + 22.4236, + 18.5392, + 19.3828, + 21.2281, + 21.7555, + 20.4501 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 20.215449999999997, + "p95": 22.4236, + "allocatedBytes": 44697104, + "totalAllocatedBytes": 44707544, + "cpuMilliseconds": 250, + "gen0Collections": 7, + "gen1Collections": 2, + "gen2Collections": 2 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-2-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 2, + "started": "2026-10-30T14:40:15.9573985+08:00", + "finished": "2026-10-30T14:40:21.1775978+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 26.2905, + 28.1354, + 25.9733, + 27.9107, + 25.9314, + 28.1436, + 29.5235, + 28.6073, + 27.9244, + 29.029 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 28.029899999999998, + "p95": 29.5235, + "allocatedBytes": 33826208, + "totalAllocatedBytes": 33827224, + "cpuMilliseconds": 343.75, + "gen0Collections": 4, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 27.1682, + 31.1207, + 29.8845, + 29.8362, + 30.1497, + 28.3895, + 28.7303, + 29.6505, + 28.7214, + 28.1986 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.1904, + "p95": 31.1207, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 343.75, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 27.618, + 28.233, + 30.8363, + 30.6911, + 30.5702, + 30.8979, + 31.1437, + 33.2156, + 26.8535, + 27.9168 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 30.63065, + "p95": 33.2156, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 453.125, + "gen0Collections": 3, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 29.3387, + 28.5477, + 28.8181, + 29.2067, + 29.1079, + 30.7978, + 29.5049, + 28.0278, + 28.0726, + 28.7327 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 28.963, + "p95": 30.7978, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 453.125, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 28.1942, + 30.7117, + 27.8641, + 30.2124, + 28.1767, + 26.5906, + 28.8853, + 32.0666, + 30.7327, + 35.6308 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.54885, + "p95": 35.6308, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774272, + "cpuMilliseconds": 406.25, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-2-current" + }, + { + "source": "current", + "workload": "shader", + "pair": 3, + "started": "2026-10-30T14:40:21.1802796+08:00", + "finished": "2026-10-30T14:40:25.7669866+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 26.678, + 28.3807, + 26.374, + 27.0566, + 26.723, + 25.5311, + 26.6422, + 25.1922, + 25.1005, + 41.3669 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 26.6601, + "p95": 41.3669, + "allocatedBytes": 33826208, + "totalAllocatedBytes": 33827200, + "cpuMilliseconds": 296.875, + "gen0Collections": 4, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 30.2034, + 31.9865, + 30.7218, + 32.4074, + 32.2866, + 31.0007, + 32.0354, + 25.2831, + 24.3549, + 25.1439 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 30.86125, + "p95": 32.4074, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 359.375, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 23.4632, + 23.9267, + 26.2309, + 23.5961, + 23.7341, + 23.9133, + 21.4697, + 22.7299, + 21.4461, + 20.3971 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 23.52965, + "p95": 26.2309, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774968, + "cpuMilliseconds": 375, + "gen0Collections": 3, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 20.8502, + 19.9719, + 20.2719, + 21.6292, + 23.8934, + 23.943, + 22.3849, + 21.6372, + 23.2751, + 23.0035 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 22.011049999999997, + "p95": 23.943, + "allocatedBytes": 32773128, + "totalAllocatedBytes": 32773608, + "cpuMilliseconds": 281.25, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 22.735, + 24.1375, + 22.8277, + 22.1048, + 24.1766, + 21.8444, + 21.039, + 19.9433, + 19.9286, + 21.3873 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 21.974600000000002, + "p95": 24.1766, + "allocatedBytes": 32772224, + "totalAllocatedBytes": 32772704, + "cpuMilliseconds": 203.125, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-3-current" + }, + { + "source": "original", + "workload": "shader", + "pair": 3, + "started": "2026-10-30T14:40:25.7709153+08:00", + "finished": "2026-10-30T14:40:30.5207038+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 25.844, + 26.1445, + 27.2148, + 26.4727, + 32.9287, + 27.1212, + 26.5909, + 27.1076, + 31.2372, + 28.1334 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 27.114400000000003, + "p95": 32.9287, + "allocatedBytes": 44700064, + "totalAllocatedBytes": 44705736, + "cpuMilliseconds": 359.375, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 26.7173, + 27.2269, + 28.8691, + 27.0771, + 27.6919, + 27.9975, + 27.072, + 31.4734, + 31.4451, + 27.1083 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 27.459400000000002, + "p95": 31.4734, + "allocatedBytes": 44694104, + "totalAllocatedBytes": 44699552, + "cpuMilliseconds": 328.125, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 2, + "milliseconds": [ + 29.8254, + 25.7628, + 27.7933, + 29.8453, + 29.217, + 28.7697, + 30.6089, + 28.4808, + 28.953, + 28.9548 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.953899999999997, + "p95": 30.6089, + "allocatedBytes": 44699664, + "totalAllocatedBytes": 44710080, + "cpuMilliseconds": 468.75, + "gen0Collections": 7, + "gen1Collections": 2, + "gen2Collections": 2 + }, + { + "repetition": 3, + "milliseconds": [ + 28.3417, + 29.0935, + 28.457, + 27.8967, + 27.2294, + 29.1209, + 30.4103, + 26.6174, + 27.3288, + 26.3089 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.1192, + "p95": 30.4103, + "allocatedBytes": 44696528, + "totalAllocatedBytes": 44701976, + "cpuMilliseconds": 421.875, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 27.6547, + 29.9833, + 30.0488, + 29.1484, + 33.0214, + 30.1748, + 30.0169, + 30.8693, + 31.1252, + 29.3415 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 30.03285, + "p95": 33.0214, + "allocatedBytes": 44700240, + "totalAllocatedBytes": 44705720, + "cpuMilliseconds": 421.875, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-3-original" + }, + { + "source": "original", + "workload": "shader", + "pair": 4, + "started": "2026-10-30T14:40:30.5243354+08:00", + "finished": "2026-10-30T14:40:36.1346186+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 35.1052, + 29.0517, + 29.861, + 33.798, + 33.1672, + 39.9877, + 35.5996, + 28.4586, + 28.0142, + 26.6984 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 31.5141, + "p95": 39.9877, + "allocatedBytes": 44697960, + "totalAllocatedBytes": 44703584, + "cpuMilliseconds": 375, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 1, + "milliseconds": [ + 27.9334, + 33.9, + 34.6557, + 39.3678, + 33.3421, + 37.2289, + 33.6931, + 33.2278, + 34.6584, + 44.4491 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 34.27785, + "p95": 44.4491, + "allocatedBytes": 46535440, + "totalAllocatedBytes": 46545856, + "cpuMilliseconds": 390.625, + "gen0Collections": 7, + "gen1Collections": 2, + "gen2Collections": 2 + }, + { + "repetition": 2, + "milliseconds": [ + 38.7778, + 63.8343, + 42.0332, + 35.1406, + 36.8356, + 29.2195, + 37.2774, + 31.8038, + 46.1429, + 33.8788 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 37.0565, + "p95": 63.8343, + "allocatedBytes": 44693872, + "totalAllocatedBytes": 44699320, + "cpuMilliseconds": 640.625, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 3, + "milliseconds": [ + 42.4327, + 39.862, + 34.784, + 33.8083, + 30.2226, + 36.4189, + 29.8108, + 33.9752, + 49.5226, + 34.8724 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 34.828199999999995, + "p95": 49.5226, + "allocatedBytes": 46528528, + "totalAllocatedBytes": 46534032, + "cpuMilliseconds": 703.125, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + }, + { + "repetition": 4, + "milliseconds": [ + 28.2518, + 26.4857, + 29.2668, + 32.6033, + 29.5475, + 26.7721, + 28.2466, + 28.2268, + 30.4178, + 33.1233 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 28.7593, + "p95": 33.1233, + "allocatedBytes": 44700176, + "totalAllocatedBytes": 44705624, + "cpuMilliseconds": 390.625, + "gen0Collections": 6, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-4-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 4, + "started": "2026-10-30T14:40:36.1379570+08:00", + "finished": "2026-10-30T14:40:41.4726311+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 27.3455, + 27.6639, + 27.1984, + 28.8988, + 28.141, + 26.9864, + 31.4303, + 28.8297, + 30.8258, + 29.8121 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 28.485349999999997, + "p95": 31.4303, + "allocatedBytes": 33826856, + "totalAllocatedBytes": 33827848, + "cpuMilliseconds": 343.75, + "gen0Collections": 4, + "gen1Collections": 1, + "gen2Collections": 0 + }, + { + "repetition": 1, + "milliseconds": [ + 30.811, + 29.4254, + 25.6567, + 27.8988, + 27.7967, + 29.7548, + 29.031, + 29.6991, + 24.8906, + 26.6385 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 28.4649, + "p95": 30.811, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774240, + "cpuMilliseconds": 359.375, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 2, + "milliseconds": [ + 24.3713, + 24.2478, + 26.059, + 27.4523, + 26.2463, + 27.8474, + 24.753, + 25.3827, + 24.296, + 24.3476 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 25.06785, + "p95": 27.8474, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774968, + "cpuMilliseconds": 296.875, + "gen0Collections": 3, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 3, + "milliseconds": [ + 25.6151, + 27.305, + 27.2518, + 31.15, + 29.9926, + 30.4809, + 27.5388, + 31.2075, + 30.0519, + 29.7491 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.870849999999997, + "p95": 31.2075, + "allocatedBytes": 32774408, + "totalAllocatedBytes": 32774888, + "cpuMilliseconds": 437.5, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + }, + { + "repetition": 4, + "milliseconds": [ + 29.6381, + 33.9936, + 30.4521, + 29.7843, + 31.7581, + 34.8923, + 31.9508, + 30.1166, + 30.8721, + 30.9673 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 30.9197, + "p95": 34.8923, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774240, + "cpuMilliseconds": 406.25, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\graph-tail-pairs-3f1e2dccf94b458b9099c16a9fb6ad81\\shader-4-current" + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/raw-input-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/raw-input-measurements.json new file mode 100644 index 000000000..0bfe7ec8d --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/raw-input-measurements.json @@ -0,0 +1,1502 @@ +[ + { + "samples": [ + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 10231.8143, + 10604.0883, + 10487.0991, + 10223.4022, + 10294.7269, + 10448.7521, + 10591.7461, + 10280.5529, + 10542.7718, + 10649.9995 + ], + "median": 10467.925599999999, + "p95": 10649.9995, + "allocatedBytes": 7542138384, + "totalAllocatedBytes": 7542156032, + "cpuMilliseconds": 104171.875, + "gen0Collections": 898, + "gen1Collections": 17, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3510.8088, + 3546.4224, + 3511.2031, + 3532.194, + 3607.9724, + 3523.3607, + 3761.5666, + 3379.4922, + 3450.2559, + 3786.8515 + ], + "median": 3527.7773500000003, + "p95": 3786.8515, + "allocatedBytes": 2463527744, + "totalAllocatedBytes": 2463533888, + "cpuMilliseconds": 35546.875, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3879.6799, + 3956.3741, + 3982.2865, + 3836.7953, + 3936.2182, + 3974.4066, + 3870.9267, + 3932.3845, + 3935.0407, + 4082.0511 + ], + "median": 3935.62945, + "p95": 4082.0511, + "allocatedBytes": 7264267232, + "totalAllocatedBytes": 7264343744, + "cpuMilliseconds": 39359.375, + "gen0Collections": 831, + "gen1Collections": 109, + "gen2Collections": 12 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4529.5462, + 4495.3389, + 4450.2717, + 4682.6979, + 4560.1174, + 4471.5824, + 4508.4048, + 4751.4065, + 4496.5942, + 4593.2799 + ], + "median": 4518.9755000000005, + "p95": 4751.4065, + "allocatedBytes": 7090206536, + "totalAllocatedBytes": 7090295720, + "cpuMilliseconds": 45531.25, + "gen0Collections": 826, + "gen1Collections": 111, + "gen2Collections": 14 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + } + ], + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "pair": 0, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-original", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "original", + "wallMilliseconds": 490841.0031 + }, + { + "samples": [ + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 77.469, + 79.7892, + 78.6792, + 72.4184, + 75.0092, + 73.9088, + 63.6526, + 62.6626, + 65.5949, + 60.5379 + ], + "median": 73.1636, + "p95": 79.7892, + "allocatedBytes": 78734992, + "totalAllocatedBytes": 78753576, + "cpuMilliseconds": 921.875, + "gen0Collections": 8, + "gen1Collections": 6, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.4442, + 1.4391, + 1.422, + 1.562, + 1.5107, + 1.451, + 1.4428, + 1.453, + 1.4265, + 1.4151 + ], + "median": 1.4435, + "p95": 1.562, + "allocatedBytes": 3009840, + "totalAllocatedBytes": 3010320, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.4237, + 1.417, + 1.4774, + 1.4445, + 1.4231, + 1.4882, + 1.4351, + 1.4091, + 1.5519, + 1.455 + ], + "median": 1.4398, + "p95": 1.5519, + "allocatedBytes": 2831936, + "totalAllocatedBytes": 2832416, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14.5603, + 14.2502, + 15.3166, + 14.9756, + 14.6098, + 16.4719, + 14.8566, + 14.8195, + 14.4403, + 14.2136 + ], + "median": 14.714649999999999, + "p95": 16.4719, + "allocatedBytes": 9383928, + "totalAllocatedBytes": 9384408, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + } + ], + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "pair": 0, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\0-current", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "current", + "wallMilliseconds": 118251.02 + }, + { + "samples": [ + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.5024, + 1.4827, + 1.4803, + 1.4962, + 1.4709, + 1.5591, + 1.5163, + 1.4767, + 1.4848, + 1.6529 + ], + "median": 1.4905, + "p95": 1.6529, + "allocatedBytes": 2831936, + "totalAllocatedBytes": 2832416, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.4078, + 1.379, + 1.4139, + 1.3906, + 1.385, + 1.4219, + 1.4659, + 1.4416, + 1.4177, + 1.4364 + ], + "median": 1.4158, + "p95": 1.4659, + "allocatedBytes": 3009840, + "totalAllocatedBytes": 3010320, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 72.7355, + 77.1866, + 73.0883, + 74.1993, + 72.1001, + 65.0174, + 62.8437, + 60.8431, + 64.2667, + 60.5218 + ], + "median": 68.55875, + "p95": 77.1866, + "allocatedBytes": 78738216, + "totalAllocatedBytes": 78756800, + "cpuMilliseconds": 921.875, + "gen0Collections": 8, + "gen1Collections": 5, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 13.9153, + 15.8653, + 14.0934, + 13.9633, + 14.3194, + 14.1654, + 13.9258, + 14.0944, + 14.0263, + 13.7638 + ], + "median": 14.05985, + "p95": 15.8653, + "allocatedBytes": 9383928, + "totalAllocatedBytes": 9384408, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + } + ], + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "pair": 1, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-current", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "current", + "wallMilliseconds": 108857.7708 + }, + { + "samples": [ + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 10211.0511, + 10180.8836, + 10168.2307, + 10179.5642, + 10165.4065, + 10283.1874, + 10270.9341, + 10234.9524, + 10182.313, + 10192.9922 + ], + "median": 10187.652600000001, + "p95": 10283.1874, + "allocatedBytes": 7542136744, + "totalAllocatedBytes": 7542154392, + "cpuMilliseconds": 101875, + "gen0Collections": 898, + "gen1Collections": 16, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3342.4177, + 3370.2648, + 3363.9175, + 3357.0057, + 3390.4197, + 3346.9671, + 3342.5212, + 3396.2747, + 3371.6665, + 3369.4574 + ], + "median": 3366.68745, + "p95": 3396.2747, + "allocatedBytes": 2463531792, + "totalAllocatedBytes": 2463537936, + "cpuMilliseconds": 33578.125, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4054.6956, + 4091.1858, + 3952.9139, + 4032.9159, + 4076.5509, + 4065.9845, + 4425.4523, + 4028.3543, + 3992.7748, + 4244.7616 + ], + "median": 4060.34005, + "p95": 4425.4523, + "allocatedBytes": 7264268688, + "totalAllocatedBytes": 7264357872, + "cpuMilliseconds": 40875, + "gen0Collections": 833, + "gen1Collections": 107, + "gen2Collections": 14 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4520.3332, + 4741.383, + 4382.5305, + 4473.2678, + 4466.5492, + 4438.9156, + 4507.5489, + 4452.8046, + 4444.0079, + 4515.6066 + ], + "median": 4469.9085, + "p95": 4741.383, + "allocatedBytes": 7090210736, + "totalAllocatedBytes": 7090299920, + "cpuMilliseconds": 44796.875, + "gen0Collections": 826, + "gen1Collections": 111, + "gen2Collections": 14 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + } + ], + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "pair": 1, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\1-original", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "original", + "wallMilliseconds": 478511.2481 + }, + { + "samples": [ + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4578.9381, + 4606.4219, + 4537.4535, + 4468.4138, + 4522.2851, + 4539.4089, + 4457.5512, + 4597.5072, + 4599.4958, + 4456.9367 + ], + "median": 4538.4312, + "p95": 4606.4219, + "allocatedBytes": 7090164064, + "totalAllocatedBytes": 7090240576, + "cpuMilliseconds": 45281.25, + "gen0Collections": 824, + "gen1Collections": 114, + "gen2Collections": 12 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 10324.398, + 10294.8698, + 10354.4547, + 10394.0164, + 10335.4606, + 10660.8104, + 10390.7406, + 10319.0687, + 10329.8278, + 11417.2039 + ], + "median": 10344.95765, + "p95": 11417.2039, + "allocatedBytes": 7542130336, + "totalAllocatedBytes": 7542147960, + "cpuMilliseconds": 104328.125, + "gen0Collections": 899, + "gen1Collections": 17, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3391.8889, + 3412.3441, + 3384.7697, + 3410.0279, + 3441.4198, + 3624.0777, + 3595.9925, + 3381.4736, + 3530.5198, + 3495.4015 + ], + "median": 3426.88195, + "p95": 3624.0777, + "allocatedBytes": 2463534072, + "totalAllocatedBytes": 2463540216, + "cpuMilliseconds": 34640.625, + "gen0Collections": 294, + "gen1Collections": 4, + "gen2Collections": 1 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3596.059, + 3534.2568, + 3614.6995, + 3568.2203, + 3608.2846, + 3574.164, + 3628.8166, + 3601.7335, + 3550.678, + 3560.2323 + ], + "median": 3585.1115, + "p95": 3628.8166, + "allocatedBytes": 7264282576, + "totalAllocatedBytes": 7264346200, + "cpuMilliseconds": 35906.25, + "gen0Collections": 829, + "gen1Collections": 127, + "gen2Collections": 10 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + } + ], + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "pair": 2, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-original", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "original", + "wallMilliseconds": 476834.7609 + }, + { + "samples": [ + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 104.3352, + 91.9632, + 92.4848, + 104.5284, + 123.7802, + 90.2223, + 86.2752, + 73.1374, + 71.7355, + 67.1693 + ], + "median": 91.09275, + "p95": 123.7802, + "allocatedBytes": 79122720, + "totalAllocatedBytes": 79141256, + "cpuMilliseconds": 1218.75, + "gen0Collections": 8, + "gen1Collections": 4, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 18.1181, + 15.9254, + 16.2993, + 15.1278, + 15.4494, + 15.2195, + 14.8231, + 15.2502, + 15.0383, + 17.9802 + ], + "median": 15.3498, + "p95": 18.1181, + "allocatedBytes": 9385280, + "totalAllocatedBytes": 9385760, + "cpuMilliseconds": 187.5, + "gen0Collections": 2, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.466, + 1.4602, + 1.4937, + 1.4591, + 1.5722, + 1.636, + 1.6065, + 1.5359, + 1.5905, + 1.9986 + ], + "median": 1.5540500000000002, + "p95": 1.9986, + "allocatedBytes": 3009840, + "totalAllocatedBytes": 3010320, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.6382, + 1.7711, + 1.8761, + 1.8493, + 1.785, + 1.7356, + 1.7281, + 1.6775, + 1.8373, + 1.7851 + ], + "median": 1.77805, + "p95": 1.8761, + "allocatedBytes": 2831936, + "totalAllocatedBytes": 2832416, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + } + ], + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "pair": 2, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\2-current", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "current", + "wallMilliseconds": 137842.0408 + }, + { + "samples": [ + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 18.942, + 21.9723, + 20.314, + 20.6038, + 20.5669, + 19.6653, + 17.2285, + 20.0913, + 20.6504, + 18.6914 + ], + "median": 20.20265, + "p95": 21.9723, + "allocatedBytes": 9384272, + "totalAllocatedBytes": 9384752, + "cpuMilliseconds": 234.375, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.6567, + 1.635, + 1.564, + 1.7605, + 1.9673, + 2.1292, + 2.0565, + 2.0376, + 1.9119, + 1.9017 + ], + "median": 1.9068, + "p95": 2.1292, + "allocatedBytes": 3009840, + "totalAllocatedBytes": 3010320, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.7735, + 1.9541, + 2.1459, + 2.2456, + 1.9084, + 1.8009, + 1.7605, + 1.7685, + 2.0797, + 2.4219 + ], + "median": 1.93125, + "p95": 2.4219, + "allocatedBytes": 2831936, + "totalAllocatedBytes": 2832416, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 90.7394, + 84.4303, + 77.8088, + 74.8589, + 76.0475, + 64.0171, + 66.8619, + 69.4557, + 64.972, + 69.0475 + ], + "median": 72.15729999999999, + "p95": 90.7394, + "allocatedBytes": 79119232, + "totalAllocatedBytes": 79137816, + "cpuMilliseconds": 968.75, + "gen0Collections": 9, + "gen1Collections": 3, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + } + ], + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "pair": 3, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-current", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "current", + "wallMilliseconds": 154739.1671 + }, + { + "samples": [ + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 5772.2654, + 5961.9365, + 5842.2665, + 6068.6114, + 6493.7052, + 5727.1596, + 5749.1581, + 5847.5731, + 6853.4044, + 6074.9406 + ], + "median": 5904.7548, + "p95": 6853.4044, + "allocatedBytes": 7266708904, + "totalAllocatedBytes": 7266950032, + "cpuMilliseconds": 59921.875, + "gen0Collections": 857, + "gen1Collections": 141, + "gen2Collections": 38 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 6076.7855, + 5834.3496, + 5883.9603, + 6251.2492, + 8356.4387, + 6923.7684, + 7129.1422, + 6829.8662, + 6144.2936, + 5308.9846 + ], + "median": 6197.7714, + "p95": 8356.4387, + "allocatedBytes": 2463662544, + "totalAllocatedBytes": 2463668688, + "cpuMilliseconds": 63562.5, + "gen0Collections": 297, + "gen1Collections": 11, + "gen2Collections": 1 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 5850.4266, + 5754.8221, + 5997.663, + 5987.595, + 5428.186, + 5964.4229, + 5915.9711, + 5667.4805, + 5888.1751, + 6654.2834 + ], + "median": 5902.0731, + "p95": 6654.2834, + "allocatedBytes": 7092870424, + "totalAllocatedBytes": 7093092520, + "cpuMilliseconds": 58484.375, + "gen0Collections": 849, + "gen1Collections": 128, + "gen2Collections": 35 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 15109.747, + 15284.5099, + 15307.5502, + 14625.2424, + 14134.9201, + 14309.8463, + 14834.487, + 16053.4, + 14631.6931, + 14419.829 + ], + "median": 14733.090049999999, + "p95": 16053.4, + "allocatedBytes": 7542823736, + "totalAllocatedBytes": 7542852592, + "cpuMilliseconds": 147359.375, + "gen0Collections": 902, + "gen1Collections": 21, + "gen2Collections": 5 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + } + ], + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "pair": 3, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\3-original", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "original", + "wallMilliseconds": 747623.944 + }, + { + "samples": [ + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 5011.2353, + 5163.1566, + 5043.1341, + 4923.0403, + 4324.8921, + 4347.2222, + 4834.3711, + 4838.2464, + 5159.7388, + 4722.7954 + ], + "median": 4880.64335, + "p95": 5163.1566, + "allocatedBytes": 2463527632, + "totalAllocatedBytes": 2463528112, + "cpuMilliseconds": 47906.25, + "gen0Collections": 294, + "gen1Collections": 6, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 14934.4839, + 16488.3506, + 19866.71, + 14654.4286, + 13903.3565, + 14044.33, + 14479.2196, + 14050.7311, + 14566.9374, + 14737.6526 + ], + "median": 14610.683, + "p95": 19866.71, + "allocatedBytes": 7542816200, + "totalAllocatedBytes": 7542845008, + "cpuMilliseconds": 149375, + "gen0Collections": 904, + "gen1Collections": 20, + "gen2Collections": 5 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4549.0722, + 4598.0554, + 4585.3029, + 4724.5218, + 5687.0343, + 4616.6273, + 4535.0225, + 4683.4078, + 4429.5312, + 4436.7932 + ], + "median": 4591.67915, + "p95": 5687.0343, + "allocatedBytes": 7266975976, + "totalAllocatedBytes": 7267261528, + "cpuMilliseconds": 46515.625, + "gen0Collections": 864, + "gen1Collections": 138, + "gen2Collections": 45 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4665.2651, + 4441.5547, + 4749.9316, + 4431.9466, + 4412.7075, + 4534.8421, + 4396.2234, + 4459.3524, + 4553.1784, + 4555.7415 + ], + "median": 4497.09725, + "p95": 4749.9316, + "allocatedBytes": 7092917080, + "totalAllocatedBytes": 7093151728, + "cpuMilliseconds": 45031.25, + "gen0Collections": 850, + "gen1Collections": 136, + "gen2Collections": 37 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + } + ], + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "pair": 4, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-original", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "original", + "wallMilliseconds": 709030.3803 + }, + { + "samples": [ + { + "workload": "hierarchy-edit", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.5396, + 1.5539, + 1.5665, + 1.5835, + 1.6024, + 1.561, + 1.7238, + 1.6283, + 1.6055, + 1.5883 + ], + "median": 1.5859, + "p95": 1.7238, + "allocatedBytes": 3009840, + "totalAllocatedBytes": 3010320, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "continuous-domain-gesture-and-actual-hierarchy-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "hierarchy-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 1.7233, + 1.7522, + 1.7663, + 1.7182, + 1.7413, + 1.7661, + 1.9979, + 1.9237, + 1.974, + 1.9028 + ], + "median": 1.7662, + "p95": 1.9979, + "allocatedBytes": 2831936, + "totalAllocatedBytes": 2832416, + "cpuMilliseconds": 15.625, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy" + }, + { + "workload": "assets-search", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 78.0047, + 76.6079, + 74.2931, + 73.8806, + 77.8859, + 70.393, + 80.7782, + 76.663, + 71.5461, + 75.6864 + ], + "median": 76.14715000000001, + "p95": 80.7782, + "allocatedBytes": 79128568, + "totalAllocatedBytes": 79147080, + "cpuMilliseconds": 812.5, + "gen0Collections": 9, + "gen1Collections": 4, + "gen2Collections": 3 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 4, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + }, + { + "workload": "assets-scroll", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 18.6575, + 18.6492, + 19.5928, + 19.0607, + 19.3357, + 17.8883, + 18.8015, + 18.5207, + 16.6373, + 16.6758 + ], + "median": 18.65335, + "p95": 19.5928, + "allocatedBytes": 9383928, + "totalAllocatedBytes": 9384408, + "cpuMilliseconds": 203.125, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "configuration": "Debug", + "viewport": "1000x800", + "warmupOperations": 5, + "measuredOperationsPerRepetition": 10, + "nativeFramesPerOperation": 1, + "consumer": "actual-native-input-and-panel-draw", + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets" + } + ], + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "pair": 4, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\input-pairs-8a06aca274e64fe581cbe62288fac0e5\\4-current", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "producer": "current", + "wallMilliseconds": 181919.9031 + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/raw-product-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/raw-product-measurements.json new file mode 100644 index 000000000..62657a5a2 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/raw-product-measurements.json @@ -0,0 +1,472 @@ +[ + { + "frames": 18928, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 320830.5255, + "firstApplicationFrameWallMilliseconds": 24200.9671, + "editorSha256": "35E522169C6377FA022BBF3CAC5B8416F544285D707FEAE6477D8363B6A5333B", + "sourceReadyFrameMilliseconds": 60460.0851, + "cpuMilliseconds": 455812.5, + "pair": 0, + "source": "original", + "mainCpuAtSourceReadyMilliseconds": 101234.375, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\original-product-runtime\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-original", + "samples": [ + { + "median": 9.67915, + "phase": "idle", + "p95": 29.4194, + "meanAllocatedBytes": 4743584.76, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 106 + }, + { + "median": 9.63265, + "phase": "menus", + "p95": 30.2297, + "meanAllocatedBytes": 4906128.6, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 109 + }, + { + "median": 11.9108, + "phase": "play", + "p95": 17.983, + "meanAllocatedBytes": 1856208.9246231157, + "gen2": 6, + "count": 199, + "gen1": 12, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 18660, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 304202.4103, + "firstApplicationFrameWallMilliseconds": 19943.63, + "editorSha256": "9B85D2606CBB09C4331AA20AB6D7B825CBFD34A4C5505C9D0C7C593969C0CFB6", + "sourceReadyFrameMilliseconds": 66056.5238, + "cpuMilliseconds": 420875.0, + "pair": 0, + "source": "current", + "mainCpuAtSourceReadyMilliseconds": 116093.75, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\0-current", + "samples": [ + { + "median": 9.37575, + "phase": "idle", + "p95": 24.8848, + "meanAllocatedBytes": 1936556.2, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 38 + }, + { + "median": 9.617650000000001, + "phase": "menus", + "p95": 25.6659, + "meanAllocatedBytes": 1954005.88, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 40 + }, + { + "median": 12.7545, + "phase": "play", + "p95": 15.4336, + "meanAllocatedBytes": 1834145.3668341709, + "gen2": 5, + "count": 199, + "gen1": 10, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 17778, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 268821.8483, + "firstApplicationFrameWallMilliseconds": 8598.3295, + "editorSha256": "9B85D2606CBB09C4331AA20AB6D7B825CBFD34A4C5505C9D0C7C593969C0CFB6", + "sourceReadyFrameMilliseconds": 52733.859, + "cpuMilliseconds": 403546.875, + "pair": 1, + "source": "current", + "mainCpuAtSourceReadyMilliseconds": 115046.875, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-current", + "samples": [ + { + "median": 9.7269, + "phase": "idle", + "p95": 25.3644, + "meanAllocatedBytes": 1936436.52, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 40 + }, + { + "median": 9.712399999999999, + "phase": "menus", + "p95": 25.5731, + "meanAllocatedBytes": 1957503.96, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 39 + }, + { + "median": 11.7569, + "phase": "play", + "p95": 13.5499, + "meanAllocatedBytes": 1834009.2864321608, + "gen2": 5, + "count": 199, + "gen1": 5, + "gen0": 35 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 18651, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 282493.8291, + "firstApplicationFrameWallMilliseconds": 8705.0177, + "editorSha256": "35E522169C6377FA022BBF3CAC5B8416F544285D707FEAE6477D8363B6A5333B", + "sourceReadyFrameMilliseconds": 50443.5293, + "cpuMilliseconds": 437296.875, + "pair": 1, + "source": "original", + "mainCpuAtSourceReadyMilliseconds": 114437.5, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\original-product-runtime\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\1-original", + "samples": [ + { + "median": 9.6667, + "phase": "idle", + "p95": 29.7502, + "meanAllocatedBytes": 4747774.16, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 105 + }, + { + "median": 9.699649999999998, + "phase": "menus", + "p95": 29.7035, + "meanAllocatedBytes": 4774186.16, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 107 + }, + { + "median": 11.3259, + "phase": "play", + "p95": 14.051, + "meanAllocatedBytes": 1856485.5879396985, + "gen2": 5, + "count": 199, + "gen1": 5, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 19147, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 299000.016, + "firstApplicationFrameWallMilliseconds": 8451.0554, + "editorSha256": "35E522169C6377FA022BBF3CAC5B8416F544285D707FEAE6477D8363B6A5333B", + "sourceReadyFrameMilliseconds": 50087.4516, + "cpuMilliseconds": 477906.25, + "pair": 2, + "source": "original", + "mainCpuAtSourceReadyMilliseconds": 115750.0, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\original-product-runtime\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-original", + "samples": [ + { + "median": 10.732949999999999, + "phase": "idle", + "p95": 33.5956, + "meanAllocatedBytes": 5139818.52, + "gen2": 15, + "count": 200, + "gen1": 15, + "gen0": 125 + }, + { + "median": 9.79795, + "phase": "menus", + "p95": 30.5176, + "meanAllocatedBytes": 4775486.96, + "gen2": 14, + "count": 200, + "gen1": 14, + "gen0": 115 + }, + { + "median": 12.1663, + "phase": "play", + "p95": 17.1537, + "meanAllocatedBytes": 1856907.336683417, + "gen2": 13, + "count": 199, + "gen1": 13, + "gen0": 44 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 18350, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 287553.2501, + "firstApplicationFrameWallMilliseconds": 8298.5185, + "editorSha256": "9B85D2606CBB09C4331AA20AB6D7B825CBFD34A4C5505C9D0C7C593969C0CFB6", + "sourceReadyFrameMilliseconds": 52945.6862, + "cpuMilliseconds": 427187.5, + "pair": 2, + "source": "current", + "mainCpuAtSourceReadyMilliseconds": 115265.625, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\2-current", + "samples": [ + { + "median": 9.58705, + "phase": "idle", + "p95": 25.5172, + "meanAllocatedBytes": 1940349.32, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 40 + }, + { + "median": 9.6327, + "phase": "menus", + "p95": 25.8475, + "meanAllocatedBytes": 1962944.08, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 39 + }, + { + "median": 11.7992, + "phase": "play", + "p95": 15.6227, + "meanAllocatedBytes": 1834056.8040201005, + "gen2": 5, + "count": 199, + "gen1": 11, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 18013, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 290968.5551, + "firstApplicationFrameWallMilliseconds": 10370.7248, + "editorSha256": "9B85D2606CBB09C4331AA20AB6D7B825CBFD34A4C5505C9D0C7C593969C0CFB6", + "sourceReadyFrameMilliseconds": 56648.4576, + "cpuMilliseconds": 436437.5, + "pair": 3, + "source": "current", + "mainCpuAtSourceReadyMilliseconds": 123078.125, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-current", + "samples": [ + { + "median": 9.6922, + "phase": "idle", + "p95": 25.5423, + "meanAllocatedBytes": 1940453.28, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 40 + }, + { + "median": 9.70125, + "phase": "menus", + "p95": 25.6267, + "meanAllocatedBytes": 1962768.84, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 40 + }, + { + "median": 11.8473, + "phase": "play", + "p95": 16.0398, + "meanAllocatedBytes": 1834040.5628140704, + "gen2": 5, + "count": 199, + "gen1": 11, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 19900, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 327586.5809, + "firstApplicationFrameWallMilliseconds": 9976.6375, + "editorSha256": "35E522169C6377FA022BBF3CAC5B8416F544285D707FEAE6477D8363B6A5333B", + "sourceReadyFrameMilliseconds": 58998.4098, + "cpuMilliseconds": 500921.875, + "pair": 3, + "source": "original", + "mainCpuAtSourceReadyMilliseconds": 133843.75, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\original-product-runtime\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\3-original", + "samples": [ + { + "median": 9.88215, + "phase": "idle", + "p95": 30.8648, + "meanAllocatedBytes": 4875279.12, + "gen2": 6, + "count": 200, + "gen1": 9, + "gen0": 109 + }, + { + "median": 9.90825, + "phase": "menus", + "p95": 29.94, + "meanAllocatedBytes": 4906543.88, + "gen2": 6, + "count": 200, + "gen1": 14, + "gen0": 107 + }, + { + "median": 11.9443, + "phase": "play", + "p95": 16.2577, + "meanAllocatedBytes": 1856203.216080402, + "gen2": 5, + "count": 199, + "gen1": 12, + "gen0": 36 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 18984, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 311802.565, + "firstApplicationFrameWallMilliseconds": 8934.5503, + "editorSha256": "35E522169C6377FA022BBF3CAC5B8416F544285D707FEAE6477D8363B6A5333B", + "sourceReadyFrameMilliseconds": 52717.7551, + "cpuMilliseconds": 478125.0, + "pair": 4, + "source": "original", + "mainCpuAtSourceReadyMilliseconds": 121609.375, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\original-product-runtime\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-original", + "samples": [ + { + "median": 10.023399999999999, + "phase": "idle", + "p95": 31.1487, + "meanAllocatedBytes": 5007234.2, + "gen2": 5, + "count": 200, + "gen1": 16, + "gen0": 111 + }, + { + "median": 10.412949999999999, + "phase": "menus", + "p95": 31.4659, + "meanAllocatedBytes": 5037921.08, + "gen2": 6, + "count": 200, + "gen1": 15, + "gen0": 113 + }, + { + "median": 14.7406, + "phase": "play", + "p95": 21.6769, + "meanAllocatedBytes": 1856437.6683417086, + "gen2": 5, + "count": 199, + "gen1": 9, + "gen0": 35 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + }, + { + "frames": 19214, + "coldScope": "First application CPU frame uses official boot timestamps and parent launch wall clock. Source-ready CPU frame uses the second public module update and monotonic timestamps. Neither measures GPU completion or physical display; source-ready CPU excludes child processes.", + "wallMilliseconds": 306272.7565, + "firstApplicationFrameWallMilliseconds": 8929.0579, + "editorSha256": "9B85D2606CBB09C4331AA20AB6D7B825CBFD34A4C5505C9D0C7C593969C0CFB6", + "sourceReadyFrameMilliseconds": 58373.1177, + "cpuMilliseconds": 458953.125, + "pair": 4, + "source": "current", + "mainCpuAtSourceReadyMilliseconds": 127578.125, + "editor": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\InnoEngine.Editor\\debug\\InnoEngine.Editor.dll", + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\product-pairs-c2c33a875f5b4184b36f19ecb983b031\\4-current", + "samples": [ + { + "median": 9.63455, + "phase": "idle", + "p95": 25.6117, + "meanAllocatedBytes": 1936929.32, + "gen2": 6, + "count": 200, + "gen1": 6, + "gen0": 39 + }, + { + "median": 9.9705, + "phase": "menus", + "p95": 26.9715, + "meanAllocatedBytes": 1962888.56, + "gen2": 5, + "count": 200, + "gen1": 5, + "gen0": 39 + }, + { + "median": 11.8801, + "phase": "play", + "p95": 15.3872, + "meanAllocatedBytes": 1833919.6783919598, + "gen2": 5, + "count": 199, + "gen1": 5, + "gen0": 35 + } + ], + "fixtureSha256": "6D46452CD09D56207264A055A812B4DDFDC0091FAB34B0DA363D84C6C0781084" + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/raw-static-measurements.json b/records/architecture/acceptance/editor-ui-2026-10-30/raw-static-measurements.json new file mode 100644 index 000000000..7aa665b7d --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/raw-static-measurements.json @@ -0,0 +1,1652 @@ +[ + { + "source": "original", + "workload": "hierarchy", + "pair": 0, + "started": "2026-10-30T12:19:41.6083259+08:00", + "finished": "2026-10-30T12:20:52.9653196+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4216.7166, + 4391.1741, + 4394.6593, + 4260.7136, + 4265.233, + 4539.3549, + 4394.9547, + 4383.4717, + 4475.3215, + 4283.3718 + ], + "median": 4387.3229, + "p95": 4539.3549, + "allocatedBytes": 7090182864, + "totalAllocatedBytes": 7090251488, + "cpuMilliseconds": 43562.5, + "gen0Collections": 825, + "gen1Collections": 107, + "gen2Collections": 12 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-0-original" + }, + { + "source": "current", + "workload": "hierarchy", + "pair": 0, + "started": "2026-10-30T12:20:52.9697126+08:00", + "finished": "2026-10-30T12:20:58.7986941+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 2.1177, + 1.9773, + 1.9983, + 2.0489, + 2.0075, + 2.0253, + 2.0071, + 1.9722, + 2.1385, + 19.7131 + ], + "median": 2.0164, + "p95": 19.7131, + "allocatedBytes": 2774320, + "totalAllocatedBytes": 2774976, + "cpuMilliseconds": 31.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-0-current" + }, + { + "source": "current", + "workload": "hierarchy", + "pair": 1, + "started": "2026-10-30T12:20:58.8008431+08:00", + "finished": "2026-10-30T12:21:04.4734154+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 2.3048, + 2.1554, + 2.1842, + 2.1209, + 2.1928, + 2.153, + 2.2617, + 2.1552, + 2.2033, + 19.5684 + ], + "median": 2.1885000000000003, + "p95": 19.5684, + "allocatedBytes": 2774320, + "totalAllocatedBytes": 2774976, + "cpuMilliseconds": 31.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-1-current" + }, + { + "source": "original", + "workload": "hierarchy", + "pair": 1, + "started": "2026-10-30T12:21:04.4753979+08:00", + "finished": "2026-10-30T12:22:15.5176402+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4314.8162, + 4506.4856, + 4399.3132, + 4342.3296, + 4396.3246, + 4363.8158, + 4342.2355, + 4412.3185, + 4467.4359, + 4355.9105 + ], + "median": 4380.0702, + "p95": 4506.4856, + "allocatedBytes": 7090167432, + "totalAllocatedBytes": 7090236056, + "cpuMilliseconds": 43968.75, + "gen0Collections": 825, + "gen1Collections": 109, + "gen2Collections": 12 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-1-original" + }, + { + "source": "original", + "workload": "hierarchy", + "pair": 2, + "started": "2026-10-30T12:22:15.5195104+08:00", + "finished": "2026-10-30T12:23:24.2249516+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4304.5804, + 4378.9718, + 4523.6256, + 4169.3853, + 4266.3093, + 4186.0803, + 4210.6949, + 4246.8057, + 4113.8499, + 4175.0401 + ], + "median": 4228.7503, + "p95": 4523.6256, + "allocatedBytes": 7090191208, + "totalAllocatedBytes": 7090265496, + "cpuMilliseconds": 42656.25, + "gen0Collections": 826, + "gen1Collections": 114, + "gen2Collections": 13 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-2-original" + }, + { + "source": "current", + "workload": "hierarchy", + "pair": 2, + "started": "2026-10-30T12:23:24.2275762+08:00", + "finished": "2026-10-30T12:23:30.0177914+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 2.1842, + 2.0712, + 2.0844, + 2.1346, + 2.2159, + 2.1959, + 2.2556, + 2.2374, + 2.3837, + 2.2545 + ], + "median": 2.2058999999999997, + "p95": 2.3837, + "allocatedBytes": 2774320, + "totalAllocatedBytes": 2774976, + "cpuMilliseconds": 31.25, + "gen0Collections": 0, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-2-current" + }, + { + "source": "current", + "workload": "hierarchy", + "pair": 3, + "started": "2026-10-30T12:23:30.0197035+08:00", + "finished": "2026-10-30T12:23:35.5788848+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 2.0886, + 2.0021, + 2.0067, + 2.0224, + 1.9927, + 2.0328, + 2.0725, + 2.0127, + 2.0263, + 19.252 + ], + "median": 2.02435, + "p95": 19.252, + "allocatedBytes": 2774320, + "totalAllocatedBytes": 2774976, + "cpuMilliseconds": 46.875, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-3-current" + }, + { + "source": "original", + "workload": "hierarchy", + "pair": 3, + "started": "2026-10-30T12:23:35.5806226+08:00", + "finished": "2026-10-30T12:24:46.9357490+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4471.1911, + 4403.6458, + 4360.2039, + 4388.9807, + 4440.4879, + 4675.2377, + 4399.3425, + 4521.0685, + 4469.0167, + 4441.9351 + ], + "median": 4441.211499999999, + "p95": 4675.2377, + "allocatedBytes": 7090190768, + "totalAllocatedBytes": 7090276520, + "cpuMilliseconds": 44593.75, + "gen0Collections": 828, + "gen1Collections": 115, + "gen2Collections": 15 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-3-original" + }, + { + "source": "original", + "workload": "hierarchy", + "pair": 4, + "started": "2026-10-30T12:24:46.9377986+08:00", + "finished": "2026-10-30T12:25:58.9475549+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 4432.0962, + 4452.9004, + 4366.5815, + 4362.0096, + 4384.5227, + 4478.3418, + 4441.0311, + 4395.9451, + 4651.0813, + 4472.4841 + ], + "median": 4436.56365, + "p95": 4651.0813, + "allocatedBytes": 7090195016, + "totalAllocatedBytes": 7090309088, + "cpuMilliseconds": 44406.25, + "gen0Collections": 833, + "gen1Collections": 98, + "gen2Collections": 20 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-4-original" + }, + { + "source": "current", + "workload": "hierarchy", + "pair": 4, + "started": "2026-10-30T12:25:58.9493204+08:00", + "finished": "2026-10-30T12:26:04.5428821+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-hierarchy-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 2.016, + 1.9447, + 1.9746, + 2.1147, + 1.973, + 1.9664, + 1.9612, + 1.9381, + 19.093, + 2.0765 + ], + "median": 1.9738, + "p95": 19.093, + "allocatedBytes": 2774744, + "totalAllocatedBytes": 2775400, + "cpuMilliseconds": 31.25, + "gen0Collections": 1, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\hierarchy", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\hierarchy-4-current" + }, + { + "source": "original", + "workload": "assets", + "pair": 0, + "started": "2026-10-30T12:26:04.5450751+08:00", + "finished": "2026-10-30T12:27:51.4895703+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3414.7774, + 3477.376, + 3433.1506, + 3419.4781, + 3435.7412, + 3436.1202, + 3422.3179, + 3490.0804, + 3428.2553, + 3429.493 + ], + "median": 3431.3217999999997, + "p95": 3490.0804, + "allocatedBytes": 2492317408, + "totalAllocatedBytes": 2492318064, + "cpuMilliseconds": 34453.125, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-0-original" + }, + { + "source": "current", + "workload": "assets", + "pair": 0, + "started": "2026-10-30T12:27:51.4916399+08:00", + "finished": "2026-10-30T12:28:43.7119009+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 17.4546, + 16.4497, + 26.502, + 16.1348, + 16.1139, + 16.1449, + 16.5869, + 17.5876, + 19.6457, + 16.0665 + ], + "median": 16.5183, + "p95": 26.502, + "allocatedBytes": 9186792, + "totalAllocatedBytes": 9187784, + "cpuMilliseconds": 187.5, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-0-current" + }, + { + "source": "current", + "workload": "assets", + "pair": 1, + "started": "2026-10-30T12:28:43.7139437+08:00", + "finished": "2026-10-30T12:29:36.9146654+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 15.2781, + 15.1702, + 24.6364, + 15.675, + 15.1874, + 15.6424, + 15.2817, + 15.8784, + 15.4703, + 15.3084 + ], + "median": 15.38935, + "p95": 24.6364, + "allocatedBytes": 9186792, + "totalAllocatedBytes": 9187784, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-1-current" + }, + { + "source": "original", + "workload": "assets", + "pair": 1, + "started": "2026-10-30T12:29:36.9164747+08:00", + "finished": "2026-10-30T12:31:20.6629221+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3418.1025, + 3397.7327, + 3407.6389, + 3382.6609, + 3403.1459, + 3407.8422, + 3412.0437, + 3434.0241, + 3450.0804, + 3410.9201 + ], + "median": 3409.38115, + "p95": 3450.0804, + "allocatedBytes": 2492317408, + "totalAllocatedBytes": 2492318064, + "cpuMilliseconds": 34125, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-1-original" + }, + { + "source": "original", + "workload": "assets", + "pair": 2, + "started": "2026-10-30T12:31:20.6653032+08:00", + "finished": "2026-10-30T12:33:05.0116342+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3470.8347, + 3522.6453, + 3411.9846, + 3414.9572, + 3431.1341, + 3483.8875, + 3431.7511, + 3481.6085, + 3423.1363, + 3490.3121 + ], + "median": 3451.2929, + "p95": 3522.6453, + "allocatedBytes": 2492317408, + "totalAllocatedBytes": 2492318064, + "cpuMilliseconds": 34484.375, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-2-original" + }, + { + "source": "current", + "workload": "assets", + "pair": 2, + "started": "2026-10-30T12:33:05.0134561+08:00", + "finished": "2026-10-30T12:33:57.3735769+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 15.2115, + 14.7612, + 24.5067, + 15.3201, + 15.2777, + 14.9563, + 14.9936, + 14.9314, + 16.1351, + 16.6264 + ], + "median": 15.244599999999998, + "p95": 24.5067, + "allocatedBytes": 9186792, + "totalAllocatedBytes": 9187784, + "cpuMilliseconds": 156.25, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-2-current" + }, + { + "source": "current", + "workload": "assets", + "pair": 3, + "started": "2026-10-30T12:33:57.3756339+08:00", + "finished": "2026-10-30T12:34:49.4798555+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 15.9182, + 16.1078, + 26.7983, + 15.9009, + 15.7356, + 15.8628, + 15.6473, + 15.5352, + 15.5339, + 15.5025 + ], + "median": 15.799199999999999, + "p95": 26.7983, + "allocatedBytes": 9186792, + "totalAllocatedBytes": 9187784, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-3-current" + }, + { + "source": "original", + "workload": "assets", + "pair": 3, + "started": "2026-10-30T12:34:49.4819680+08:00", + "finished": "2026-10-30T12:36:33.5503101+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3423.9321, + 3468.6314, + 3440.8177, + 3421.0842, + 3403.892, + 3486.708, + 3376.4198, + 3431.9093, + 3426.0343, + 3439.0321 + ], + "median": 3428.9718, + "p95": 3486.708, + "allocatedBytes": 2492317408, + "totalAllocatedBytes": 2492318064, + "cpuMilliseconds": 34312.5, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-3-original" + }, + { + "source": "original", + "workload": "assets", + "pair": 4, + "started": "2026-10-30T12:36:33.5527561+08:00", + "finished": "2026-10-30T12:38:17.1978706+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "BD56CC91E3259213EBF4B6C66A38D8185486E7A668F0FC5D9F196CE0DBA68C24", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 3386.017, + 3406.1775, + 3418.6297, + 3487.6917, + 3445.0065, + 3438.4035, + 3483.7433, + 3507.2268, + 3422.3719, + 3447.5225 + ], + "median": 3441.705, + "p95": 3507.2268, + "allocatedBytes": 2492317408, + "totalAllocatedBytes": 2492318064, + "cpuMilliseconds": 34515.625, + "gen0Collections": 296, + "gen1Collections": 3, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-4-original" + }, + { + "source": "current", + "workload": "assets", + "pair": 4, + "started": "2026-10-30T12:38:17.2000270+08:00", + "finished": "2026-10-30T12:39:09.6247633+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.PlayMode.Tests\\debug\\Inno.Editor.PlayMode.Tests.dll", + "assemblySha256": "CB262A500BD081B545969F39E7038403E0355C5D2869DE49AE93C9692932CA52", + "measurement": { + "workload": "10000-asset-directory-rows", + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 15.3453, + 15.1656, + 24.9492, + 15.1692, + 15.156, + 15.4967, + 15.8414, + 14.6982, + 14.7258, + 14.6072 + ], + "median": 15.1674, + "p95": 24.9492, + "allocatedBytes": 9186792, + "totalAllocatedBytes": 9187784, + "cpuMilliseconds": 171.875, + "gen0Collections": 1, + "gen1Collections": 1, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1000x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\assets", + "consumer": "native-command-offset-validation", + "preparationExcludedFromFrameTiming": false, + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\assets-4-current" + }, + { + "source": "original", + "workload": "shader", + "pair": 0, + "started": "2026-10-30T12:39:09.6288672+08:00", + "finished": "2026-10-30T12:39:20.0894269+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 24.7994, + 23.5006, + 23.4532, + 23.2673, + 23.7329, + 22.9519, + 22.5422, + 22.709, + 22.795, + 22.3107 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 23.1096, + "p95": 24.7994, + "allocatedBytes": 43648200, + "totalAllocatedBytes": 43653824, + "cpuMilliseconds": 250, + "gen0Collections": 5, + "gen1Collections": 1, + "gen2Collections": 1 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-0-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 0, + "started": "2026-10-30T12:39:20.0919445+08:00", + "finished": "2026-10-30T12:39:31.0316461+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 23.7033, + 24.6676, + 23.3906, + 24.979, + 23.4014, + 26.1753, + 24.2721, + 25.1357, + 24.0688, + 27.3035 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 24.46985, + "p95": 27.3035, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774416, + "cpuMilliseconds": 359.375, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-0-current" + }, + { + "source": "current", + "workload": "shader", + "pair": 1, + "started": "2026-10-30T12:39:31.0340911+08:00", + "finished": "2026-10-30T12:39:34.2042862+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 22.507, + 28.9926, + 37.3605, + 39.7908, + 38.7975, + 37.5971, + 28.4668, + 27.3477, + 27.9346, + 29.3229 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 29.15775, + "p95": 39.7908, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774416, + "cpuMilliseconds": 328.125, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-1-current" + }, + { + "source": "original", + "workload": "shader", + "pair": 1, + "started": "2026-10-30T12:39:34.2063793+08:00", + "finished": "2026-10-30T12:39:37.2774014+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 23.575, + 22.1804, + 23.359, + 22.7483, + 25.1399, + 23.0556, + 25.438, + 24.586, + 25.424, + 23.7895 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 23.68225, + "p95": 25.438, + "allocatedBytes": 43647800, + "totalAllocatedBytes": 43658416, + "cpuMilliseconds": 250, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-1-original" + }, + { + "source": "original", + "workload": "shader", + "pair": 2, + "started": "2026-10-30T12:39:37.2796032+08:00", + "finished": "2026-10-30T12:39:40.2859170+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 22.6462, + 22.3252, + 23.4664, + 22.9411, + 24.7798, + 22.5363, + 29.9459, + 25.2428, + 25.2836, + 24.3159 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 23.89115, + "p95": 29.9459, + "allocatedBytes": 43647232, + "totalAllocatedBytes": 43657848, + "cpuMilliseconds": 281.25, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-2-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 2, + "started": "2026-10-30T12:39:40.2880849+08:00", + "finished": "2026-10-30T12:39:43.4877312+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 22.8056, + 24.3457, + 23.0389, + 25.1699, + 23.5567, + 23.7865, + 25.8047, + 23.7551, + 36.6837, + 31.5189 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 24.0661, + "p95": 36.6837, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774416, + "cpuMilliseconds": 265.625, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-2-current" + }, + { + "source": "current", + "workload": "shader", + "pair": 3, + "started": "2026-10-30T12:39:43.4901059+08:00", + "finished": "2026-10-30T12:39:46.7210693+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 23.3354, + 26.515, + 23.0835, + 25.3857, + 23.4487, + 23.3235, + 35.9288, + 28.0488, + 27.6963, + 28.7727 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 25.95035, + "p95": 35.9288, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774416, + "cpuMilliseconds": 265.625, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-3-current" + }, + { + "source": "original", + "workload": "shader", + "pair": 3, + "started": "2026-10-30T12:39:46.7251825+08:00", + "finished": "2026-10-30T12:39:49.8407995+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 23.1468, + 23.2236, + 25.0054, + 23.911, + 25.5367, + 23.1034, + 25.0521, + 35.0849, + 30.36, + 29.2272 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 25.028750000000002, + "p95": 35.0849, + "allocatedBytes": 43645280, + "totalAllocatedBytes": 43655896, + "cpuMilliseconds": 296.875, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-3-original" + }, + { + "source": "original", + "workload": "shader", + "pair": 4, + "started": "2026-10-30T12:39:49.8429863+08:00", + "finished": "2026-10-30T12:39:53.0332800+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\original-managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "18E566F57495ECA1E83C263B062E8E080164CB5FC784446826D3FF7AF221CEB1", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 22.2282, + 21.969, + 23.4728, + 22.768, + 24.6083, + 22.06, + 23.6436, + 32.6808, + 28.5992, + 27.0097 + ], + "vertices": [ + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260, + 182260 + ], + "indices": [ + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183, + 456183 + ], + "median": 23.5582, + "p95": 32.6808, + "allocatedBytes": 43647016, + "totalAllocatedBytes": 43657632, + "cpuMilliseconds": 265.625, + "gen0Collections": 6, + "gen1Collections": 2, + "gen2Collections": 2 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-4-original" + }, + { + "source": "current", + "workload": "shader", + "pair": 4, + "started": "2026-10-30T12:39:53.0355534+08:00", + "finished": "2026-10-30T12:39:56.0982694+08:00", + "assembly": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\managed\\bin\\Inno.Editor.Scripting.Tests\\debug\\Inno.Editor.Scripting.Tests.dll", + "assemblySha256": "145AE256F5D91CAA686E038419EE82474954AC0961C13017AF14FA2A6A8AD757", + "measurement": { + "workload": "2000-shader-nodes", + "nodeCount": 2000, + "edgeCount": 1003, + "configuration": "Debug", + "samples": [ + { + "repetition": 0, + "milliseconds": [ + 22.5636, + 23.5268, + 22.9927, + 24.1598, + 22.7718, + 23.0966, + 26.4938, + 23.4689, + 33.9387, + 30.2943 + ], + "vertices": [ + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030, + 184030 + ], + "indices": [ + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625, + 458625 + ], + "median": 23.49785, + "p95": 33.9387, + "allocatedBytes": 32773760, + "totalAllocatedBytes": 32774416, + "cpuMilliseconds": 265.625, + "gen0Collections": 4, + "gen1Collections": 0, + "gen2Collections": 0 + } + ], + "framework": ".NET 10.0.13", + "viewport": "1200x800", + "warmupFrames": 5, + "measuredFramesPerRepetition": 10, + "sharedInput": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\shared-input\\shader-graph", + "consumer": "native-command-offset-validation", + "supportsVertexOffsets": true + }, + "evidence": "D:\\InnoEngine-Verification\\editor-ui-closure-2026-10-27\\e11\\pairs-e9cf8c205df0499784abe33dae5ff767\\shader-4-current" + } +] diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/source-inventory.json b/records/architecture/acceptance/editor-ui-2026-10-30/source-inventory.json new file mode 100644 index 000000000..b5a2bc4e2 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/source-inventory.json @@ -0,0 +1,485 @@ +{ + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "changes": [], + "trackedProjects": 241, + "editorProjects": 30, + "forbiddenChanges": [], + "importsRequiringReview": [ + { + "path": "tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs", + "line": 551, + "text": "global using InnoEngine.Scene;" + }, + { + "path": "tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs", + "line": 129, + "text": "[InlineData(\"Directory.Build.props\", \"true\", \"implicit usings\", true)]" + }, + { + "path": "tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs", + "line": 130, + "text": "[InlineData(\"build/Probe.targets\", \"\", \"MSBuild Using\", true)]" + } + ], + "repositories": [ + { + "name": "InnoEngine", + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "branch": "editor-ui", + "changes": [] + }, + { + "name": "InnoEngine.Canvas", + "revision": "eed5fd9bdb13d5f29351aff2b8c52eed58138266", + "branch": "main", + "changes": [] + }, + { + "name": "InnoEngine.Rendering2D", + "revision": "067d4eb023b4dea0df114872247d9b44f69fe20f", + "branch": "main", + "changes": [] + }, + { + "name": "InnoEngine.Samples", + "revision": "3e331fd0fae08491fe11938faa5955c6e79e5089", + "branch": "main", + "changes": [] + }, + { + "name": "BindGen-CS", + "revision": "2894538911645ad28c74060840a02c154c15ce4d", + "branch": "main", + "changes": [] + } + ], + "commits": [ + { + "revision": "dd5979c613b0fe8ebd9f7026f25001ceee9f8cee", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(scripting): reuse atomic directory publication for script caches" + }, + { + "revision": "5e329bbe96e4812fcec1207151dd98c520237bbd", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "refactor(editor): remove an unreachable hierarchy mutation path" + }, + { + "revision": "4d0fdb0a47a90fdcf266b40c415f889afc268b81", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): own physical package probes through process exit" + }, + { + "revision": "1917b25381591efcd4e1d037bae71742291c0d32", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): reuse frozen graph identity projections during capture" + }, + { + "revision": "98fd5a7dff2606f1ebbdf57649b99e8f8252c03d", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(io): read published documents through rename-compatible handles" + }, + { + "revision": "163c193890feb07f508fcde6407531d0f7b9558a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(scene): complete reference scope failure and documentation contracts" + }, + { + "revision": "2bb4da4000bdd1c96d3ec0bdb9cc60ef2ec24e71", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): keep inline rename in the keyboard-focused browser view" + }, + { + "revision": "bc88dc0479398135b6debec254968baa2b31d467", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): cover large-view editing and shortcut dispatch workloads" + }, + { + "revision": "e52689204e8bf60cebd494ead307bcc24f502db9", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): resolve property references through the owning identity scope" + }, + { + "revision": "973a1aa76b1efff7b51d892383dbd681396ff1e4", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): cover large-view input and continuous edit workloads" + }, + { + "revision": "2434cd96cb10b0e6733f12ba6e4e83f7638f9b1c", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse proven array elements and bounded canonical blocks" + }, + { + "revision": "a25d630220603933cad8fd7bc16753f369940d42", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): retire export form tables when target presentation fails" + }, + { + "revision": "be44532f00dfa8644bceee8116830bd284e5d2b0", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse complete neutral capture keys within owned snapshots" + }, + { + "revision": "bced2aefd471cdfb00bc7ca5096f8c90aa652592", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): separate cold preparation from panel lifecycle acceptance" + }, + { + "revision": "329568bafab6fb60014bd88826d992e9d54bccf6", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): freeze shader inputs in owned compilation workspaces" + }, + { + "revision": "564e00d0733634c928508787baece2e837471248", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): verify loaded assemblies retire with their collectible contexts" + }, + { + "revision": "06f7e3c29893ab94fab96ba7ffb5f4b81079b06f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): record verified owner fixes and remaining acceptance gates" + }, + { + "revision": "d7f383f01dddbeaf184864c5d8d6e2cc32d73f61", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): prepare complete contract owners and execute scaled workflows" + }, + { + "revision": "397ae3edb6842126cd9e64ffde3d17c3d6e4a99b", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(imgui): document supplemental font ownership on its partial declaration" + }, + { + "revision": "6277db99b1475240e0062971eece72cb313d336a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(scene): reuse authoritative attachments and ordinal scalar captures" + }, + { + "revision": "8fd31ceeaca311c1761ac84f6f25a95e0d790cb1", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve unavailable scene source assignments until recovery" + }, + { + "revision": "23f0ceb25e606e97ed8ac263b0204f69e563df09", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "chore(assets): preserve bundled font license bytes across checkouts" + }, + { + "revision": "e3f02ebeacdb908da6e274063f44c640adac0750", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(imgui): own supplemental glyph data through complete context retirement" + }, + { + "revision": "0b0a821c728a592ff18cde432c22e8d85b258d16", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): reuse exact neutral captures without retaining source generations" + }, + { + "revision": "a38437f7d27307aadcf964f76e9228256d26185f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(build): verify output ownership through executed SDK results" + }, + { + "revision": "318e30aab746e49f549d0fa8083adf07ef2dbf09", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(io): publish complete files without replacing concurrent creators" + }, + { + "revision": "bb5b4bb9aff7d926cf585040c2feb4566f7ad74e", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "feat(editor): initialize new projects with the standard dock layout" + }, + { + "revision": "870a0af0be0977313054e0a1127977fa4e98317b", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): isolate product outputs and restore entry reference owners" + }, + { + "revision": "e2e6ca32f7aa07d9cfab230edcf1d2ad396e92fe", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): balance Shader settings scopes and presentation source contracts" + }, + { + "revision": "6df06585afb8f72dcb9d67abffe181be2b2d17c1", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(serialization): bound encoding buffers and converter lookup contention" + }, + { + "revision": "14c4d0b83c6c13b08d7b0961d222a72c1795e4f2", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): record controlled performance results and remaining acceptance" + }, + { + "revision": "f7dce89c0d91ecb0f2333b8ca3117fefc71db96a", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve input semantics and bound serialization allocations" + }, + { + "revision": "660962a5bcd4bc393028738b0dc3b0d53a0b0e40", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): forward the selected tool host to UI acceptance" + }, + { + "revision": "0974c317c3afcdb97f8f95adf35e4677c40b6c46", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): synchronize current acceptance and API references" + }, + { + "revision": "a4d975abbd17cb12b866a36a5447ea0f07ad0e52", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): apply settings atomically and retire failed presentation" + }, + { + "revision": "e053be5b1de0ece46df91619711232eb54d5fd82", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): honor evaluated SDK output owners" + }, + { + "revision": "af6eac3153219c2457f9b187e332a1126952fb11", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): require explicit product initialization expectations" + }, + { + "revision": "081f41f917be7d1f4d33a6848d1fb69bfb68abf7", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(testing): keep temporary SDK work independent of result paths" + }, + { + "revision": "14338d61df533f66646a82ba0f2d3184d264c003", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(tooling): validate complete graphs against their SDK output owner" + }, + { + "revision": "e23f4ba5b8e836264ec5a0f805707243c27ac5f6", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(assets): verify writable source content before reusing imports" + }, + { + "revision": "8dcceb94f4dd805075f6ed28db996eb017eae619", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): isolate neutral Foundation bootstrap compilation outputs" + }, + { + "revision": "63d4f7dce8b21daa22a6f7e5a00825be3f46dd2d", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "feat(editor): complete reusable presentation and SDK-owned compilation contracts" + }, + { + "revision": "273c42ad32be511c21fa21b1e99626af708f29c8", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(assets): preserve source identities during filesystem refresh" + }, + { + "revision": "b7a8924dfb0b9611ad8725f508a1d43cc30e6bfe", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(editor): reuse unchanged statistic snapshots" + }, + { + "revision": "6a656b674a90ebdda740ba6cb407f2f974564619", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(build): unify compiler tool references across SDK graphs" + }, + { + "revision": "88df3c798ab5186e4357e57bb5ff4d058b25b968", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "perf(editor): index command metadata per extension generation" + }, + { + "revision": "65d222ae43631083c0a5db6747300d655611c298", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): preserve drawing scopes when extensions fail" + }, + { + "revision": "199ae0dcff070c21728bc19994edb407378f43ce", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "chore(editor): track closure issues and acceptance requirements" + }, + { + "revision": "30d18489fbf68763229cd8ed35a7005f543019ad", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "docs(editor): reconcile UI acceptance with the approved plan" + }, + { + "revision": "2dc37114b7c9b5137e866efc057a66e0dfd72046", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "test(editor): align export shutdown fixture with session ownership" + }, + { + "revision": "61f855c00275d26274e6faedfdab204b11b05a2f", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "fix(editor): guard transient targets and script SDK owners" + }, + { + "revision": "fa794a5ab3f18a04d072c7f2fc923f4b678a8fea", + "author": "Aaron Liao", + "email": "hsuankailiao@gmail.com", + "committer": "Aaron Liao", + "committerEmail": "hsuankailiao@gmail.com", + "subject": "refactor(editor): unify UI ownership and editing workflows" + } + ], + "coauthorTrailerCommits": [], + "documents": [ + { + "path": "C:\\Dev\\GameEngineDev\\InnoEngine\\AGENTS.md", + "sha256": "220f7eab11a12926dbec3bf93b062e249ff08e7728a40184658e72020c014503" + }, + { + "path": "C:\\Users\\23842\\Desktop\\InnoEngine_Editor_UI_Plan.md", + "sha256": "cbb7d50a26a08e4a0d50c7e0f9aabe6c8538ae3fbe229957d2094a4b60325d9e" + } + ] +} \ No newline at end of file diff --git a/records/architecture/acceptance/editor-ui-2026-10-30/test-cases.csv b/records/architecture/acceptance/editor-ui-2026-10-30/test-cases.csv new file mode 100644 index 000000000..bdec36c13 --- /dev/null +++ b/records/architecture/acceptance/editor-ui-2026-10-30/test-cases.csv @@ -0,0 +1,2068 @@ +profile,name,outcome,duration,evidence +Contracts,"Inno.Build.Tests.SupportPackClosureValidationTests.StaticClosureRejectsAnAdditionalArchive(extra: ""unselected/fixture-target/extra.a"")",Passed,00:00:00.0028551,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryTests.PhysicalProviderPreservesTheExactOwnerPath,Passed,00:00:00.0058332,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NuGetLoadedIdentityTests.SdkOutputScopesMatchTheActuallyLoadedRuntime(isolated: False, staleDefault: False)",Passed,00:00:03.0579093,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.LivePublicationIsNotAnAssetButPermanentImportSettingsAreBuildInputs,Passed,00:00:00.0138372,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.IdenticalPreparationSkipsProducerAndDamagedOutputsRequireRepair,Passed,00:00:00.1563698,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.UnselectedPlatformDoesNotInvalidateTheSelectedGeneration,Passed,00:00:23.2594611,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskPublisherBootstrapTests.ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuildOwnership(configuration: ""Release"")",Passed,00:03:43.7557659,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.ActiveConsumersBlockReplacementAndReleaseAllowsRepair,Passed,00:00:23.2890960,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.BootstrapOwnershipIsForwardedWithoutHidingDerivedCompilerInputs(affectsCompilation: True, expectedBuilds: 2)",Passed,00:00:00.3871505,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeDeploymentIndexTests.MissingAndUnknownIndexFieldsAreRejected,Passed,00:00:00.0012393,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.IdenticalSelectionPreservesTheImportTimestampAndChangedSelectionReplacesIt,Passed,00:00:00.0130587,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.OwnedRestoreTests.EmptyProjectSelectionFailsBeforeTakingOwnership,Passed,00:00:00.0007543,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--clean"", ""true""])",Passed,00:00:00.0000283,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.WorkspaceRetirementUsesTheActualProducerLease,Passed,00:00:00.0028310,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.TargetCleanupPreservesOtherTargetsSharedArtifactsAndTheirDependencies,Passed,00:00:00.0044341,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackClosureValidationTests.ACustomBackendUsesItsDeclaredGroupWithoutStandardLibraryNames,Passed,00:00:00.0025637,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.EditorShaderProfileTests.SharedCompositionShadersConsumeExplicitProductFacts(platform: ""WindowsX64"", shaderPlatform: ""windows-x64"", apiCount: 4)",Passed,00:00:00.5437651,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeDeploymentIndexTests.UnknownSystemAndSuffixResolveThroughExplicitMappings,Passed,00:00:00.0047544,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.CancellationCannotPublishAnArtifact,Passed,00:00:00.7054570,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntrySdkSelectionTests.InvalidSdkSelectionFailsBeforeCheckoutOwnership(options: [""--dotnet"", ""dotnet"", ""--dotnet"", ""dotnet""])",Passed,00:00:00.0001910,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskPublisherBootstrapTests.ProductPublishPropertiesDoNotReachFoundationBootstrap(framework: ""net10.0"", runtimeIdentifier: ""win-x64"")",Passed,00:00:10.5820538,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.ForeignTargetIsRejectedBeforeInvokingTheProfile,Passed,00:00:00.0070980,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.BuildProfileRejectsNamesThatAreInvalidOnEitherTarget(productName: ""CON"")",Passed,00:00:18.4821709,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.EnvironmentUsesTheProjectSelectedToolsWithoutChangingTheParent,Passed,00:00:00.2358658,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeComponentBuildOptionsTests.LinkageOrderedArgumentsAndConfigurationBytesAllChangeRecipeIdentity,Passed,00:00:00.0668981,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedBuildInputSnapshotTests.RuntimeInputEvaluationDoesNotShareOrdinaryRestoreOutputs,Passed,00:00:09.9274417,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.EditorShaderProfileTests.FrozenProductSourcesKeepTheExplicitOfflineToolOwner,Passed,00:00:00.7180522,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.FreshTargetChangePreventsRetiringAnOriginalCandidate,Passed,00:00:00.0023274,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.UnprotectedProcessWorkIsRetiredWithoutEvictingReusablePayloadsUnderBudget,Passed,00:00:00.0029581,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.BackendDeclarationUsesItsProducerOptionsWithoutGeneratingBindings(audio: True),Passed,00:00:00.0043574,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.InputsChangingDuringCompilationNeverEnterTheBuildRegistry,Passed,00:00:00.2574636,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeWorkspaceReuseTests.BindingGenerationChangesReuseTheWorkspaceAndCopiedInputLocation(aggregate: True, externalBindings: False)",Passed,00:00:00.2204512,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--dry-run""])",Passed,00:00:00.0008819,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BindingDefinitionTests.ExecutableOrNonLiteralDefinitionsFailBeforeOutputCreation(body: """")",Passed,00:00:00.1088840,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeProductExecutorTests.NewTargetAndBackendUseTheSameExecutorAndStaticLinkageCanUseAStandaloneProducer,Passed,00:00:00.1088623,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.ChangedInputAfterOwnershipWaitNeverEntersPreparation,Passed,00:00:00.0265524,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.BuildProfileRejectsNamesThatAreInvalidOnEitherTarget(productName: ""nul.txt"")",Passed,00:00:18.9286267,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.CancellationAndDurationLimitPreserveUnprocessedArtifacts,Passed,00:00:00.0022294,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheOwnerCatalogTests.CorruptRegistrationOrChangedRoleIsRejectedBeforeRetirement,Passed,00:00:00.0121704,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.WorkingClaimPrecedesPayloadAndNeverAdoptsUnrecordedDirectories,Passed,00:00:00.0201666,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.CompilationWorkspaceMetadataUsesTheExistingRegisteredRetirementProtocol,Passed,00:00:00.0964307,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackClosureValidationTests.MissingDeclaredArchiveFailsEvenWhenItsGroupContainsAnotherArchive,Passed,00:00:00.0014698,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.RepeatedPublicationReusesOneVerifiedRuntimeWithoutChangingItsFiles,Passed,00:00:00.1180761,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.ValidPublicationIsReusedAcrossBuildsAndRejectsSameLengthSameTimeSourceChanges,Passed,00:00:00.5504437,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.StaticComponentsHaveOneAggregateOwnerAndNoUnusedStandaloneProducer,Passed,00:00:00.0005539,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SolutionBootstrapOwnershipTests.SolutionSharesOneBootstrapOwnerAndRenewsItForTheNextBuild,Passed,00:00:01.8000246,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ArchitectureVerificationCommandTests.VerifyRoutesToTheArchitectureCommandWithoutPreparingAProduct,Passed,00:00:09.2964925,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryTests.CanceledWaitDoesNotRemoveAnotherOperationsExecutionPath,Passed,00:00:00.1615663,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.DamagedArtifactDoesNotReplaceAnExistingConsumerCopy,Passed,00:00:00.0216351,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedDeploymentTests.DotNetProvidersConsumeAnOpenFrozenTargetSelection,Passed,00:00:00.0032732,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderToolSelectionTests.OfflineToolsExcludeCmakeWhileRuntimeBindingsKeepTheSelectedSdk,Passed,00:00:00.0082449,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: ""macos-arm64"", runtime: ""osx-arm64"", aheadOfTime: False, trimmed: False)",Passed,00:00:01.6343459,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProductNativeCostTests.RealHotProductBatchReadsLessThanSequentialPublicProducers,Passed,00:00:00.0086656,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.CppBridgeAndManagedBindingsShareOneCompletedGeneration,Passed,00:00:12.8677546,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.MissingPackFailsPromptlyWhileAsynchronousPreparationIsPending,Passed,00:00:18.4873995,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackClosureValidationTests.RequirementPathsCannotEscapeTheirOwner(path: ""C:/outside"")",Passed,00:00:00.0000213,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.ManagedSelectionPreservesExactNativeIdentitiesInStableProjectOrder,Passed,00:00:00.0144136,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.ProfileRejectsPersistentDataPathsOutsidePortableApplicationFolders(path: ""C:/absolute"")",Passed,00:00:18.4727004,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.ReacquiringAFailedBuildNodeReleasesThePublicationOwner,Passed,00:00:00.0047468,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.GeneratorRestoreScopeTests.CompilerToolsKeepTheirSdkOutputOwnerWhenGivenAnExplicitRuntime(relative: ""src/foundation/core/Inno.Core.Serialization.Genera""···)",Passed,00:00:00.5266235,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.ProfileRejectsPersistentDataPathsOutsidePortableApplicationFolders(path: ""studio//game"")",Passed,00:00:18.4301005,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.DuplicateTargetRegistrationsFailBeforeAnyBuild,Passed,00:00:00.0010692,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryBuildTests.ProcessTempDirectoriesSelectCompatibleCMakeWorkspaces,Passed,00:00:11.8507127,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.PlatformOwnershipContractTests.UnsupportedHostAndTargetFailBeforeResolvingAnSdkOrCreatingArtifacts(system: ""MacOS"", architecture: ""arm64"", target: ""windows-x64"")",Passed,00:00:00.0022521,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.GameBuildRejectsManagedProjectOutputWithoutLeavingAProduct,Passed,00:00:19.5835976,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryTests.OpeningExistingToolOutputsPreservesThePublishedArtifactFileSet,Passed,00:00:00.0147648,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.ProtectedArtifactsCanExceedTheBudgetWithoutBecomingCandidates,Passed,00:00:00.0013482,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.EditorHostRoutingPreservesTheProductReferenceContext(sdkHost: ""win-x64"", expectedTarget: ""windows-x64"")",Passed,00:00:00.5938317,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackClosureValidationTests.RequirementPathsCannotEscapeTheirOwner(path: ""native/../outside"")",Passed,00:00:00.0002806,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ShaderBuildInputsTests.FreshVerificationIncludesContentAndTheCompleteInventory(mutation: ""add"")",Passed,00:00:00.0078785,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.ChangedSelectedSourceCannotPublishOrReplaceAnExistingRuntime,Passed,00:00:00.0689837,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProductNativeCacheLookupTests.ConcurrentHotRequestsShareReadOwnershipWithoutWorkspaceDeadlock,Passed,00:00:00.2522439,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCachePublisherTests.ProcessOwnedLoadsProtectTheirExactHostDependency,Passed,00:00:00.5307212,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.GameContentCompilerBindingTests.BindingRejectsMissingOrMismatchedComponentsBeforeAnyWork,Passed,00:00:00.0015868,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryTests.RelativePathsAreRejectedBeforeCreatingAnAlias,Passed,00:00:00.0011234,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.BridgeBackendDeclarationExcludesHostGeneratedOutput(ui: False),Passed,00:00:00.0053725,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BuildProfileStoreRejectsCorruptCurrentFormatWithoutFallback,Passed,00:00:20.0884766,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedDeploymentTests.CapabilityInputsAndPublicationArtifactsAreImmutable,Passed,00:00:00.0003697,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: ""windows-x64"", runtime: ""win-x64"", aheadOfTime: True, trimmed: True)",Passed,00:00:01.4726817,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.HostRuntimeClosureBuildTests.RuntimeDiscoveryReceivesPrivateDependenciesWithoutExposingTheirCompileAssets(exposeClosure: True),Passed,00:00:51.0713115,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.ProcessParticipantsReuseOnlyTheSameActiveDeclaredOperation,Passed,00:00:00.0249159,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.UnselectedToolchainAndCancellationNeverRunAComponentProducer,Passed,00:00:00.0017125,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntrySdkSelectionTests.InvalidSdkSelectionFailsBeforeCheckoutOwnership(options: [""--dotnet"", ""inno-missing-sdk-executable""])",Passed,00:00:00.0029441,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.NewPublicationRetainsTheImmutableInputsHeldByEarlierReaders,Passed,00:00:00.1187529,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.ProductClosureFreezesOrderAndRejectsDuplicateOrUnfulfilledDependencies,Passed,00:00:00.0007439,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskHostPublicationTests.InvalidInputIdentityFailsBeforeCreatingOutput(relative: ""../escaped.dll"")",Passed,00:00:00.0009296,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheMetadataRetirementTests.MetadataConvergesWithoutBytePressureAndDryRunDoesNotRetireClaims,Passed,00:00:00.0211133,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.CancellationDuringValidationPreservesTheInstalledPack,Passed,00:00:00.0618737,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.OpenTargetAcceptsItsExplicitSdkInvocationBeforeSourceValidation,Passed,00:00:00.0016021,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.FailedOrCanceledGenerationKeepsTheLastCompleteBundle,Passed,00:00:10.7883955,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.PlatformImportChoiceChangesOfficialGenerationAndFrozenSelection(importMode: LibraryImport),Passed,00:00:17.6216971,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildOutputOwnershipTests.ConcurrentSdkRequestsKeepExplicitOutputOwnersForTheSameTarget,Passed,00:00:03.4983829,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.UnsupportedPlatformDeploymentPairNeverStartsSupportPackPreparation,Passed,00:00:18.5173081,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.EditorShaderProfileTests.BackendConsumesAProductProfileWithoutSelectingAKnownNativeTarget(platform: ""WindowsX64"", shaderPlatform: ""windows-x64"", nativeTarget: ""fixture-new-cpu"", apiCount: 4)",Passed,00:00:00.5584644,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.BuildProfileRejectsNamesThatAreInvalidOnEitherTarget(productName: ""Bad:Name"")",Passed,00:00:19.0870640,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRegistrationBuildTests.PreEvaluatedProjectsBindPrivatePublishersToTheWorkerTaskRegistry,Passed,00:00:43.6935853,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackPublisherTests.PreflightFailuresLeaveNoPublicationDirectory(failure: ""wrong-target"")",Passed,00:00:00.0010926,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheWorkspaceLinkTests.WorkspaceLibraryAliasesAreCountedOnceAndRetiredWithoutFollowingLinks,NotExecuted,00:00:00.0010000,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainWorkingDirectoryTests.OccupiedAliasFailsWithoutRemovingTheOccupantAndReleasesOwnership,Passed,00:00:00.0028247,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.AlreadyMissingEntriesAreNotReportedAsRecoveredSpace,Passed,00:00:00.0026959,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.PublicationRejectsChangedOrIncompleteManagedSelection,Passed,00:00:00.0151014,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.GameBuildPublishesOnlyVerifiedContentPacksAndRuntimeAssemblies(targetValue: ""macos-arm64"")",Passed,00:00:22.0006245,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.BootstrapRetirementPreservesCurrentProcessAndRemovesOnlyConfirmedDeadOwners,Passed,00:00:01.3289953,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeBindingGenerationTests.ManagedSelectionPreservesImportInitializationIndependentlyOfLinkage(linkage: Static, runtimeImports: True)",Passed,00:00:00.0145975,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.CompilerInputChangesCannotReuseThePreviousRuntime(itemType: ""GlobalAnalyzerConfigFiles"")",Passed,00:00:00.4663247,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.BridgeBackendDeclarationExcludesHostGeneratedOutput(ui: True),Passed,00:00:00.0060104,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.RepositoryWikiTests.CliLogicalRuntimeReferencesIncludeAnimation,Passed,00:00:05.2273085,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.ActiveParentOutsideTheProposalProtectsItsDependency,Passed,00:00:00.0122424,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCompositionTests.DistributionRejectsAmbiguousOrIncompleteRegistrationSets,Passed,00:00:00.0021414,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.FailedImportDoesNotWriteTheAuthoringTreeOrReplaceAnExistingArtifact,Passed,00:00:02.1525213,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.CheckOnlyDetectsModifiedOutputsWithoutPublishing,Passed,00:00:24.5318057,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.SupportPackRejectsMissingTextOrUiRuntime(nativeRuntime: ""libinno-text-release.dylib"")",Passed,00:00:18.8352256,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.CancelingAHostConsumerRetainsThePublisherAndPreviousConsumerCopy,Passed,00:00:00.1115738,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.CancellationDoesNotDisturbAnExclusivePublisher,Passed,00:00:00.0129196,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBuildContextTests.SelectedCheckoutOwnsIntermediatesEvenWhenTheToolRunsFromAnotherCheckout,Passed,00:00:00.0013924,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.MaterializationPreservesIdenticalTimestampsAndRejectsChangesAfterTheSourceSnapshot,Passed,00:00:00.1835602,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeProductExecutorTests.IncompleteRoleInventoryIsRejectedBeforePublication,Passed,00:00:00.0619572,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.StaticGraphRestoreTests.ProductRestoreAcceptsLargeInheritedProperties(inheritSolution: True),Passed,00:00:14.1086994,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.ResolvedPackageBytesCannotReuseThePreviousRuntime(assetGroup: ""compile"")",Passed,00:00:00.4776637,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.FileProductPublicationTests.SingleFilePublicationRetainsTheNativeClosureAndRejectsMissingInputs(platform: ""WindowsX64"", nativeFileName: ""fixture.dll"")",Passed,00:00:11.4170793,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryBuildTests.EditorEntryOwnsItsReferenceBuildWithoutEnablingDesignTimeBuilds(sdkHost: ""win-x64"", designTime: True)",Passed,00:00:00.5325035,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProductNativeCacheLookupTests.CompleteHitUsesOneFreshUnionAndDoesNotInvokeProducers,Passed,00:00:00.1686085,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.PlatformOwnershipContractTests.SharedBackendSourcesKeepDistinctRuntimeBindingAndOfflineToolOwners(target: ""windows-x64"")",Passed,00:00:00.0027932,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeComponentBuildOptionsTests.StaticRecipesDeclareOnlyNativeSourceRootsThatTheyActuallyConsume,Passed,00:00:00.0003814,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BuildProfileStorePreservesCommittedDocumentWhenCandidateIsInvalid,Passed,00:00:18.5605194,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.InputMutationDuringOutputValidationRejectsSelectionAndPreservesTheCurrentPack(cacheHit: False),Passed,00:00:00.0191027,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.WorkspacePreferenceDoesNotPermanentlyPinAnIdleWorkspace,Passed,00:00:00.0206485,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: ""windows-x64"", runtime: ""win-x64"", aheadOfTime: False, trimmed: True)",Passed,00:00:01.5821398,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeWorkspaceReuseTests.SourceChangesReuseTheWorkspaceButPublishDistinctImmutableProducts,Passed,00:00:00.2202123,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.OwnedRestoreTests.CancellationDoesNotEnterRestoreOrRemoveAnotherOwnersLock,Passed,00:00:00.0116792,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.CancellationRejectsCandidateBeforeInstallation,Passed,00:00:00.0013846,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.CapturePreservesStructuredArgumentsAndChildOnlyEnvironment,Passed,00:00:00.2346454,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.PlatformValidatorRejectsForeignAbiDespiteTheExpectedFilename(windows: False),Passed,00:00:18.3736598,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BuildTargetInventoryCannotBeMutatedThroughItsCollectionView,Passed,00:00:19.0620274,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.BusyOwnerDefersTheWholeCrossOwnerOperation,Passed,00:00:00.0029534,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedBuildInputSnapshotTests.FrameworkSelectionUsesTheSdkAndRestoredTargetSemantics(framework: ""netstandard2.0"")",Passed,00:00:09.5715793,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeDeploymentIndexTests.InvalidDeploymentPathsFailBeforeResolution(path: ""/absolute.bin"")",Passed,00:00:00.0355402,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.PreviewAndExecutionUseTheSameProposalAndFreshRoots,Passed,00:00:00.0023983,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeWorkspaceReuseTests.IncompatibleCompilerDeclarationsUseSeparateWorkspaces,Passed,00:00:00.1829871,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingSelectionLifetimeTests.PublicationWithoutItsCompiledSelectionFailsBeforePreparingTheTaskRuntime,Passed,00:00:00.3623336,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NuGetBindingDependencyTests.RestoredRuntimePackageMatchesItsDeclaredVersionAndNuGetContentIdentity,Passed,00:00:01.5228936,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.WaitingWriterCanCancelWithoutEnteringPreparationOrDisturbingTheCurrentWriter,Passed,00:00:00.0636571,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.ResolvedPackageBytesCannotReuseThePreviousRuntime(assetGroup: ""analyzers"")",Passed,00:00:00.4952693,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.HostConsumerYieldsItsNodeAndWaitsForThePublicationOwner,Passed,00:00:32.0467567,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.EditorShaderProfileTests.SdkAddedReferencesRetainTheProductTargetAndShaderProfile,Passed,00:00:00.6731167,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.InvalidInstalledSupportPackDoesNotTriggerProvisioning,Passed,00:00:18.3675187,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.OwnedRestoreTests.WritersYieldAndNeverEnterRestoreTogether,Passed,00:00:00.0259786,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBuildContextTests.ContextRejectsMissingCheckoutAndUnsupportedConfiguration,Passed,00:00:00.0014151,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.ManifestRejectsEscapingDirectoriesAndMalformedRecords,Passed,00:00:00.0075067,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.WorkingRegistrationRefusesToAdoptUnknownExistingPayload,Passed,00:00:00.0077608,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.BootstrapOwnershipIsForwardedWithoutHidingDerivedCompilerInputs(affectsCompilation: False, expectedBuilds: 1)",Passed,00:00:00.2577909,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainEnvironmentTests.NativeBuildRootRequiresAnOwner,Passed,00:00:00.0007065,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedBuildInputSnapshotTests.FixedExecutionCompilesPlannedBytesWhenTheOriginalSourceChangesAndReturns,Passed,00:00:13.2498232,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.SharedBuildsYieldWhileWaitingAndSerializeWrites,Passed,00:00:00.2981848,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCachePublisherTests.SizeAccountingIsRecordedByTheProducerWithoutServingAsIntegrityValidation,Passed,00:00:00.4934515,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.EditorShaderProfileTests.SharedCompositionShadersConsumeExplicitProductFacts(platform: ""BrowserWasm32"", shaderPlatform: ""browser-wasm"", apiCount: 1)",Passed,00:00:00.6534096,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeComponentBuildOptionsTests.ProductPlanDeclaresStaticArchivesAndDoesNotGuessLinkageFromTheHost,Passed,00:00:00.0055413,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.PluginBuildIsDeterministicSourceOnlyAndUsesTheInstallContract,Passed,00:00:18.6852660,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.CompilerToolBuildScopeTests.AnalyzerRuntimeAndTransitiveEdgesShareOneCompilerToolBuild(configuration: ""Release"")",Passed,00:00:12.4649704,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.PreparationIndexFailureReportsCommittedPublicationWithoutThrowing,Passed,00:00:00.1158356,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeWorkspaceReuseTests.BindingGenerationChangesReuseTheWorkspaceAndCopiedInputLocation(aggregate: False, externalBindings: False)",Passed,00:00:00.2296679,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.CompilerToolBuildScopeTests.AnalyzerRuntimeAndTransitiveEdgesShareOneCompilerToolBuild(configuration: ""Debug"")",Passed,00:00:10.0041163,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.InvalidOwnershipAndDependencyCyclesFailBeforeRemoval,Passed,00:00:00.0022912,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntrySdkSelectionTests.InvalidSdkSelectionFailsBeforeCheckoutOwnership(options: [""--dotnet""])",Passed,00:00:00.0022632,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeDeploymentIndexTests.InvalidDeploymentPathsFailBeforeResolution(path: ""../outside.bin"")",Passed,00:00:00.0002913,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedOutputOwnershipTests.SdkSeparatesNeutralAndProductOutputsBeforeRestore(configuration: ""Release"", artifacts: True)",Passed,00:00:08.3500434,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.CanceledProducerCannotPublishItsCandidate,Passed,00:00:00.0792413,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BackgroundArtifactExportRejectsACorruptCurrentFormatManifest,Passed,00:00:18.3045600,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.GameBuildPublishesOnlyVerifiedContentPacksAndRuntimeAssemblies(targetValue: ""windows-x64"")",Passed,00:00:21.6654483,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.CaptureRejectsFailedTools,Passed,00:00:00.2345175,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NuGetBindingDependencyTests.RuntimeConsumptionUsesAnExactPackageWithoutARepositorySourceReference(project: ""backends/Sdl3/native/Inno.Native.Sdl3/Inno.Native.""···)",Passed,00:00:00.5432079,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.SourceInputIdentityDetectsSelectedInputsWithoutGeneratingOrReadingUnselectedTargets,Passed,00:00:05.8749465,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""support-pack"", ""--clean""])",Passed,00:00:00.0000312,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationProcessTests.DictionaryMappingsRemainStableAcrossIndependentProcesses,Passed,00:00:11.3029703,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.SupportPackRejectsMissingMiniAudioRuntime,Passed,00:00:18.6426271,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.OwnedRestoreTests.ExactPropertiesAreForwardedAndFailureReleasesOwnership,Passed,00:00:00.0014463,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.MissingSupportPackIsPreparedBeforeTheOwnerThreadExport,Passed,00:00:33.3984260,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.DistributionDoesNotDeriveProductTargetsFromTheToolHost,Passed,00:00:00.0037088,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.SourceNoiseDoesNotWeakenExplicitNativeInputDeclarations,Passed,00:00:00.1648993,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainProcessTests.CancellationTerminatesTheActiveProcessAndItsChild(rawArguments: False),Passed,00:00:00.5546545,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.BackendDeclarationUsesItsProducerOptionsWithoutGeneratingBindings(audio: False),Passed,00:00:00.0044816,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRestoreEvaluationTests.RestoreDoesNotReusePackageContentItemsDuringCompilation(ownedRestore: True),Passed,00:00:00.3608357,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.EditorShutdownDrainsSupportPackCleanupBeforeReleasingExportServices,Passed,00:00:18.8382745,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: """", runtime: """", aheadOfTime: False, trimmed: False)",Passed,00:00:01.4566155,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.PlatformOwnershipContractTests.UnsupportedHostAndTargetFailBeforeResolvingAnSdkOrCreatingArtifacts(system: ""Windows"", architecture: ""arm64"", target: ""windows-x64"")",Passed,00:00:00.0004745,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainEnvironmentTests.NativeBuildRootBelongsToTheToolchainProject,Passed,00:00:00.0004391,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.CaptureDoesNotStartAnAlreadyCanceledOperation,Passed,00:00:00.0007815,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCompositionTests.EveryHostObtainsTheSameFrozenBuiltInDistribution,Passed,00:00:00.0051292,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.ProductSourceDeclarationUsesEachSelectedOwnerWithoutExecutingProducers,Passed,00:00:00.0051055,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.PreparedPlanIsExecutedOnceWithoutRepeatingDiscovery,Passed,00:00:00.0693681,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.EnvironmentRejectsAProjectWithoutWorkloadSelection,Passed,00:00:00.2350645,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""engine""])",Passed,00:00:00.0012447,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.IdenticalPublicationReusesTheExistingImmutableDirectory,Passed,00:00:00.0713356,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ArchitectureVerificationCommandTests.NativeVerificationRejectsTargetsWithoutAnEditorRunnerBeforeCreatingState,Passed,00:00:01.3068595,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.CorruptedImmutableRuntimeIsRepairedBeforeReuse,Passed,00:00:00.4044772,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.MissingSelectedSdkFailsBeforeCreatingCandidatesAndPreservesPublishedBindings,Passed,00:00:09.0405041,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.PythonBytecodeIsDerivedWhileSourceAndExplicitCompilerInputsRemainVerified,Passed,00:00:00.0187950,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedBuildInputSnapshotTests.PackageDeclarationsBelongToFrozenCompilationInputs,Passed,00:00:10.7423433,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.CommittedGameRemainsSuccessfulWhenItsFinalProgressObserverThrows,Passed,00:00:22.8980490,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeDeploymentIndexTests.InvalidDeploymentPathsFailBeforeResolution(path: ""C:/outside.bin"")",Passed,00:00:00.0005603,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.SupportPackRejectsForeignNativeRuntime,Passed,00:00:18.7330807,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--configuration""])",Passed,00:00:00.0000358,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainProcessTests.RawRunnerRejectsFailedAndAlreadyCanceledTools,Passed,00:00:00.6704932,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedBuildInputSnapshotTests.SelectedSdkGraphDetectsSameLengthSourceResourceImportAndNewFilesWithoutCompiling,Passed,00:00:28.0133694,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProductNativeCacheLookupTests.DamagedMemberFallsBackWithoutReturningPartialProducts,Passed,00:00:00.2945645,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheWorkspaceLinkTests.WorkspacesRejectDirectoryLinksEvenWhenTheTargetIsOwned,Passed,00:00:00.3193654,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--unrecognized"", ""value""])",Passed,00:00:00.0000406,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.SourceDeclarationFreezesRuntimeAndToolOptionsBeforeBindingExists(tools: False),Passed,00:00:00.0114276,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.ResolvedPackageBytesCannotReuseThePreviousRuntime(assetGroup: ""runtime"")",Passed,00:00:00.4836458,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.CompilerInputChangesCannotReuseThePreviousRuntime(itemType: ""EditorConfigFiles"")",Passed,00:00:00.4772947,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingToolchainTaskTests.MissingOrCanceledDefinitionCannotCreateGenerationCandidates(canceled: True),Passed,00:00:00.0007969,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.PlatformPackRejectsAMissingContentSource(windows: False),Passed,00:00:18.3819504,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeDeploymentIndexTests.InvalidDeploymentPathsFailBeforeResolution(path: ""part\\wrong.bin"")",Passed,00:00:00.0002686,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.PlatformImportChoiceChangesOfficialGenerationAndFrozenSelection(importMode: DllImport),Passed,00:00:17.4320134,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.PersistentLeasesCannotBeInsideRetiredDirectoriesOrSharedByEntries,Passed,00:00:00.0019502,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeProductExecutorTests.UnsupportedLinkageIsRejectedBeforeBindingOrStaging,Passed,00:00:00.0039033,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskHostPublicationTests.InvalidInputIdentityFailsBeforeCreatingOutput(relative: ""C:/escaped.dll"")",Passed,00:00:00.0014122,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.ManifestChecksIdentityBytesAndTheCompleteOutputSet,Passed,00:00:00.0222859,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskPublisherBootstrapTests.ProductPublishPropertiesDoNotReachFoundationBootstrap(framework: ""net10.0-browser"", runtimeIdentifier: ""browser-wasm"")",Passed,00:00:14.5386294,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.LogicalInputsCanMoveWithoutChangingIdentityAndSameTimestampTamperingInvalidatesIt,Passed,00:00:00.0113865,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolRunnerPathTests.DeepOwnedExecutableStartsAndRemainsAfterItsAliasIsReleased,Passed,00:00:00.3064455,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainProcessTests.OutputFailureTerminatesTheActiveProcessAndItsChild(standardError: True),Passed,00:00:00.5351474,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.InvalidPropertiesFailBeforeCreatingSharedOutput(declarations: ""Configuration=Debug;Configuration=Release"")",Passed,00:00:00.0013644,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingSelectionLifetimeTests.SelectionRemainsProductOwnedThroughBuildAndPublish,Passed,00:00:05.0479604,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeComponentBuildOptionsTests.RecipeOwnedDefinitionsCannotBeReplacedBeforeAnyToolStarts,Passed,00:00:00.0064398,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.FailedPreparationPreservesInstalledPackAndRemovesStaging,Passed,00:00:00.0793352,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedDeploymentTests.OpenProviderCatalogDispatchesCustomIdentityWithoutPlatformBranches,Passed,00:00:00.0010088,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.PlatformValidatorRejectsForeignAbiDespiteTheExpectedFilename(windows: True),Passed,00:00:18.7712753,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ProcessOwnershipTests.MalformedSubprocessClaimsPreventRetirement(malformed: ""{\""directory\"":\""valid\"",\""processId\"":\""wrong\"",\""···)",Passed,00:00:00.0165108,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeWorkspaceReuseTests.BindingGenerationChangesReuseTheWorkspaceAndCopiedInputLocation(aggregate: False, externalBindings: True)",Passed,00:00:00.2127185,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.OneBatchReusesSharedReadsOnlyWithinEachFreshPhase,Passed,00:00:46.2729519,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingDefinitionTests.UndeclaredTargetNeverFallsBackToTheHostAbi,Passed,00:00:00.0074189,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: ""browser-wasm"", runtime: """", aheadOfTime: False, trimmed: False)",Passed,00:00:01.6509646,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BuildTargetsAreOpenIdsOwnedByTheRegisteredAdapterCatalog,Passed,00:00:18.6204852,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.RegistrationAndRetirementCoordinateThroughTheSamePersistentLease,Passed,00:00:00.1445434,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformBackendIntegrationTests.IntegrationsPreserveExactShaderAndNativeTargetIdentities,Passed,00:00:00.0007136,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeWorkspaceReuseTests.BindingGenerationChangesReuseTheWorkspaceAndCopiedInputLocation(aggregate: True, externalBindings: True)",Passed,00:00:00.2207748,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.RepositoryWikiTests.LogicalEditorReferencesIncludeTheSharedProductFeatures,Passed,00:00:03.8662457,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.MissingGenerationPreventsPublicationBeforeProducerOrStaging,Passed,00:00:00.0227947,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingProfileTests.BrowserAggregateBridgeRequiresExplicitExtensionAndReusesVerifiedGeneration,Passed,00:00:21.9332083,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.SupportPackRejectsMissingTextOrUiRuntime(nativeRuntime: ""libinno-ui-release.dylib"")",Passed,00:00:18.7338177,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskPublisherBootstrapTests.ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuildOwnership(configuration: ""Debug"")",Passed,00:03:50.1304237,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.FileProductPublicationTests.SingleFilePublicationRetainsTheNativeClosureAndRejectsMissingInputs(platform: ""MacOSArm64"", nativeFileName: ""libfixture.dylib"")",Passed,00:00:11.5495008,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""rebuild""])",Passed,00:00:00.0000233,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BackgroundArtifactExportUsesAnOwnerThreadSerializationSnapshot,Passed,00:00:18.8026809,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.OwnedRestoreTests.RestoreRemovesTheParentSolutionEntryPoint,Passed,00:00:00.0012056,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackClosureValidationTests.StaticClosureRejectsAnAdditionalArchive(extra: ""custom/fixture-target/extra.a"")",Passed,00:00:00.0021225,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.EditorShaderProfileTests.BackendConsumesAProductProfileWithoutSelectingAKnownNativeTarget(platform: ""MacOSArm64"", shaderPlatform: ""macos-arm64"", nativeTarget: ""fixture-new-cpu"", apiCount: 3)",Passed,00:00:00.5580423,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.RealPlatformPreflightRejectsUnavailableSdkOrHostWithoutCreatingOutput(unsupportedHost: True),Passed,00:00:00.0013779,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.ConcurrentIdenticalRequestsPrepareOnlyOneCandidate,Passed,00:00:00.1001638,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.DeploymentUsesExactProductsAndPreservesThePriorTreeOnCancellationOrTampering,Passed,00:00:00.1877205,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.ActiveReaderPreventsRetirementUntilItReleasesOwnership,Passed,00:00:00.0116551,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskHostPublicationTests.InvalidInputIdentityFailsBeforeCreatingOutput(relative: ""nested//escaped.dll"")",Passed,00:00:00.0016645,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.ConcurrentRequestsShareOneCompleteProductAndTamperingIsRepaired,Passed,00:00:00.3189859,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.SharedInitialInputsAreHashedOnceButEveryPublicationRechecksTheirBytes,Passed,00:00:00.1978453,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.BuildProfileRejectsNamesThatAreInvalidOnEitherTarget(productName: ""Trailing."")",Passed,00:00:18.6232765,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.SourceChangesSelectIndependentGenerationsAndTamperingIsRepaired,Passed,00:00:28.6507739,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.InputMutationDuringOutputValidationRejectsSelectionAndPreservesTheCurrentPack(cacheHit: True),Passed,00:00:00.0741958,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.CanceledMaterializationPreservesTheOwnedIntermediate,Passed,00:00:00.0877073,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.DevelopmentEntryCommandTests.CancellationReturnsBeforeCreatingBuildState,Passed,00:00:00.0005072,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.CacheValidationDetectsSameLengthAndTimestampTamperingAndExtraPayload,Passed,00:00:00.1539101,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--clean"", ""--clean""])",Passed,00:00:00.0000249,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryCommandTests.InvalidCommandsFailBeforeSdkDiscoveryOrCleanup(arguments: [""editor"", ""--target"", ""one"", ""--target"", ""two""])",Passed,00:00:00.0000211,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.FrozenSdkPathsSelectIndependentHeadersWithoutChangingTheProcessEnvironment,Passed,00:00:16.3697914,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingCompilationTests.TargetCompilationReplacesHostSourceAndRejectsAMissingSelection,Passed,00:02:38.6595561,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.GameBuildWaitsForPendingStartupSceneImportBeforeValidation,Passed,00:00:21.9217831,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingSelectionBuildTests.ANewSelectionDocumentDoesNotRecompileANeutralProject,Passed,00:00:04.5506670,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.MultipurposeRecipeOwnerIncludesOnlyItsDeclaredImplementationClosure,Passed,00:00:00.0446420,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainEnvironmentTests.NativeBuildRootRejectsAnUnrelatedOwner,Passed,00:00:00.0005441,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ShaderBuildInputsTests.EmptyAuthoredDirectoriesParticipateInTheFrozenInventory,Passed,00:00:00.0018019,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.SdkDirectoryAliasesContributeContentAndDirectoryCyclesFailBeforeProducingOutput,Passed,00:00:00.7172141,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeWorkspaceReuseTests.WindowsCMakeReusesUnchangedObjectsAndPublishesChangedSourceBytes,Passed,00:01:02.1418178,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ManagedBuildInputSnapshotTests.CheckoutAliasPreservesOwnedSourcesWhileSharingPhysicalInputReads,Passed,00:00:09.3346837,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeVerificationPhaseTests.FreshPhasesDetectSameLengthSameTimestampChangesAndReuseSharedReads,Passed,00:00:00.0038832,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.StandardRestoreResolvesSdkDependenciesWithoutWritingSourceManifests(target: ""windows-x64"", runtime: ""win-x64"", aheadOfTime: False, trimmed: False)",Passed,00:00:01.4241862,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.PlatformPackRejectsAMissingContentSource(windows: True),Passed,00:00:18.5635492,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.UnregisteredProtectionRootsRejectPlanningAndFreshExecution,Passed,00:00:00.0023007,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ShaderBuildInputsTests.FreshVerificationIncludesContentAndTheCompleteInventory(mutation: ""delete"")",Passed,00:00:00.0072515,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NuGetLoadedIdentityTests.SdkOutputScopesMatchTheActuallyLoadedRuntime(isolated: True, staleDefault: False)",Passed,00:00:02.6007403,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.MissingSelectedTargetRequiresRestoreBeforePublication,Passed,00:00:00.1953909,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.CompilerInputChangesCannotReuseThePreviousRuntime(itemType: ""AdditionalFiles"")",Passed,00:00:00.4396395,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedOutputOwnershipTests.SdkSeparatesNeutralAndProductOutputsBeforeRestore(configuration: ""Debug"", artifacts: False)",Passed,00:00:06.1515844,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.StaticGraphRestoreTests.DevelopmentEntryRestoresItsDirectProductReferenceOwners,Passed,00:00:22.3364395,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.ChangedAuthoringGenerationCannotCommitAMixedBuildSnapshot,Passed,00:00:21.2889797,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.CanceledGameBuildLeavesNoProductOrStagingDirectory,Passed,00:00:18.8866471,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.SourceDeclarationFreezesRuntimeAndToolOptionsBeforeBindingExists(tools: True),Passed,00:00:00.0031957,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheMetadataRetirementTests.StableCurrentPointersAreNotRewrittenAndCorruptionIsReported,Passed,00:00:00.0377592,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingMaterializationTests.RelativeArtifactOwnerIsRejectedBeforeCreatingAConsumerDirectory,Passed,00:00:00.0029914,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BrowserToolchainTests.EnvironmentRejectsAnIncompleteSelectedToolchain,Passed,00:00:00.2386584,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeBindingGenerationTests.ManagedSelectionPreservesImportInitializationIndependentlyOfLinkage(linkage: Static, runtimeImports: False)",Passed,00:00:00.0137697,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.RepairWaitsForReadersAndCancellationPreservesThePinnedDirectory,Passed,00:00:00.2960849,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BindingGenerationBatchTests.AFailedBatchPublishesNoPartialClosureAndPreservesEarlierOutputs,Passed,00:00:21.1781303,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.CollidingManagedSelectionDoesNotReplaceAnExistingCompleteSelection,Passed,00:00:00.0065106,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactIdentityTests.RequestDescriptorRejectsUnavailableOutputs,Passed,00:00:00.0126252,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProcessOwnershipTests.ProcessIdentityIncludesItsStartTimeAndInvalidIdentityIsConservative,Passed,00:00:00.0023000,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.WindowsColdCMakeBuildCompilesCopiedInputsEvenWhenOriginalBytesAreTemporarilyChanged,Passed,00:00:10.8026172,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.PlatformOwnershipContractTests.StandardShaderTargetIsIndependentOfTheOfflineToolTarget,Passed,00:00:00.0008670,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.RegisteredCustomTargetInstallsWithoutCorePlatformBranches,Passed,00:00:00.0609543,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackPublisherTests.PreflightFailuresLeaveNoPublicationDirectory(failure: ""null-plan"")",Passed,00:00:00.0004597,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.CompilationFailureLeavesNoProductOrStagingDirectory,Passed,00:00:20.8873222,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.OfflineToolsDoNotRequestRuntimeBindings,Passed,00:00:00.0164881,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.DamagedInstalledArtifactCannotBeSilentlyReplacedWhileReadersHoldIt,Passed,00:00:00.2695830,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPublisherTests.CancellationDuringPlanningLeavesNoOutputAndExecutesNoPlan,Passed,00:00:00.0015117,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCachePublisherTests.ActivePublisherPreventsRetirementDuringReadingHandoff,Passed,00:00:00.1578762,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NuGetLoadedIdentityTests.SdkOutputScopesMatchTheActuallyLoadedRuntime(isolated: True, staleDefault: True)",Passed,00:00:02.5793930,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.CanceledBootstrapNeverCreatesACacheOrReader,Passed,00:00:00.2460505,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeLinkPlanTests.OrderedLinkerInputsAreFrozen,Passed,00:00:00.0003823,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.UnknownManagedDeploymentNeverStartsSupportPackPreparation,Passed,00:00:18.6872836,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.ProfileRejectsPersistentDataPathsOutsidePortableApplicationFolders(path: ""../other-game"")",Passed,00:00:18.5760503,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.CompilerInputChangesCannotReuseThePreviousRuntime(itemType: ""AdditionalCompileInputs"")",Passed,00:00:00.4409525,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BindingDefinitionTests.ExecutableOrNonLiteralDefinitionsFailBeforeOutputCreation(body: ""$(SDK)/config.js""···)",Passed,00:00:00.0071284,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildPipelineTests.BuildProfileRejectsNamesThatAreInvalidOnEitherTarget(productName: ""Trailing "")",Passed,00:00:18.5579141,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.CompletedPreparationProtectsWorkspaceDuringMaintenanceAndCachedRestoreReuse,Passed,00:00:00.2958531,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheWorkspaceLinkTests.FileAliasesCannotEscapeCycleDangleOrEnterImmutableArtifacts,NotExecuted,00:00:00.0010000,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedBuildInputSnapshotTests.FrameworkSelectionUsesTheSdkAndRestoredTargetSemantics(framework: ""net10.0-browser"")",Passed,00:00:09.3229164,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBuildContextTests.UnresolvedToolchainsFailBeforeIntermediatesOrSourceDiscovery,Passed,00:00:00.0013019,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.StaticGraphRestoreTests.ProductRestoreAcceptsLargeInheritedProperties(inheritSolution: False),Passed,00:00:21.9535249,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ToolchainProcessTests.OutputFailureTerminatesTheActiveProcessAndItsChild(standardError: False),Passed,00:00:01.1047575,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeLinkPlanTests.InvalidOrUnassignedFormatsFailDuringSelection,Passed,00:00:00.0005913,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BuildCompositionTests.EditorAndStandaloneShaderToolsShareTheSameBindingFreeReleaseProducer(target: ""macos-arm64"")",Passed,00:00:00.0041776,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheOwnerCatalogTests.EmptyCheckoutAndInvalidRequestsDoNotPrepareGeneratorsOrClaimArtifacts,Passed,00:00:00.0022702,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingCMakeTests.WindowsCompilationUsesFrozenHeadersAndKeepsBindingDefinesOutOfUpstreamSources,Passed,00:00:12.9934360,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SdkRestoreTests.EditorHostRoutingPreservesTheProductReferenceContext(sdkHost: ""osx-arm64"", expectedTarget: ""macos-arm64"")",Passed,00:00:00.5728881,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeBindingGenerationTests.ManagedSelectionPreservesImportInitializationIndependentlyOfLinkage(linkage: Shared, runtimeImports: False)",Passed,00:00:00.0132867,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.HostBindingFailurePreservesBothPublishedOutputTrees,Passed,00:00:12.6520989,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.TaskRuntimeBuildTests.InvalidPropertiesFailBeforeCreatingSharedOutput(declarations: ""ArtifactsPath=other"")",Passed,00:00:00.0015556,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.BuildProfileStoreRoundTripsGeneratedCurrentFormat,Passed,00:00:18.4035129,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.PublisherBindsActualGenerationBytesAndIdentityAfterSourceDeclaration(changeBytes: True),Passed,00:00:00.2689398,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntrySdkSelectionTests.InvalidSdkSelectionFailsBeforeCheckoutOwnership(options: [""--dotnet"", ""--configuration"", ""Release""])",Passed,00:00:00.0002719,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCompositionTests.TargetsContributeDistinctImmutableProductClosuresWithoutChangingTheInvokingHost,Passed,00:00:00.0013827,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.CurrentRootsProtectDependenciesAcrossOwners,Passed,00:00:00.0044457,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.RepositoryWikiTests.CurrentWikiCompilesWithTheCurrentLogicalScriptContracts,Passed,00:08:59.7735955,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskHostPublicationTests.RetirementKeepsLiveReadersAndReleasesDeadReadersAndTheirUnreferencedRuntime,Passed,00:00:01.5974588,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.TaskRuntimeBuildTests.FailedBuildDoesNotRetainSharedOwnership,Passed,00:00:00.2310030,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedOutputOwnershipTests.SdkSeparatesNeutralAndProductOutputsBeforeRestore(configuration: ""Release"", artifacts: False)",Passed,00:00:06.1901517,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBindingGenerationTests.UnsafeManagedProjectNameIsRejectedBeforeCreatingSelectionOutput,Passed,00:00:00.0003757,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBuildContextTests.CanceledBuildDoesNotStartAComponentOrCreateIntermediates(editor: True),Passed,00:00:00.0009813,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.DotNetSdkEnvironmentTests.SelectedSdkIgnoresAnInvokingBuildsSdkIdentityAndPreservesItsNativeEnvironment,Passed,00:00:00.9960578,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProductNativeCostTests.RealPresentationBuildUsesDeclaredCMakeAndFrozenWindowSource,Passed,00:00:00.0008846,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeBuildContextTests.CanceledBuildDoesNotStartAComponentOrCreateIntermediates(editor: False),Passed,00:00:00.0022943,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.NativeBindingGenerationTests.ManagedSelectionPreservesImportInitializationIndependentlyOfLinkage(linkage: Shared, runtimeImports: True)",Passed,00:00:00.0133870,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BgfxNativeProfileTests.InvocationOwnsAnImmutableArgumentSnapshot,Passed,00:00:00.0010135,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCacheRetirementTests.ChangedInventoryDoesNotDeleteTheEarlierGeneration,Passed,00:00:00.0036642,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeRecipeClosureTests.SourceDeclarationDoesNotRequireOrCreateGeneratedOutputs,Passed,00:00:00.0221382,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.ExportFormBalancesItsTablesWhenARegisteredTargetLabelFails,Passed,00:00:18.7542247,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildCachePublisherTests.NativeCurrentRetainsItsExactOlderBindingGeneration,Passed,00:00:00.4475513,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.ManagedOutputOwnershipTests.SdkSeparatesNeutralAndProductOutputsBeforeRestore(configuration: ""Debug"", artifacts: True)",Passed,00:00:08.5580722,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.ProjectCommandSourceTests.InvalidExplicitSourceIsRejectedBeforeToolsAndProjectStateAreCreated,Passed,00:00:00.0095297,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeVerificationPhaseTests.DifferentDirectoryAliasesKeepLogicalMappingsButShareOnePhysicalRead,Passed,00:00:00.3021326,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.SupportPackPreparationCacheTests.PostCommitStagingCleanupFailurePreservesReadingOwnership,Passed,00:00:00.0643240,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.BuildPipelineTests.ProjectSettingsUseTheHostSuppliedInitialIdentity,Passed,00:00:18.3114544,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.SupportPackClosureValidationTests.RequirementPathsCannotEscapeTheirOwner(path: ""../outside"")",Passed,00:00:00.0001832,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,Inno.Build.Tests.NativeArtifactPublicationTests.ChangedClosureAndFailedPreparationPreserveEarlierProducts,Passed,00:00:00.1958293,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.DevelopmentEntryBuildTests.EditorEntryOwnsItsReferenceBuildWithoutEnablingDesignTimeBuilds(sdkHost: ""osx-arm64"", designTime: False)",Passed,00:00:00.6400774,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.GeneratorRestoreScopeTests.CompilerToolsKeepTheirSdkOutputOwnerWhenGivenAnExplicitRuntime(relative: ""src/runtime/generators/Inno.Runtime.Generators/Inn""···)",Passed,00:00:00.5122745,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_08_17_net10.0.trx +Contracts,"Inno.Build.Tests.BindingDefinitionTests.ExecutableOrNonLiteralDefinitionsFailBeforeOutputCreation(body: ""../config.json { public class Inner { } } p""···, rejected: True)",Passed,00:00:00.2664939,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.LogicalExamplesRequireExplicitReferencesAndExcludeHostImplementations,Passed,00:00:00.3862991,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""Inno.Build.Cli.dll contains the public BuildApplic""···, rejected: False)",Passed,00:00:00.1166925,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentConfigurationCannotOwnPlatformPolicy(configuration: ""{\""targets\"":[]}"", rejected: True)",Passed,00:00:00.1004136,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorPublicReferencePolicyFollowsCompiledVisibility(declaration: ""public class Probe { public Value value; }"", privateReference: False, rejected: False)",Passed,00:00:00.2161720,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""missing-summary"", accepted: False)",Passed,00:00:00.1795052,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { public Task Chan""···, rejected: True)",Passed,00:00:00.2876228,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/content/assets/Probe.cs"", source: ""internal class Probe { void Read() { context.With<""···, expected: ""owner-complete"", rejected: True)",Passed,00:00:00.2300365,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/composition/editor/panels/Probe.cs"", source: ""/// \n/// Removed identity.\n/// 0; }"", rejected: False)",Passed,00:00:00.2842410,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { public (int count, Raw value)""···, rejected: True)",Passed,00:00:00.2647513,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""InnoEngine.Build.dll rebuild"", rejected: True)",Passed,00:00:00.1154571,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""missing-returns"", accepted: False)",Passed,00:00:00.1699082,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.CompositionOnlyExposesNeutralAdapterContracts(owner: ""Inno.Player"", adapter: ""Inno.Adapter.Rendering.Bgfx"", rejected: True)",Passed,00:00:00.2359688,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.ExplicitWikiTargetRecordsForeignProductsWithoutSubstitutingTheirReferences,Passed,00:00:05.2892210,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationUsesTheOwningProjectInsteadOfACopiedDependency,Passed,00:00:00.2458631,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorPublicReferencePolicyFollowsCompiledVisibility(declaration: ""public class Probe { public Value value; }"", privateReference: True, rejected: True)",Passed,00:00:00.2118776,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""wrong-cref"", accepted: False)",Passed,00:00:00.1915258,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""src/composition/editor/panels/Inno.Editor.Panel.A"", dependency: ""src/composition/editor/panels/Inno.Editor.Panel.B"", expected: ""Panels cannot depend on other Panels"", rejected: True)",Passed,00:00:00.1036446,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""Browser Canvas and .iplugin use Inno.Plugins."", rejected: False)",Passed,00:00:00.1167751,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""override"", accepted: True)",Passed,00:00:00.1787861,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""platforms/WindowsX64/build/Probe"", dependency: ""build/bindings/Inno.Build.Bindings"", expected: ""platform modules must borrow"", rejected: True)",Passed,00:00:00.1051358,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorPublicReferencePolicyFollowsCompiledVisibility(declaration: ""public class Probe { protected Value Read() => nul""···, privateReference: True, rejected: True)",Passed,00:00:00.2130762,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { protected Raw Read() => defau""···, rejected: True)",Passed,00:00:00.2697169,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentCannotKeepAnAdditionalTargetPreset,Passed,00:00:00.0978858,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""platforms/WindowsX64/integrations/Inno.Integration""···, dependency: ""backends/Bgfx/native/Inno.Native.Bgfx"", expected: ""runtime surface integrations cannot depend"", rejected: True)",Passed,00:00:00.1050605,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.OwnedApiFragmentsContributeExactSignaturesWithoutDuplicatingProjectPages,Passed,00:00:00.3570936,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { private protected Raw Read() ""···, rejected: False)",Passed,00:00:00.2614186,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""build/toolchains/Probe"", dependency: ""build/bindings/Inno.Build.Bindings"", expected: ""neutral toolchains cannot depend"", rejected: True)",Passed,00:00:00.1028198,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SharedContentIoChecksResolvedSymbolsInsteadOfIoNamespaceText(operation: ""throw new System.IO.InvalidDataException(\""Invalid""···, rejected: False)",Passed,00:00:00.1755105,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.RepositoryArchitectureTests.CurrentRepositoryPassesArchitectureRules,Passed,00:04:22.4545792,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""missing-parameter"", accepted: False)",Passed,00:00:00.1687631,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/services/audio/Probe.cs"", source: ""internal class Probe { void Release() { try { } ca""···, expected: ""wrapped pending ownership"", rejected: True)",Passed,00:00:00.2179928,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksExplicitInteropRuntimeOwnership(projectLocation: ""src/services/input/Inno.Input.Probe"", rejected: True)",Passed,00:00:00.1059885,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.InventoryIncludesPrimaryRecordMembersAndInheritedContracts,Passed,00:00:00.3600589,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { public void Read() where T""···, rejected: True)",Passed,00:00:00.3497921,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""platforms/WindowsX64/integrations/Inno.Integration""···, dependency: ""backends/Bgfx/runtime/Inno.Adapter.Rendering.Bgfx"", expected: ""runtime surface integrations cannot depend"", rejected: False)",Passed,00:00:00.1068769,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""src/composition/editor/panels/Inno.Editor.Panel.A"", dependency: ""backends/ImGui/native/Inno.Native.ImGui"", expected: ""native UI access belongs"", rejected: True)",Passed,00:00:00.1084603,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentConfigurationCannotOwnPlatformPolicy(configuration: ""{\""source\"":{\""includeDirectories\"":[\""../Native/i""···, rejected: False)",Passed,00:00:00.0996062,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesBindingDefinitionOwnership(scenario: ""conditional"", expected: ""cannot evaluate conditions"", rejected: True)",Passed,00:00:00.1288808,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.TestRepositoryInputsTests.AbsentArtifactsMetadataPreservesNormalSdkOutputSelection,Passed,00:00:00.0002348,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentConfigurationCannotOwnPlatformPolicy(configuration: ""{\""extends\"":\""common.json\""}"", rejected: True)",Passed,00:00:00.1083361,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorPublicReferencePolicyFollowsCompiledVisibility(declaration: ""internal class Probe { public Value value; }"", privateReference: True, rejected: False)",Passed,00:00:00.2153588,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""entry/Rebuild/Program.cs"", rejected: True)",Passed,00:00:00.1365386,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksHandwrittenMultiParameterDeclarations(source: ""internal sealed class Probe(int first, int second)""···, rejected: True)",Passed,00:00:00.1116939,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliRejectsCompilationWideImportsAcrossSourceAndBuildInputs(relative: ""Directory.Build.props"", content: ""true null; ""···, rejected: True)",Passed,00:00:00.2729989,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/composition/player/Inno.Player.Runtime/Probe.c""···, source: ""internal class Probe { private BgfxDevice device; ""···, expected: ""backend-neutral"", rejected: True)",Passed,00:00:00.2226492,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.SignatureMappingDetectsNullableChangesAndDeletedOverloads,Passed,00:00:00.5217191,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliRejectsCompilationWideImportsAcrossSourceAndBuildInputs(relative: ""tests/Probe.cs"", content: ""using System; // global using is prohibited\ninter""···, diagnostic: ""global using"", rejected: False)",Passed,00:00:00.1054115,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ExecutableExamplesAreCompiledAgainstProjectReferences(example: ""int value = \""invalid\"";"", rejected: True)",Passed,00:00:00.1440313,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksImportedMSBuildOwnership(scenario: ""target"", expected: ""effective product target and native ABI"")",Passed,00:00:00.5422038,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliRejectsCompilationWideImportsAcrossSourceAndBuildInputs(relative: ""build/Probe.cs.txt"", content: ""global using System;"", diagnostic: ""global using"", rejected: True)",Passed,00:00:00.1081105,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.RepositoryInputTests.NullAssemblyIsRejectedBeforeReadingMetadata,Passed,00:00:00.0007593,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.CompositionOnlyExposesNeutralAdapterContracts(owner: ""Inno.Editor.Hosting"", adapter: ""Inno.Adapter.Platform.Sdl3"", rejected: True)",Passed,00:00:00.2522119,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.TypeModifiersAndRefReturnsParticipateInSignatureIdentity,Passed,00:00:00.3307339,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.InheritedDocumentationRequiresACompleteImplementedContract(scenario: ""standalone"", accepted: False)",Passed,00:00:00.1751058,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/services/audio/Probe.cs"", source: ""public class Probe { public MaEngine engine; }"", expected: ""native implementation"", rejected: True)",Passed,00:00:00.2332547,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.RepositoryInputTests.MissingRelativeAndUnavailableDeclarationsAreRejected,Passed,00:00:00.2333594,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.CompositionOnlyExposesNeutralAdapterContracts(owner: ""Inno.Shell"", adapter: ""Inno.Adapter.Audio"", rejected: False)",Passed,00:00:00.2338404,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.TestRepositoryInputsTests.ExplicitArtifactsMetadataPreservesTheRecordedAbsoluteOwner,Passed,00:00:00.0002001,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.ReplaceableBackendContractsUseOpenIdentifiers(declaration: ""public readonly record struct InputBackendId(strin""···, rejected: False)",Passed,00:00:00.2551944,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.RepositoryInputTests.EachAssemblyUsesItsOwnDeclaredSource,Passed,00:00:00.0025285,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SharedContentIoChecksResolvedSymbolsInsteadOfIoNamespaceText(operation: ""System.IO.Directory.CreateDirectory(\""cache\"");"", rejected: True)",Passed,00:00:00.1908014,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""internal class Probe { public Raw value; }"", rejected: False)",Passed,00:00:00.2580056,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""backends/Bgfx/build/ProbeBackend"", dependency: ""platforms/WindowsX64/integrations/Probe"", expected: ""reusable backends cannot depend"", rejected: True)",Passed,00:00:00.1067004,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/composition/editor/panels/Probe.cs"", source: ""using Ui = Inno.Editor.ImGui.ImGui; internal class""···, expected: ""presentation loop ID scopes"", rejected: False)",Passed,00:00:00.2299793,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { protected internal void Write""···, rejected: True)",Passed,00:00:00.2860151,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksImportedMSBuildOwnership(scenario: ""source"", expected: ""effective hand-authored Compile source"")",Passed,00:00:00.5462155,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""platforms/WindowsX64/integrations/Probe"", dependency: ""backends/Bgfx/build/ProbeBackend"", expected: ""platform base modules cannot select"", rejected: False)",Passed,00:00:00.1063373,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public unsafe class Probe { public delegate* unman""···, rejected: True)",Passed,00:00:00.2645171,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { protected class Nested { publ""···, rejected: True)",Passed,00:00:00.8052397,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.ScriptSdkReferencesPanelContractsOnlyWhenThePanelActuallyExportsThem(exportsContract: True),Passed,00:00:00.1143743,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { private Raw value; }"", rejected: False)",Passed,00:00:00.2984123,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.RepositoryInputTests.DuplicateDeclarationsAreRejectedEvenWhenTheyAgree,Passed,00:00:00.0031170,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SharedContentIoChecksResolvedSymbolsInsteadOfIoNamespaceText(operation: ""System.IO.File.ReadAllBytes(\""payload\"");"", rejected: True)",Passed,00:00:00.1691563,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""innoengine.canvas/README.md"", rejected: True)",Passed,00:00:00.1475768,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ExamplesOutsideProjectPagesAreAlsoCompiled(page: ""docs/architecture/GUIDE.md"", example: ""int value = missing;"", rejected: True)",Passed,00:00:00.1478173,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.LinksValidatePathsAndAnchorsOutsideCodeFences,Passed,00:00:00.1295473,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliRejectsCompilationWideImportsAcrossSourceAndBuildInputs(relative: ""tests/Probe.cs"", content: ""global using System;"", diagnostic: ""global using"", rejected: True)",Passed,00:00:00.1041075,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.ReplaceableBackendContractsUseOpenIdentifiers(declaration: ""public enum InputBackend { BuiltIn }"", rejected: True)",Passed,00:00:00.1672558,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/composition/editor/panels/Probe.cs"", source: ""/// \n/// Actual section.\n/// ""···, expected: ""multiple XML summary elements"", rejected: False)",Passed,00:00:00.2225182,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.NativePolicyUsesAssemblyIdentityRatherThanSourceTypeNames(assemblyName: ""Inno.Native.Bgfx"")",Passed,00:00:00.2179327,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorReferenceGroupsKeepImplementationBeforePublicApi(layout: 1, rejected: True)",Passed,00:00:00.2731734,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe { private class Nested { public""···, rejected: False)",Passed,00:00:00.2536098,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksHandwrittenMultiParameterDeclarations(source: ""internal sealed class Probe { void Apply(int first""···, rejected: True)",Passed,00:00:01.2478836,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentConfigurationCannotOwnPlatformPolicy(configuration: ""{\""source\"":{\""systemIncludeDirectories\"":[]}}"", rejected: True)",Passed,00:00:00.1122988,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/services/Probe.cs"", source: ""using Ui = Inno.Editor.ImGui.ImGui; internal class""···, expected: ""presentation loop ID scopes"", rejected: False)",Passed,00:00:00.2124913,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.NativeSurfaceAbiRemainsInsideAdapterBoundary(relative: ""src/services/platform/Inno.Platform"", rejected: True)",Passed,00:00:00.1552153,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesBindingDefinitionOwnership(scenario: ""unknown-property"", expected: ""unknown or repeated property"", rejected: True)",Passed,00:00:00.1334396,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorReferenceGroupsKeepImplementationBeforePublicApi(layout: 0, rejected: False)",Passed,00:00:00.2788448,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorImplementationReferencesRequireCompilePrivacy(privateReference: False, rejected: True)",Passed,00:00:00.2375776,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesBindingDefinitionOwnership(scenario: ""missing-input"", expected: ""binding Config input 'missing.json' is missing"", rejected: True)",Passed,00:00:00.1195040,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesBindingDefinitionOwnership(scenario: ""valid"", expected: ""binding Config"", rejected: False)",Passed,00:00:00.1216586,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.NativePolicyUsesAssemblyIdentityRatherThanSourceTypeNames(assemblyName: ""Inno.Native.MiniAudio"")",Passed,00:00:00.2182294,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.WikiCoverageTests.ContentBoundariesDistinguishConsumersFromEngineConcepts(content: ""dotnet build/cli/Inno.Build.Cli/bin/Release/net10.""···, rejected: True)",Passed,00:00:00.1312958,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.NativePlatformPolicyTests.ComponentConfigurationCannotOwnPlatformPolicy(configuration: ""{\""sdk\"":{}}"", rejected: True)",Passed,00:00:00.0996779,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksExplicitInteropRuntimeOwnership(projectLocation: ""backends/Interop/native/Inno.Native.Probe"", rejected: False)",Passed,00:00:00.1100383,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.IntegrationBoundaryTests.CliChecksActualPlatformIntegrationProjectReferences(owner: ""platforms/BrowserWasm32/integrations/Probe"", dependency: ""platforms/WindowsX64/integrations/ProbeWindows"", expected: ""one concrete platform cannot depend"", rejected: True)",Passed,00:00:00.1045335,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksHandwrittenMultiParameterDeclarations(source: ""internal sealed class Probe\n{\n void Apply(\n ""···, rejected: False)",Passed,00:00:00.1173211,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.EditorReferenceGroupsKeepImplementationBeforePublicApi(layout: 2, rejected: True)",Passed,00:00:00.2653285,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureSymbolTests.SymbolAuditFollowsPublicShapesAndRespectsEffectiveAccessibility(declaration: ""public class Probe where T : Inno.Native.MiniAu""···, rejected: True)",Passed,00:00:00.2701879,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,Inno.Tooling.Architecture.Tests.WikiCoverageTests.EditorExamplesUseBothLogicalScopesAndRecordTheirFrozenIdentity,Passed,00:00:00.3589478,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliValidatesSourceContracts(relative: ""src/services/audio/Probe.cs"", source: ""internal class Probe { void Release() { OnCleanupF""···, expected: ""diagnostic-only"", rejected: True)",Passed,00:00:00.2188736,Contracts\fc2c983e1ebe4783aa2c5833d17daff3\Contracts\23842_AARONWIN_2026-10-30_10_57_55_net10.0.trx +Contracts,"Inno.Tooling.Architecture.Tests.ArchitectureCommandTests.CliChecksHandwrittenMultiParameterDeclarations(source: ""internal sealed class Probe { System.Func /// Callback that emits presentation commands for the current frame. /// - void RenderFrame(Action drawFrame); + /// + /// Optional synchronous preparation after the owned context and display metrics are selected, + /// before frame creation. Failure prevents drawing and no partial frame is published. + /// + /// + /// The draw callback is null. + /// + /// + /// Another frame operation is active on this context. + /// + void RenderFrame( + Action drawFrame, + Action? prepareFrame = null + ); /// /// Registers a persistent render texture for presentation drawing. diff --git a/src/composition/editor/EditorProduct.props b/src/composition/editor/EditorProduct.props index b6c3ede3b..5f01c81e3 100644 --- a/src/composition/editor/EditorProduct.props +++ b/src/composition/editor/EditorProduct.props @@ -1,6 +1,9 @@ + + + @@ -14,7 +17,7 @@ - + diff --git a/src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs b/src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs index 5fa84e4e2..46af4351e 100644 --- a/src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Documents/AssetDraftDocuments.cs @@ -10,7 +10,6 @@ using Inno.Editor.Interactions; namespace Inno.Editor.Assets; - /// /// Owns detached native asset drafts with shared document, History, recovery and explicit-save semantics. /// @@ -31,9 +30,6 @@ public sealed class AssetDraftDocuments : IDisposable where TAsset : Ass private readonly string m_label; private IDisposable? m_provider; private long m_sourceRevision = -1; - private long m_updateSerial; - private Guid[] m_activeGroup = []; - /// /// Creates a feature-owned draft store; call Start after document services become available. /// @@ -77,9 +73,14 @@ string label throw new ArgumentException("A native file extension is required.", nameof(extension)); if (providerId is "." or ".." || providerId.IndexOfAny(['/', '\\', ':']) >= 0) throw new ArgumentException("The provider identifier must be a safe single directory name.", nameof(providerId)); - m_assets = assets; m_interactions = interactions; m_serialization = serialization; - m_sources = m_assets.CreateSourceStore(); m_providerId = providerId; m_historyKind = historyKind; - m_extension = extension; m_label = label; + m_assets = assets; + m_interactions = interactions; + m_serialization = serialization; + m_sources = m_assets.CreateSourceStore(); + m_providerId = providerId; + m_historyKind = historyKind; + m_extension = extension; + m_label = label; } /// @@ -92,15 +93,6 @@ string label /// Read-only presentation state; it contains no live asset or extension objects. /// public Draft GetDraft(Guid assetId) => m_drafts[assetId]; - - /// - /// Marks a document as inspected in this frame so a lost gesture can be completed. - /// - /// - /// Persistent source identity. - /// - public void TouchInspection(Guid assetId) => m_drafts[assetId].lastInspection = m_updateSerial; - /// /// Opens a native asset source in the shared document service without revealing a second Inspector. /// @@ -130,7 +122,6 @@ public Guid Open(AssetPath path) /// A detached editable asset whose changes have not been published. /// public TAsset Read(Guid assetId) => m_sources.Decode(m_drafts[assetId].bytes); - /// /// Updates the open draft through shared History without saving or publishing a runtime asset. /// @@ -140,107 +131,67 @@ public Guid Open(AssetPath path) /// /// Detached edited asset. /// - /// - /// True completes one history transaction; false continues the same active gesture. + /// + /// The current gesture key, or null for an independent discrete edit. /// public void Replace( Guid assetId, TAsset candidate, - bool finishGesture = true - ) => Edit(m_drafts[assetId], candidate, finishGesture); - - /// - /// Finishes the active edit gesture without saving or applying its draft. - /// - /// - /// Open asset identity. - /// - public void Commit(Guid assetId) => Commit(m_drafts[assetId]); + string? mergeKey = null + ) { + Draft draft = m_drafts[assetId]; + if (draft.readOnly) + throw new InvalidOperationException("Copy this installed asset to the project before editing."); + RecordReplace(draft, m_sources.Encode(candidate), mergeKey); + } /// - /// Edits compatible selected drafts as one gesture without saving any source. + /// Records a fully validated selection sample atomically in the shared History. /// /// - /// Detached candidate assets indexed by their open asset identities. + /// Detached candidates keyed by their persistent source identity. /// - /// - /// Whether this sample completes the shared gesture. + /// + /// The owning gesture key, or null for a discrete edit. /// public void ReplaceMany( IReadOnlyDictionary candidates, - bool finishGesture = true + string? mergeKey = null ) { ArgumentNullException.ThrowIfNull(candidates); - if (m_activeGroup.Length != 0 && !m_activeGroup.ToHashSet().SetEquals(candidates.Keys)) - CommitMany(m_activeGroup); var encoded = new Dictionary(); foreach (var pair in candidates) { - Draft draft = m_drafts[pair.Key]; - if (draft.readOnly) + if (m_drafts[pair.Key].readOnly) throw new InvalidOperationException("The selection contains a read-only asset."); encoded.Add(pair.Key, m_sources.Encode(pair.Value)); } - foreach (var pair in encoded) - { - Draft draft = m_drafts[pair.Key]; - if (draft.bytes.AsSpan().SequenceEqual(pair.Value)) - continue; - draft.gestureBefore ??= draft.bytes; - draft.bytes = pair.Value; - Changed(draft); - } - m_activeGroup = candidates.Keys.ToArray(); - if (finishGesture) - CommitMany(candidates.Keys); - } - /// - /// Commits all selected draft samples in a single shared history transaction. - /// - /// - /// Open asset identities belonging to this gesture. - /// - public void CommitMany(IEnumerable assetIds) - { - ArgumentNullException.ThrowIfNull(assetIds); - using var transaction = m_interactions.history.BeginTransaction("Edit " + m_label + " Selection"); - foreach (Guid id in assetIds) - Commit(m_drafts[id]); + using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction("Edit " + m_label + " Selection"); + foreach (var pair in encoded) + RecordReplace(m_drafts[pair.Key], pair.Value, mergeKey); transaction.Commit(); - m_activeGroup = []; } - private void Edit( + private void RecordReplace( Draft draft, - TAsset candidate, - bool finishGesture + byte[] next, + string? mergeKey = null ) { - if (draft.readOnly) - throw new InvalidOperationException("Copy this installed asset to the project before editing."); - byte[] next = m_sources.Encode(candidate); - if (!draft.bytes.AsSpan().SequenceEqual(next)) - { - draft.gestureBefore ??= draft.bytes; - draft.bytes = next; - Changed(draft); - } - if (finishGesture) - Commit(draft); - } - - private void Commit(Draft draft) - { - byte[]? before = draft.gestureBefore; - if (before is null) - return; - draft.gestureBefore = null; - if (before.AsSpan().SequenceEqual(draft.bytes)) + byte[] before = draft.bytes; + if (before.AsSpan().SequenceEqual(next)) return; - var payload = new ChangeData { assetId = draft.id, before = before, after = draft.bytes }; - var change = new EditorHistoryChange(m_historyKind, EditorHistoryPayload.FromBytes(m_serialization.Serialize(payload))); + var payload = new ChangeData + { + assetId = draft.id, + before = before, + after = next + }; + var change = new EditorHistoryChange(m_historyKind, EditorHistoryPayload.FromBytes(m_serialization.Serialize(payload)), mergeKey); + draft.bytes = next; try { + Changed(draft); m_interactions.history.RecordApplied("Edit " + m_label, change); } catch @@ -252,6 +203,43 @@ private void Commit(Draft draft) } } + /// + /// Combines adjacent samples of the same document and gesture without retaining live assets. + /// + /// + /// The preceding owned record. + /// + /// + /// The subsequent owned record. + /// + /// + /// Receives a new independent payload when the records form a contiguous edit. + /// + /// + /// True when both records describe the same continuous value change. + /// + public bool TryMergeHistory( + EditorHistoryChange older, + EditorHistoryChange newer, + out EditorHistoryChange? merged + ) { + merged = null; + if (older.kind != m_historyKind || newer.kind != m_historyKind || older.mergeKey is null || older.mergeKey != newer.mergeKey) + return false; + ChangeData first = m_serialization.Deserialize(older.payload.ReadBytes()); + ChangeData last = m_serialization.Deserialize(newer.payload.ReadBytes()); + if (first.assetId != last.assetId || !first.after.AsSpan().SequenceEqual(last.before)) + return false; + var combined = new ChangeData + { + assetId = first.assetId, + before = first.before, + after = last.after + }; + merged = new EditorHistoryChange(m_historyKind, EditorHistoryPayload.FromBytes(m_serialization.Serialize(combined)), older.mergeKey); + return true; + } + /// /// Validates a feature's neutral History record without changing the draft. /// @@ -265,7 +253,9 @@ public void ValidateHistory( EditorHistoryChange change, EditorHistoryDirection direction ) { - _ = ResolveHistory(change, direction); + ChangeData data = m_serialization.Deserialize(change.payload.ReadBytes()); + if (!m_drafts.ContainsKey(data.assetId)) + throw new InvalidOperationException("Reopen the asset document to use its history."); } /// @@ -293,8 +283,6 @@ EditorHistoryDirection direction ChangeData data = m_serialization.Deserialize(change.payload.ReadBytes()); if (!m_drafts.TryGetValue(data.assetId, out Draft? draft)) throw new InvalidOperationException("Reopen the asset document to use its history."); - if (draft.gestureBefore is not null) - throw new InvalidOperationException("Finish the active asset gesture first."); byte[] expected = direction == EditorHistoryDirection.Undo ? data.after : data.before; if (!draft.bytes.AsSpan().SequenceEqual(expected)) throw new InvalidOperationException("The asset draft changed outside this history entry."); @@ -311,8 +299,11 @@ private void Changed(Draft draft) DeleteRecovery(draft.id); return; } - AtomicFile.WriteAllBytes(RecoveryPath(draft.id), m_serialization.Serialize(new Recovery - { assetId = draft.id, hash = draft.hash, baseline = draft.baseline, bytes = draft.bytes })); + + AtomicFile.WriteAllBytes( + RecoveryPath(draft.id), + m_serialization.Serialize(new Recovery { assetId = draft.id, hash = draft.hash, baseline = draft.baseline, bytes = draft.bytes }) + ); if (draft.error.StartsWith("Recovery write failed:", StringComparison.Ordinal)) draft.error = ""; } @@ -326,7 +317,6 @@ private bool Save(Draft draft) { try { - Commit(draft); RefreshPath(draft); draft.hash = m_sources.Save(draft.path, draft.bytes, draft.hash); draft.baseline = draft.bytes; @@ -346,13 +336,9 @@ private bool Revert(Draft draft) { try { - Commit(draft); RefreshPath(draft); AssetSourceSnapshot source = m_sources.Read(draft.path); - byte[] before = draft.bytes; - draft.gestureBefore = before; - draft.bytes = source.bytes; - Commit(draft); + RecordReplace(draft, source.bytes); draft.hash = source.contentHash; draft.baseline = draft.bytes; draft.readOnly = source.isReadOnly; @@ -382,8 +368,7 @@ private void Open(EditorDocumentContext document) AssetPath path = AssetPath.Parse(document.assetPath); if (m_assets.TryGetInfo(document.assetId, out AssetInfo? info) && info is not null) path = info.assetPath; - Recovery? recovery = File.Exists(RecoveryPath(document.assetId)) - ? m_serialization.Deserialize(File.ReadAllBytes(RecoveryPath(document.assetId))) : null; + Recovery? recovery = File.Exists(RecoveryPath(document.assetId)) ? m_serialization.Deserialize(File.ReadAllBytes(RecoveryPath(document.assetId))) : null; if (recovery is not null && recovery.assetId != document.assetId) throw new InvalidDataException("Asset recovery identity does not match its document."); AssetSourceSnapshot? source = null; @@ -391,9 +376,19 @@ private void Open(EditorDocumentContext document) { source = m_sources.Read(path); } - catch (Exception error) when (recovery is not null && Recoverable(error)) { } - var draft = new Draft(document.assetId, document.documentId, path, recovery?.bytes ?? source!.bytes, - recovery?.baseline ?? source!.bytes, recovery?.hash ?? source!.contentHash, source?.isReadOnly ?? true); + catch (Exception error) when (recovery is not null && Recoverable(error)) + { + } + + var draft = new Draft( + document.assetId, + document.documentId, + path, + recovery?.bytes ?? source!.bytes, + recovery?.baseline ?? source!.bytes, + recovery?.hash ?? source!.contentHash, + source?.isReadOnly ?? true + ); if (recovery is not null && source?.contentHash != recovery.hash) draft.error = "Source changed externally. Recovery retained; resolve or Revert explicitly."; m_drafts.Add(draft.id, draft); @@ -412,16 +407,10 @@ public void Start() } /// - /// Completes abandoned gestures, imports explicit saves and reconciles source changes. + /// Imports explicit saves and reconciles source changes without owning UI gesture lifetimes. /// public void Update() { - m_updateSerial++; - if (m_activeGroup.Any(id => m_drafts[id].lastInspection >= 0 && m_drafts[id].lastInspection < m_updateSerial - 1)) - CommitMany(m_activeGroup); - foreach (Draft draft in m_drafts.Values) - if (draft.gestureBefore is not null && draft.lastInspection >= 0 && draft.lastInspection < m_updateSerial - 1) - Commit(draft); foreach (AssetPath path in m_imports.ToArray()) { m_imports.Remove(path); @@ -429,8 +418,14 @@ public void Update() { if (!m_assets.Import(path)) { - string diagnostics = m_assets.TryGetInfo(path, out AssetInfo? info) && info is not null - ? string.Join("\n", info.diagnostics) : "The asset importer did not publish this asset."; + string diagnostics = m_assets.TryGetInfo( + path, + out AssetInfo? info + ) + && info is not null ? string.Join( + "\n", + info.diagnostics + ) : "The asset importer did not publish this asset."; foreach (Draft draft in m_drafts.Values.Where(value => value.path == path)) draft.error = "Saved; import failed: " + diagnostics; } @@ -441,6 +436,7 @@ public void Update() draft.error = "Saved; import failed: " + error.Message; } } + if (m_sourceRevision != m_assets.revision) { foreach (Draft draft in m_drafts.Values) @@ -458,11 +454,12 @@ private void SynchronizeSource(Draft draft) draft.readOnly = source.isReadOnly; if (draft.hash == source.contentHash) return; - if (draft.isDirty || draft.gestureBefore is not null) + if (draft.isDirty) { draft.error = "Source changed externally. Your draft is retained; Revert reloads it without overwriting the external edit."; return; } + draft.bytes = source.bytes; draft.baseline = source.bytes; draft.hash = source.contentHash; @@ -480,13 +477,12 @@ private void SynchronizeSource(Draft draft) /// public void Dispose() { - CommitMany(m_activeGroup); foreach (Draft draft in m_drafts.Values) { - Commit(draft); if (draft.isDirty) Changed(draft); } + m_provider?.Dispose(); m_provider = null; } @@ -498,15 +494,16 @@ private void DeleteRecovery(Guid id) if (File.Exists(path)) File.Delete(path); } + private static bool Recoverable(Exception error) => error is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException or FormatException && Inno.Core.Execution.RetirementPendingException.Find(error) is null; - private sealed class Provider(AssetDraftDocuments owner) : EditorDocumentProvider { /// /// Gets the stable identity used to reference this value across subsystem boundaries. /// -public override string id => owner.m_providerId; + public override string id => owner.m_providerId; + /// /// Checks whether this document handler supports the selected asset. /// @@ -516,14 +513,14 @@ private sealed class Provider(AssetDraftDocuments owner) : EditorDocumen /// /// when the documented condition is satisfied; otherwise, . /// -public override bool CanOpen(string assetPath) => assetPath.EndsWith(owner.m_extension, StringComparison.OrdinalIgnoreCase); + public override bool CanOpen(string assetPath) => assetPath.EndsWith(owner.m_extension, StringComparison.OrdinalIgnoreCase); /// /// Opens the requested resource and establishes its active lifetime. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -public override void Open(EditorDocumentContext context) => owner.Open(context); + public override void Open(EditorDocumentContext context) => owner.Open(context); /// /// Persists the supplied value through the configured storage contract. /// @@ -533,7 +530,7 @@ private sealed class Provider(AssetDraftDocuments owner) : EditorDocumen /// /// when the documented condition is satisfied; otherwise, . /// -public override bool Save(EditorDocumentContext context) => owner.Save(owner.m_drafts[context.assetId]); + public override bool Save(EditorDocumentContext context) => owner.Save(owner.m_drafts[context.assetId]); /// /// Restores the draft from the latest imported asset state. /// @@ -543,16 +540,15 @@ private sealed class Provider(AssetDraftDocuments owner) : EditorDocumen /// /// when the documented condition is satisfied; otherwise, . /// -public override bool Revert(EditorDocumentContext context) => owner.Revert(owner.m_drafts[context.assetId]); + public override bool Revert(EditorDocumentContext context) => owner.Revert(owner.m_drafts[context.assetId]); /// /// Closes the active resource and releases its operation-scoped state. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -public override void Close(EditorDocumentContext context) + public override void Close(EditorDocumentContext context) { - owner.CommitMany(owner.m_activeGroup); owner.DeleteRecovery(context.assetId); owner.m_drafts.Remove(context.assetId); } @@ -566,8 +562,6 @@ public sealed class Draft internal byte[] bytes; internal byte[] baseline; internal string hash; - internal byte[]? gestureBefore; - internal long lastInspection = -1; internal Draft( Guid id, Guid documentId, @@ -585,6 +579,7 @@ bool readOnly this.hash = hash; this.readOnly = readOnly; } + /// /// Gets the persistent source identity. /// @@ -613,16 +608,28 @@ bool readOnly private sealed class Recovery : ISerializable { - [SerializableProperty] internal Guid assetId { get; set; } - [SerializableProperty] internal string hash { get; set; } = ""; - [SerializableProperty] internal byte[] baseline { get; set; } = []; - [SerializableProperty] internal byte[] bytes { get; set; } = []; + [SerializableProperty] + internal Guid assetId { get; set; } + + [SerializableProperty] + internal string hash { get; set; } = ""; + + [SerializableProperty] + internal byte[] baseline { get; set; } = []; + + [SerializableProperty] + internal byte[] bytes { get; set; } = []; } private sealed class ChangeData : ISerializable { - [SerializableProperty] internal Guid assetId { get; set; } - [SerializableProperty] internal byte[] before { get; set; } = []; - [SerializableProperty] internal byte[] after { get; set; } = []; + [SerializableProperty] + internal Guid assetId { get; set; } + + [SerializableProperty] + internal byte[] before { get; set; } = []; + + [SerializableProperty] + internal byte[] after { get; set; } = []; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetCreationRegistry.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetCreationRegistry.cs similarity index 53% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetCreationRegistry.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetCreationRegistry.cs index 22337749c..a6f4895c8 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetCreationRegistry.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetCreationRegistry.cs @@ -6,12 +6,10 @@ using Inno.Editor.Assets; using Inno.Extensibility.Types; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal sealed class AssetCreationRegistry : TypeRegistry { - internal AssetCreationRegistry(TypeCatalog types) - : base(types) + internal AssetCreationRegistry(TypeCatalog types) : base(types) { } @@ -21,7 +19,6 @@ internal bool TryGet( string id, out Registration? registration ) => current.byId.TryGetValue(id, out registration); - /// /// Builds a validated result from the current immutable input snapshot. /// @@ -31,52 +28,44 @@ out Registration? registration /// /// The validated snapshot that represents the completed operation. /// -protected override Snapshot Build(TypeCacheSnapshot types) + protected override Snapshot Build(TypeCacheSnapshot types) { var registrations = new List(); var byId = new Dictionary(StringComparer.Ordinal); var byMenuPath = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (Type type in types.GetSubTypesOf() - .Select(typeRef => typeRef.Resolve(types)) - .Where(static type => !type.IsAbstract) - .OrderBy(static type => type.FullName, StringComparer.Ordinal)) + foreach (Type type in types.GetSubTypesOf().Select(typeRef => typeRef.Resolve(types)).Where(static type => !type.IsAbstract).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + )) { - AssetCreationMenuAttribute declaration = type.GetCustomAttribute(false) - ?? throw new InvalidOperationException( - $"Asset creation template '{type.FullName}' requires " + - $"'{typeof(AssetCreationMenuAttribute).FullName}' source metadata."); + AssetCreationMenuAttribute declaration = type.GetCustomAttribute(false) ?? throw new InvalidOperationException($"Asset creation template '{type.FullName}' requires " + $"'{typeof(AssetCreationMenuAttribute).FullName}' source metadata."); AssetCreationTemplate template = CreateExtension(type); - if (!typeof(AssetObject).IsAssignableFrom(template.assetType) - || template.assetType.IsAbstract) + if (!typeof(AssetObject).IsAssignableFrom(template.assetType) || template.assetType.IsAbstract) { - throw new InvalidOperationException( - $"Asset creation template '{type.FullName}' declares invalid asset type " + - $"'{template.assetType.FullName}'."); + throw new InvalidOperationException($"Asset creation template '{type.FullName}' declares invalid asset type " + $"'{template.assetType.FullName}'."); } var registration = new Registration(type, template, declaration); if (!byId.TryAdd(declaration.id, registration)) { - throw new InvalidOperationException( - $"Asset creation template id '{declaration.id}' is registered by both " + - $"'{byId[declaration.id].implementationType.FullName}' and '{type.FullName}'."); + throw new InvalidOperationException($"Asset creation template id '{declaration.id}' is registered by both " + $"'{byId[declaration.id].implementationType.FullName}' and '{type.FullName}'."); } + if (!byMenuPath.TryAdd(declaration.menuPath, registration)) { - throw new InvalidOperationException( - $"Asset creation menu path '{declaration.menuPath}' is registered by both " + - $"'{byMenuPath[declaration.menuPath].implementationType.FullName}' and '{type.FullName}'."); + throw new InvalidOperationException($"Asset creation menu path '{declaration.menuPath}' is registered by both " + $"'{byMenuPath[declaration.menuPath].implementationType.FullName}' and '{type.FullName}'."); } + registrations.Add(registration); } - Registration[] ordered = registrations - .OrderBy(static value => value.declaration.groupOrder) - .ThenBy(static value => value.declaration.menuPath, StringComparer.Ordinal) - .ThenBy(static value => value.declaration.itemOrder) - .ThenBy(static value => value.declaration.id, StringComparer.Ordinal) - .ToArray(); + Registration[] ordered = registrations.OrderBy(static value => value.declaration.groupOrder).ThenBy( + static value => value.declaration.menuPath, + StringComparer.Ordinal + ).ThenBy(static value => value.declaration.itemOrder).ThenBy( + static value => value.declaration.id, + StringComparer.Ordinal + ).ToArray(); return new Snapshot(ordered, byId); } @@ -86,15 +75,12 @@ protected override Snapshot Build(TypeCacheSnapshot types) /// /// The immutable state snapshot consumed by this operation. /// -protected override void DisposeSnapshot(Snapshot snapshot) - => DisposeExtensions(snapshot.registrations.Select(static value => value.template)); - + protected override void DisposeSnapshot(Snapshot snapshot) => DisposeExtensions(snapshot.registrations.Select(static value => value.template)); internal sealed record Registration( Type implementationType, AssetCreationTemplate template, AssetCreationMenuAttribute declaration ); - internal sealed record Snapshot( Registration[] registrations, IReadOnlyDictionary byId diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs similarity index 54% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs index 44513cce6..7775c5406 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.Creation.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Creation.cs @@ -6,25 +6,52 @@ using Inno.Core.IO; using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Handles asset creation actions in the Editor file browser. /// -public sealed partial class AssetEditorModule +public sealed partial class AssetEditingModule { /// - /// Selects a newly created source and starts its shared inline rename interaction. + /// Creates a uniquely named directory and records its removal and restoration in shared History. /// - /// - /// The newly indexed writable source. + /// + /// An existing writable source directory. /// - public void BeginCreatedSourceRename(AssetFileEntry entry) + /// + /// The indexed identity of the newly created directory. + /// + /// + /// The source is read-only, unavailable, or cannot retain the History entry. + /// + public AssetFileEntry CreateDirectory(AssetPath parent) { - ArgumentNullException.ThrowIfNull(entry); - EditorInteraction interaction = m_interactions.For(FileBrowserInteractionIds.C_AREA, entry); - _ = interaction.Select(); - _ = interaction.Execute(FileBrowserInteractionIds.C_RENAME); + AssetSourceMount mount = m_pipeline.sourceMounts.SingleOrDefault(source => source.id == parent.source) ?? throw new InvalidOperationException("The asset source mount is unavailable."); + if (mount.isReadOnly) + throw new InvalidOperationException("Installed asset sources are read-only."); + string prefix = parent.localPath.Length == 0 ? string.Empty : parent.localPath + "/"; + int suffix = 0; + AssetPath candidate; + do + { + string name = suffix == 0 ? "New Folder" : $"New Folder {suffix}"; + candidate = new AssetPath(parent.source, prefix + name); + suffix++; + } + while (m_pipeline.TryGetFileSystemEntry(candidate, out _)); + m_pipeline.CreateDirectory(candidate); + var data = new AssetHistoryData(AssetHistoryOperationKind.CreateDirectory, candidate.ToString(), string.Empty, true, []); + using var change = new EditorHistoryChange(AssetHistoryKinds.SourceOperation, EditorHistoryPayload.FromBytes(data.Encode())); + try + { + m_interactions.history.RecordApplied("Create Folder", change); + } + catch (Exception failure) + { + RollbackAndRethrow(failure, () => m_pipeline.Delete(candidate)); + } + + return m_pipeline.TryGetFileSystemEntry(candidate, out AssetFileEntry entry) ? entry : throw new InvalidOperationException("The created directory was not indexed."); } /// @@ -49,8 +76,7 @@ public AssetFileEntry CreateSource( AssetPath path, ReadOnlySpan bytes ) { - AssetSourceMount mount = m_pipeline.sourceMounts.SingleOrDefault(source => source.id == path.source) - ?? throw new InvalidOperationException("The asset source mount is unavailable."); + AssetSourceMount mount = m_pipeline.sourceMounts.SingleOrDefault(source => source.id == path.source) ?? throw new InvalidOperationException("The asset source mount is unavailable."); if (mount.isReadOnly) throw new InvalidOperationException("Installed asset sources are read-only."); string destination = mount.Resolve(path.localPath); @@ -80,7 +106,7 @@ ReadOnlySpan bytes m_pipeline.Delete(path); throw; } - return m_pipeline.TryGetFileSystemEntry(path, out AssetFileEntry entry) ? entry - : throw new InvalidOperationException("The created asset was not indexed."); + + return m_pipeline.TryGetFileSystemEntry(path, out AssetFileEntry entry) ? entry : throw new InvalidOperationException("The created asset was not indexed."); } } diff --git a/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs new file mode 100644 index 000000000..7b440e600 --- /dev/null +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.Selection.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Assets.Pipeline; +using Inno.Editor.Core; +using Inno.Editor.Interactions; +using Inno.Scripting.Api; + +namespace Inno.Editor.Assets; +/// +/// Applies validated selection operations through the authoritative asset and History owners. +/// +public sealed partial class AssetEditingModule +{ + /// + /// Checks the entire source selection before a batch mutation, including extension validation and read-only ownership. + /// + /// + /// The immediate Editor context. + /// + /// + /// Fresh source entries in selection order. + /// + /// + /// The first rejection, or a valid result when every source can be deleted. + /// + [ScriptingApiIgnore] + public AssetOperationValidation ValidateDeleteSelection( + EditorContext editor, + IReadOnlyList targets + ) { + ArgumentNullException.ThrowIfNull(editor); + ArgumentNullException.ThrowIfNull(targets); + if (targets.Count == 0) + return AssetOperationValidation.Invalid("The selection contains unavailable entries."); + foreach (AssetFileEntry target in targets) + { + if (target.isReadOnly + || target.identity.runtimeIdentity is null + || !TryCreateContext( + editor, + target.assetPath.ToString(), + out AssetEditorContext? context + ) + || context is null) + return AssetOperationValidation.Invalid("Every selected source must be available and writable."); + AssetOperationValidation validation = m_editors.Resolve(context.assetType).ValidateDelete(context); + if (!validation.isValid) + return validation; + } + + return AssetOperationValidation.valid; + } + + /// + /// Deletes minimal selected source roots in one History transaction, restoring earlier deletions if a later one fails. + /// + /// + /// The immediate Editor context. + /// + /// + /// Fresh selected source entries; nested entries are covered by their selected directory. + /// + /// + /// Validation or a mutation fails; rollback failures remain explicit. + /// + [ScriptingApiIgnore] + public void DeleteSelection( + EditorContext editor, + IReadOnlyList targets + ) { + AssetOperationValidation validation = ValidateDeleteSelection(editor, targets); + if (!validation.isValid) + throw new InvalidOperationException(validation.message); + string[] roots = SelectedRoots(targets); + using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction("Delete Assets"); + foreach (string path in roots) + { + if (!TryCreateContext(editor, path, out AssetEditorContext? context) || context is null || !DeleteWithHistory(context, m_interactions.history)) + throw new InvalidOperationException($"Selected source '{path}' could not be deleted."); + } + + transaction.Commit(); + } + + /// + /// Validates every minimal selected source and rejects destination collisions before a batch move. + /// + /// + /// Live source entries in selection order. + /// + /// + /// The writable project destination. + /// + /// + /// True when every source can move to a distinct destination. + /// + [ScriptingApiIgnore] + public bool CanMoveSelection( + IReadOnlyList targets, + string targetDirectory + ) { + ArgumentNullException.ThrowIfNull(targets); + if (targets.Count == 0 || targets.Any(value => value.isReadOnly || value.identity.runtimeIdentity is null)) + return false; + var destinations = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (string source in SelectedRoots(targets)) + if (!TryPrepareMove(source, targetDirectory, true, out _, out _, out string destination) || !destinations.Add(destination)) + return false; + return true; + } + + /// + /// Moves selected source roots in one transaction; a later failure restores earlier moves. + /// + /// + /// Live entries, including descendants covered by a selected directory. + /// + /// + /// The writable project destination. + /// + /// + /// Any source or destination rejects the operation. + /// + [ScriptingApiIgnore] + public void MoveSelection( + IReadOnlyList targets, + string targetDirectory + ) { + if (!CanMoveSelection(targets, targetDirectory)) + throw new InvalidOperationException("The selected assets cannot move to this directory."); + string[] roots = SelectedRoots(targets); + using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction("Move Assets"); + foreach (string source in roots) + _ = MoveToDirectoryWithHistory(source, targetDirectory, m_interactions.history); + transaction.Commit(); + } + + private static string[] SelectedRoots(IReadOnlyList targets) + { + string[] paths = targets.Select(value => value.assetPath.ToString()).Distinct(StringComparer.Ordinal).ToArray(); + return paths.Where(path => !paths.Any(parent => parent != path && path.StartsWith(parent + "/", StringComparison.Ordinal))).ToArray(); + } +} diff --git a/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs new file mode 100644 index 000000000..6f10882ca --- /dev/null +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditingModule.cs @@ -0,0 +1,651 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.ExceptionServices; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Logging; +using Inno.Extensibility.Types; +using Inno.Editor.Core; +using Inno.Editor.Assets; +using Inno.Editor.Interactions; +using Inno.Plugins.Authoring; + +namespace Inno.Editor.Assets; +/// +/// Owns source mutations, asset extension dispatch and neutral shared-history records independently of presentation. +/// +[EditorModule("assets.editing", order: 90)] +public sealed partial class AssetEditingModule : EditorModule +{ + private readonly AssetPipeline m_pipeline; + private readonly AssetEditorRegistry m_editors; + private readonly AssetCreationRegistry m_creations; + private readonly EditorInteractions m_interactions; + private readonly Logger m_log; + private readonly PluginEnvironment m_plugins; + private EditorContext? m_context; + /// + /// Creates the asset editing service for one authoring generation. + /// + /// + /// The active editor interaction entry point. + /// + /// + /// The isolated authoring asset pipeline used by browser operations. + /// + /// + /// The active Plugin catalog that authoritatively identifies Plugin-owned source mounts. + /// + /// + /// The active type catalog used to build extension registry snapshots. + /// + /// + /// The application log router used for Asset Browser operation diagnostics. + /// + /// + /// Thrown when any required service is . + /// + public AssetEditingModule( + EditorInteractions interactions, + AssetPipeline pipeline, + PluginEnvironment plugins, + TypeCatalog types, + LogRouter logs + ) { + m_interactions = interactions ?? throw new ArgumentNullException(nameof(interactions)); + m_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); + m_plugins = plugins ?? throw new ArgumentNullException(nameof(plugins)); + ArgumentNullException.ThrowIfNull(types); + ArgumentNullException.ThrowIfNull(logs); + m_log = logs.CreateLogger(); + m_editors = new AssetEditorRegistry(types); + m_creations = new AssetCreationRegistry(types); + } + + /// + /// Gets the editor interaction owner used by source operations. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public EditorInteractions interactions => m_interactions; + + /// + /// Gets the authoritative authoring pipeline; mutations must use its existing ownership protocol. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetPipeline pipeline => m_pipeline; + /// + /// Gets the current immutable creation menu declarations without retaining template instances. + /// + public IReadOnlyList creationTemplates => m_creations.templates.Select(static registration => registration.declaration).ToArray(); + + /// + /// Checks that a creation template and writable destination are available. + /// + /// + /// The source-relative writable destination directory. + /// + /// + /// The stable identity of a discovered creation template. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool CanCreateAsset( + string directory, + string templateId + ) { + if (!m_pipeline.isInitialized || !m_creations.TryGet(templateId, out _)) + return false; + AssetPath path = AssetPath.Parse(directory); + AssetSourceMount? mount = m_pipeline.sourceMounts.FirstOrDefault(source => source.id == path.source); + return mount is { isReadOnly: false } && (string.IsNullOrEmpty(path.localPath) || m_pipeline.TryGetFileSystemEntry(path, out AssetFileEntry entry) && entry.isDirectory); + } + + /// + /// Creates and imports a source using a discovered template and records the committed change. + /// + /// + /// The source-relative writable destination directory. + /// + /// + /// The stable identity of a discovered creation template. + /// + /// + /// The committed source identity, or a validation result describing why the operation is unavailable. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetFileEntry CreateAsset( + string directory, + string templateId + ) { + if (!m_creations.TryGet(templateId, out AssetCreationRegistry.Registration? registration) || registration is null) + { + throw new InvalidOperationException($"Asset creation template '{templateId}' is unavailable."); + } + + if (!CanCreateAsset(directory, templateId)) + throw new InvalidOperationException("The selected Asset directory is not writable."); + AssetCreationMenuAttribute declaration = registration.declaration; + AssetPath parent = AssetPath.Parse(directory); + AssetSourceMount mount = m_pipeline.sourceMounts.Single(source => source.id == parent.source); + string prefix = parent.localPath.TrimEnd('/'); + if (prefix.Length != 0) + prefix += "/"; + int suffix = 1; + AssetPath path; + do + { + string number = suffix == 1 ? string.Empty : " " + suffix; + path = new AssetPath(parent.source, prefix + declaration.defaultName + number + declaration.extension); + suffix++; + } + while (File.Exists(mount.Resolve(path.localPath)) || File.Exists(mount.Resolve(path.localPath) + ".imeta")); + AssetObject candidate = registration.template.Create() ?? throw new InvalidOperationException($"Asset creation template '{templateId}' returned no asset."); + if (candidate.GetType() != registration.template.assetType) + { + throw new InvalidOperationException($"Asset creation template '{templateId}' returned '{candidate.GetType().FullName}' " + $"instead of '{registration.template.assetType.FullName}'."); + } + + AssetSourceStore sources = m_pipeline.CreateSourceStore(); + byte[] source = registration.template.Encode( + new AssetCreationContext(sources), + candidate + ) ?? throw new InvalidOperationException($"Asset creation template '{templateId}' returned no source data."); + return CreateSource(path, source); + } + + /// + /// Determines whether the active Plugin catalog owns a source mount. + /// + /// + /// The source mount identity to query. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool IsPluginSource(AssetSourceId source) => m_plugins.TryGet(source, out _); + /// + /// Determines whether an indexed entry is the root of an installed Plugin. + /// + /// + /// The currently indexed source identity. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool IsPluginRoot(AssetFileEntry entry) + { + ArgumentNullException.ThrowIfNull(entry); + return string.IsNullOrEmpty(entry.assetPath.localPath) && IsPluginSource(entry.source); + } + + /// + /// Captures the currently indexed source and extension metadata for one immediate operation. + /// + /// + /// The current editor context used only during this invocation. + /// + /// + /// The isolated source-relative path to resolve. + /// + /// + /// Receives the current source snapshot, or null when the source is unavailable. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool TryCreateContext( + EditorContext editor, + string relativePath, + out AssetEditorContext? context + ) { + if (!m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry)) + { + context = null; + return false; + } + + _ = m_pipeline.TryGetInfo(AssetPath.Parse(relativePath), out AssetInfo? info); + _ = m_pipeline.TryGetAssetType(AssetPath.Parse(relativePath), out Type? assetType); + context = new AssetEditorContext(editor, m_interactions, entry.assetPath.ToString(), entry.name, entry.isDirectory, info, assetType, () => CreateDefaultDragData(entry)); + return true; + } + + /// + /// Opens a source using its current asset editor extension and reports rejected or failed opens. + /// + /// + /// The immutable metadata captured for this immediate source operation. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool Open(AssetEditorContext context) + { + AssetEditor editor = m_editors.Resolve(context.assetType); + if (!editor.CanOpen(context)) + return false; + try + { + editor.Open(context); + return true; + } + catch (Exception exception) + { + m_log.Write(LogLevel.Error, "Failed to open asset '{0}': {1}", [context.relativePath, exception]); + return false; + } + } + + /// + /// Validates a requested name without modifying the source. + /// + /// + /// The immutable metadata captured for this immediate source operation. + /// + /// + /// The requested entry name without its protected file extension. + /// + /// + /// The committed source identity, or a validation result describing why the operation is unavailable. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public EditorValidationResult ValidateRename( + AssetEditorContext context, + string newName + ) { + AssetEditor editor = m_editors.Resolve(context.assetType); + return ToEditorValidation(ValidateRename(editor, context, Normalize(context.relativePath), newName, validateEditor: true, out _)); + } + + /// + /// Renames a source atomically and records its reversible history payload. + /// + /// + /// The immutable metadata captured for this immediate source operation. + /// + /// + /// The requested entry name without its protected file extension. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public void Rename( + AssetEditorContext context, + string newName + ) { + AssetEditor editor = m_editors.Resolve(context.assetType); + AssetOperationValidation validation = ValidateRename(editor, context, Normalize(context.relativePath), newName, validateEditor: false, out string targetPath); + if (!validation.isValid) + throw new InvalidOperationException(validation.message); + string sourcePath = Normalize(context.relativePath); + EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); + if (!result.succeeded) + throw new InvalidOperationException(result.message); + var data = new AssetHistoryData(AssetHistoryOperationKind.Move, sourcePath, targetPath, isDirectory: context.isDirectory, archive: []); + try + { + m_interactions.history.RecordApplied("Rename Asset", new EditorHistoryChange(AssetHistoryKinds.SourceOperation, EditorHistoryPayload.FromBytes(data.Encode()))); + } + catch (Exception exception) + { + RollbackAndRethrow(exception, () => RequireMove(editor, context, targetPath, sourcePath)); + } + } + + /// + /// Checks whether a source can move into the destination without a cycle or name collision. + /// + /// + /// The current isolated source-relative path. + /// + /// + /// The isolated destination directory. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool CanMoveToDirectory( + string sourcePath, + string targetDirectory + ) => TryPrepareMove(sourcePath, targetDirectory, validateEditor: true, out _, out _, out _); + /// + /// Moves a source and records the committed mutation in the supplied history. + /// + /// + /// The current isolated source-relative path. + /// + /// + /// The isolated destination directory. + /// + /// + /// The authoritative history that receives the committed mutation. + /// + /// + /// The committed source identity, or a validation result describing why the operation is unavailable. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetFileEntry MoveToDirectoryWithHistory( + string sourcePath, + string targetDirectory, + IEditorHistory history + ) { + ArgumentNullException.ThrowIfNull(history); + if (!TryPrepareMove( + sourcePath, + targetDirectory, + validateEditor: true, + out AssetEditorContext? context, + out AssetEditor? editor, + out string targetPath + ) + || context is null + || editor is null) + { + throw new InvalidOperationException($"Asset '{sourcePath}' cannot be moved to '{targetDirectory}'."); + } + + string normalizedSource = Normalize(sourcePath); + EditorHistoryResult result = MoveAsset(editor, context, normalizedSource, targetPath); + if (!result.succeeded) + throw new InvalidOperationException(result.message); + var data = new AssetHistoryData(AssetHistoryOperationKind.Move, normalizedSource, targetPath, context.isDirectory, archive: []); + try + { + history.RecordApplied("Move Asset", new EditorHistoryChange(AssetHistoryKinds.SourceOperation, EditorHistoryPayload.FromBytes(data.Encode()))); + } + catch (Exception exception) + { + RollbackAndRethrow(exception, () => RequireMove(editor, context, targetPath, normalizedSource)); + } + + return m_pipeline.TryGetFileSystemEntry( + AssetPath.Parse(targetPath), + out AssetFileEntry moved + ) ? moved : throw new InvalidOperationException($"Moved asset '{targetPath}' is unavailable after the transaction."); + } + + internal bool Delete(AssetEditorContext context) + { + AssetEditor editor = m_editors.Resolve(context.assetType); + AssetOperationValidation validation = editor.ValidateDelete(context); + if (!validation.isValid) + { + m_log.Write(LogLevel.Warn, "Asset delete was rejected for '{0}': {1}", [context.relativePath, validation.message]); + return false; + } + + string path = context.relativePath; + m_pipeline.Delete(AssetPath.Parse(path)); + try + { + editor.OnDeleted(context); + } + catch (Exception exception) + { + m_log.Write(LogLevel.Error, "Asset editor delete hook failed for '{0}': {1}", [path, exception]); + } + + return true; + } + + /// + /// Deletes a source with a recoverable archive and records the committed mutation. + /// + /// + /// The captured source metadata to validate and delete. + /// + /// + /// The authoritative History receiving this mutation. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool DeleteWithHistory( + AssetEditorContext asset, + IEditorHistory history + ) { + ArgumentNullException.ThrowIfNull(history); + ArgumentNullException.ThrowIfNull(asset); + byte[] archive = AssetSourceArchive.Capture(m_pipeline, asset.relativePath, out bool isDirectory); + if (!Delete(asset)) + return false; + var data = new AssetHistoryData(AssetHistoryOperationKind.Delete, Normalize(asset.relativePath), string.Empty, isDirectory, archive); + try + { + history.RecordApplied("Delete Asset", new EditorHistoryChange(AssetHistoryKinds.SourceOperation, EditorHistoryPayload.FromBytes(data.Encode()))); + } + catch (Exception exception) + { + RollbackAndRethrow(exception, () => AssetSourceArchive.Restore(m_pipeline, Normalize(asset.relativePath), isDirectory, archive)); + } + + return true; + } + + internal void MoveFromHistory( + string sourcePath, + string targetPath + ) { + EditorContext editorContext = m_context ?? throw new InvalidOperationException("Asset editor module is not attached."); + if (!TryCreateContext(editorContext, sourcePath, out AssetEditorContext? context) || context is null) + throw new InvalidOperationException($"Asset '{sourcePath}' is no longer available."); + AssetEditor editor = m_editors.Resolve(context.assetType); + EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); + if (!result.succeeded) + throw new InvalidOperationException(result.message); + } + + internal void DeleteFromHistory(string relativePath) + { + EditorContext editorContext = m_context ?? throw new InvalidOperationException("Asset editor module is not attached."); + if (!TryCreateContext(editorContext, relativePath, out AssetEditorContext? context) || context is null) + throw new InvalidOperationException($"Asset '{relativePath}' is no longer available."); + if (!Delete(context)) + throw new InvalidOperationException($"Asset '{relativePath}' could not be deleted."); + } + + /// + /// Selects the current source identity without retaining an indexed entry across generations. + /// + /// + /// The isolated source-relative path to resolve. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public void SelectPath(string relativePath) + { + object? target = m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry) ? entry : null; + m_interactions.SetSelection(target); + } + + /// + /// Creates identity-based drag data when the current extension permits dragging. + /// + /// + /// The immutable metadata captured for this immediate source operation. + /// + /// + /// Receives identity-based drag data, or null when dragging is unavailable. + /// + /// + /// True when the request is accepted; otherwise false. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public bool TryCreateDragData( + AssetEditorContext context, + out EditorDragData? data + ) { + AssetEditor editor = m_editors.Resolve(context.assetType); + if (!editor.CanStartDrag(context)) + { + data = null; + return false; + } + + data = editor.CreateDragData(context); + return data is not null; + } + + /// + /// Initializes this feature when its owning runtime becomes active. + /// + /// + /// The context that supplies state and services for this operation. + /// + protected override void OnStart(EditorContext context) + { + m_context = context ?? throw new ArgumentNullException(nameof(context)); + } + + /// + /// Stops this feature before its owning runtime releases the active generation. + /// + /// + /// The context that supplies state and services for this operation. + /// + protected override void OnStop(EditorContext context) + { + m_context = null; + } + + /// + /// Releases resources retained by this feature after it has stopped. + /// + protected override void OnDispose() + { + m_creations.Dispose(); + m_editors.Dispose(); + } + + private AssetOperationValidation ValidateRename( + AssetEditor editor, + AssetEditorContext context, + string sourcePath, + string newName, + bool validateEditor, + out string targetPath + ) { + newName = newName.Trim(); + targetPath = string.Empty; + if (string.IsNullOrEmpty(newName)) + return AssetOperationValidation.Invalid("An asset name is required."); + if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || newName.Contains('/') || newName.Contains('\\')) + { + return AssetOperationValidation.Invalid("Asset names cannot contain path separators or invalid file-name characters."); + } + + string renamedEntry = context.isDirectory ? newName : newName + Path.GetExtension(sourcePath); + string parent = Normalize(Path.GetDirectoryName(sourcePath)); + targetPath = string.IsNullOrEmpty(parent) ? renamedEntry : $"{parent}/{renamedEntry}"; + if (!string.Equals(sourcePath, targetPath, StringComparison.OrdinalIgnoreCase) && m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) + { + return AssetOperationValidation.Invalid($"Asset '{targetPath}' already exists."); + } + + return validateEditor ? editor.ValidateRename(context, targetPath) : AssetOperationValidation.valid; + } + + private bool TryPrepareMove( + string sourcePath, + string targetDirectory, + bool validateEditor, + out AssetEditorContext? context, + out AssetEditor? editor, + out string targetPath + ) { + context = null; + editor = null; + targetPath = string.Empty; + string source = Normalize(sourcePath); + string directory = Normalize(targetDirectory); + AssetPath isolatedSource = AssetPath.Parse(source); + AssetPath isolatedDirectory = AssetPath.Parse(directory); + if (isolatedSource.source != AssetSourceId.project || isolatedDirectory.source != AssetSourceId.project) + { + return false; + } + + EditorContext? editorContext = m_context; + if (editorContext is null || string.IsNullOrEmpty(source) || !TryCreateContext(editorContext, source, out context) || context is null) + { + return false; + } + + if (!string.IsNullOrEmpty(directory) && (!m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(directory), out AssetFileEntry destination) || !destination.isDirectory)) + { + return false; + } + + string parent = Normalize(Path.GetDirectoryName(source)); + if (string.Equals(parent, directory, StringComparison.OrdinalIgnoreCase)) + return false; + if (context.isDirectory && (string.Equals(source, directory, StringComparison.OrdinalIgnoreCase) || directory.StartsWith(source + "/", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + string name = Path.GetFileName(source); + targetPath = string.IsNullOrEmpty(directory) ? name : $"{directory}/{name}"; + if (m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) + return false; + editor = m_editors.Resolve(context.assetType); + return !validateEditor || editor.ValidateRename(context, targetPath).isValid; + } + + private EditorHistoryResult MoveAsset( + AssetEditor editor, + AssetEditorContext context, + string sourcePath, + string targetPath + ) { + m_pipeline.Move(AssetPath.Parse(sourcePath), AssetPath.Parse(targetPath)); + try + { + editor.OnRenamed(context, sourcePath, targetPath); + } + catch (Exception exception) + { + m_log.Write(LogLevel.Error, "Asset editor rename hook failed for '{0}': {1}", [targetPath, exception]); + } + + return EditorHistoryResult.Success(); + } + + private void RequireMove( + AssetEditor editor, + AssetEditorContext context, + string sourcePath, + string targetPath + ) { + EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); + if (!result.succeeded) + throw new InvalidOperationException(result.message); + } + + private static void RollbackAndRethrow( + Exception failure, + Action rollback + ) { + try + { + rollback(); + } + catch (Exception rollbackException) + { + throw new AggregateException("An Asset mutation could not be recorded and its compensation also failed.", failure, rollbackException); + } + + ExceptionDispatchInfo.Capture(failure).Throw(); + } + + private static EditorValidationResult ToEditorValidation(AssetOperationValidation validation) => new(validation.isValid, validation.message); + private static string Normalize(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/').Trim('/'); + private static EditorDragData CreateDefaultDragData(AssetFileEntry entry) => new(entry, entry.name); +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs similarity index 98% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs index ceeed0b70..97199c762 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditor.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditor.cs @@ -1,7 +1,6 @@ using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Customizes editor interactions for one imported asset type. /// @@ -17,7 +16,6 @@ public abstract class AssetEditor /// when can handle the entry; otherwise, . /// public virtual bool CanOpen(AssetEditorContext context) => false; - /// /// Opens the supplied asset entry when no more specific typed open action handled it. /// @@ -44,7 +42,6 @@ public virtual AssetOperationValidation ValidateRename( AssetEditorContext context, string targetPath ) => AssetOperationValidation.valid; - /// /// Runs after an asset move transaction commits successfully. /// @@ -74,7 +71,6 @@ string newPath /// A valid result when the transaction may proceed, or a diagnostic describing the rejection. /// public virtual AssetOperationValidation ValidateDelete(AssetEditorContext context) => AssetOperationValidation.valid; - /// /// Runs after an asset deletion transaction commits successfully. /// @@ -95,7 +91,6 @@ public virtual void OnDeleted(AssetEditorContext context) /// when drag data can be created; otherwise, . /// public virtual bool CanStartDrag(AssetEditorContext context) => true; - /// /// Creates the managed source, preview label, and validity predicate for an asset drag. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs similarity index 97% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs index c3543b7c5..c770bfcf0 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorAttribute.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorAttribute.cs @@ -1,7 +1,6 @@ using System; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Associates an asset editor with an imported asset type. /// @@ -37,12 +36,10 @@ public AssetEditorAttribute( /// Gets the imported asset type handled by the editor. /// public Type assetType { get; } - /// /// Gets whether derived asset types are accepted. /// public bool useForChildren { get; } - /// /// Gets the tie-breaking priority. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs similarity index 89% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs index 48fb24597..4d5c0fc25 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorContext.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorContext.cs @@ -1,18 +1,15 @@ using System; - using Inno.Assets; using Inno.Editor.Core; using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Provides an immutable snapshot for an asset editor operation. /// public sealed class AssetEditorContext { private readonly Func m_createDefaultDragData; - /// /// Creates an immutable snapshot used by one asset-editor operation. /// @@ -41,7 +38,7 @@ public sealed class AssetEditorContext /// The generation-bound factory for the built-in drag payload. /// /// - /// Thrown when , , , or is . + /// Thrown when , , , or is . /// internal AssetEditorContext( EditorContext editorContext, @@ -60,40 +57,33 @@ Func createDefaultDragData this.isDirectory = isDirectory; this.info = info; this.assetType = assetType; - m_createDefaultDragData = createDefaultDragData ?? - throw new ArgumentNullException(nameof(createDefaultDragData)); + m_createDefaultDragData = createDefaultDragData ?? throw new ArgumentNullException(nameof(createDefaultDragData)); } /// /// Gets the shared editor context. /// public EditorContext editorContext { get; } - /// /// Gets the active editor interaction entry point. /// public EditorInteractions interactions { get; } - /// /// Gets the source-relative path. /// public string relativePath { get; } - /// /// Gets the final source path segment. /// public string name { get; } - /// /// Gets whether the source represents a directory. /// public bool isDirectory { get; } - /// /// Gets the cataloged asset information when available. /// public AssetInfo? info { get; } - /// /// Gets the resolved imported asset type when available. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorRegistry.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorRegistry.cs similarity index 61% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorRegistry.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorRegistry.cs index 506d99ff8..36398d252 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorRegistry.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetEditorRegistry.cs @@ -1,18 +1,15 @@ +using Inno.Editor.Core; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; - using Inno.Extensibility.Types; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal sealed class AssetEditorRegistry : TypeRegistry { private static readonly AssetEditor S_DEFAULT_EDITOR = new DefaultAssetEditor(); - - internal AssetEditorRegistry(TypeCatalog types) - : base(types) + internal AssetEditorRegistry(TypeCatalog types) : base(types) { } @@ -24,17 +21,17 @@ internal AssetEditor Resolve(Type? assetType) int bestDistance = int.MaxValue; foreach (Registration registration in current.registrations) { - if (!AssetTypeDistance.TryGet(assetType, registration.assetType, out int distance)) + if (!EditorTypeSpecificity.TryGet(assetType, registration.assetType, out int distance)) continue; if (distance > 0 && !registration.useForChildren) continue; - if (best is null || distance < bestDistance || - distance == bestDistance && registration.priority > best.priority) + if (best is null || distance < bestDistance || distance == bestDistance && registration.priority > best.priority) { best = registration; bestDistance = distance; } } + return best?.editor ?? S_DEFAULT_EDITOR; } @@ -51,33 +48,24 @@ protected override Snapshot Build(TypeCacheSnapshot types) { var instances = new Dictionary(); var registrations = new List(); - foreach (Type type in types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static value => value.FullName, StringComparer.Ordinal)) + foreach (Type type in types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static value => value.FullName, + StringComparer.Ordinal + )) { - AssetEditor editor = instances.TryGetValue(type, out AssetEditor? existing) - ? existing - : CreateExtension(type); + AssetEditor editor = instances.TryGetValue(type, out AssetEditor? existing) ? existing : CreateExtension(type); instances[type] = editor; foreach (AssetEditorAttribute attribute in type.GetCustomAttributes(false)) { - if (registrations.Any(value => - value.assetType == attribute.assetType && - value.useForChildren == attribute.useForChildren && - value.priority == attribute.priority)) + if (registrations.Any(value => value.assetType == attribute.assetType && value.useForChildren == attribute.useForChildren && value.priority == attribute.priority)) { - throw new InvalidOperationException( - $"Asset editor registration for '{attribute.assetType.FullName}' conflicts at " + - $"priority {attribute.priority}."); + throw new InvalidOperationException($"Asset editor registration for '{attribute.assetType.FullName}' conflicts at " + $"priority {attribute.priority}."); } - registrations.Add(new Registration( - attribute.assetType, - attribute.useForChildren, - attribute.priority, - type, - editor)); + + registrations.Add(new Registration(attribute.assetType, attribute.useForChildren, attribute.priority, type, editor)); } } + return new Snapshot(registrations.ToArray()); } @@ -87,11 +75,8 @@ protected override Snapshot Build(TypeCacheSnapshot types) /// /// The immutable state snapshot consumed by this operation. /// - protected override void DisposeSnapshot(Snapshot snapshot) - => DisposeExtensions(snapshot.registrations.Select(static registration => registration.editor)); - + protected override void DisposeSnapshot(Snapshot snapshot) => DisposeExtensions(snapshot.registrations.Select(static registration => registration.editor)); internal sealed record Snapshot(Registration[] registrations); - internal sealed record Registration( Type assetType, bool useForChildren, @@ -99,6 +84,5 @@ internal sealed record Registration( Type implementationType, AssetEditor editor ); - private sealed class DefaultAssetEditor : AssetEditor; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs similarity index 97% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs rename to src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs index dd8c2c42c..7715e2c0c 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetOperationValidation.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Editing/AssetOperationValidation.cs @@ -1,5 +1,4 @@ -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Describes whether an editor asset operation may proceed. /// @@ -26,12 +25,10 @@ string message /// Gets whether the operation may proceed. /// public bool isValid { get; } - /// /// Gets the validation diagnostic. /// public string message { get; } - /// /// Gets a successful validation result. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryData.cs b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryData.cs similarity index 97% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryData.cs rename to src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryData.cs index 01d420da4..7142d0fb5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryData.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryData.cs @@ -2,15 +2,15 @@ using System.IO; using System.Text; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal sealed record AssetHistoryData( AssetHistoryOperationKind operationKind, string sourcePath, string targetPath, bool isDirectory, byte[] archive -) { +) +{ internal byte[] Encode() { using var stream = new MemoryStream(); diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryHandler.cs b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryHandler.cs similarity index 75% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryHandler.cs rename to src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryHandler.cs index 6d51f7246..ee2638df3 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryHandler.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryHandler.cs @@ -1,20 +1,16 @@ using System; - using Inno.Assets; using Inno.Core.Logging; using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; [EditorHistoryHandler(AssetHistoryKinds.SourceOperation)] internal sealed class AssetHistoryHandler( - AssetEditorModule assets, + AssetEditingModule assets, LogRouter logs ) : EditorHistoryHandler { - private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))) - .CreateLogger(); - + private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))).CreateLogger(); /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -44,8 +40,7 @@ EditorHistoryDirection direction AssetHistoryOperationKind.CreateDirectory => QueryCreateDirectory(data, direction), AssetHistoryOperationKind.Delete => QueryDelete(data, direction), AssetHistoryOperationKind.CreateAsset => QueryCreateAsset(data, direction), - _ => EditorHistoryAvailability.Unavailable("Unknown asset history operation.") - }; + _ => EditorHistoryAvailability.Unavailable("Unknown asset history operation.")}; } catch (Exception exception) { @@ -81,12 +76,8 @@ EditorHistoryDirection direction switch (data.operationKind) { case AssetHistoryOperationKind.Move: - string moveSource = direction == EditorHistoryDirection.Undo - ? data.targetPath - : data.sourcePath; - string moveTarget = direction == EditorHistoryDirection.Undo - ? data.sourcePath - : data.targetPath; + string moveSource = direction == EditorHistoryDirection.Undo ? data.targetPath : data.sourcePath; + string moveTarget = direction == EditorHistoryDirection.Undo ? data.sourcePath : data.targetPath; result = ApplyMove(moveSource, moveTarget); selectionPath = moveTarget; break; @@ -102,6 +93,7 @@ EditorHistoryDirection direction default: return EditorHistoryResult.Failure("Unknown asset history operation."); } + if (!result.succeeded) return result; try @@ -112,6 +104,7 @@ EditorHistoryDirection direction { m_log.Write(LogLevel.Error, "Asset history selection notification failed: {0}", [exception]); } + return EditorHistoryResult.Success(); } catch (Exception exception) @@ -131,8 +124,7 @@ string targetPath } catch (Exception exception) { - if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(sourcePath), out _) && - assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) + if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(sourcePath), out _) && assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) { try { @@ -140,11 +132,10 @@ string targetPath } catch (Exception rollbackException) { - return StateIntegrityFailure( - $"Asset move failed: {exception.Message} " + - $"Rollback failed: {rollbackException.Message}"); + return StateIntegrityFailure($"Asset move failed: {exception.Message} " + $"Rollback failed: {rollbackException.Message}"); } } + return EditorHistoryResult.Failure(exception.Message); } } @@ -177,10 +168,9 @@ EditorHistoryDirection direction } catch (Exception rollbackException) { - return StateIntegrityFailure( - $"Asset folder operation failed: {exception.Message} " + - $"Rollback failed: {rollbackException.Message}"); + return StateIntegrityFailure($"Asset folder operation failed: {exception.Message} " + $"Rollback failed: {rollbackException.Message}"); } + return EditorHistoryResult.Failure(exception.Message); } } @@ -213,10 +203,9 @@ EditorHistoryDirection direction } catch (Exception rollbackException) { - return StateIntegrityFailure( - $"Asset delete operation failed: {exception.Message} " + - $"Rollback failed: {rollbackException.Message}"); + return StateIntegrityFailure($"Asset delete operation failed: {exception.Message} " + $"Rollback failed: {rollbackException.Message}"); } + return EditorHistoryResult.Failure(exception.Message); } } @@ -249,10 +238,9 @@ EditorHistoryDirection direction } catch (Exception rollbackException) { - return StateIntegrityFailure( - $"Asset creation history failed: {exception.Message} " + - $"Rollback failed: {rollbackException.Message}"); + return StateIntegrityFailure($"Asset creation history failed: {exception.Message} " + $"Rollback failed: {rollbackException.Message}"); } + return EditorHistoryResult.Failure(exception.Message); } } @@ -265,44 +253,40 @@ EditorHistoryDirection direction string target = direction == EditorHistoryDirection.Undo ? data.sourcePath : data.targetPath; if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(source), out _)) return EditorHistoryAvailability.Unavailable($"Asset '{source}' no longer exists."); - return !assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(target), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Asset '{target}' already exists."); + return !assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(target), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Asset '{target}' already exists."); } private EditorHistoryAvailability QueryCreateDirectory( AssetHistoryData data, EditorHistoryDirection direction - ) - => direction == EditorHistoryDirection.Undo - ? assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Folder '{data.sourcePath}' no longer exists.") - : !assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Folder '{data.sourcePath}' already exists."); - + ) => direction == EditorHistoryDirection.Undo ? assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Folder '{data.sourcePath}' no longer exists.") : !assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Folder '{data.sourcePath}' already exists."); private EditorHistoryAvailability QueryDelete( AssetHistoryData data, EditorHistoryDirection direction - ) - => direction == EditorHistoryDirection.Undo - ? !assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' already exists.") - : assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' no longer exists."); - + ) => direction == EditorHistoryDirection.Undo ? !assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' already exists.") : assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' no longer exists."); private EditorHistoryAvailability QueryCreateAsset( AssetHistoryData data, EditorHistoryDirection direction - ) - => direction == EditorHistoryDirection.Undo - ? assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' no longer exists.") - : !assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(data.sourcePath), out _) - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' already exists."); + ) => direction == EditorHistoryDirection.Undo ? assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' no longer exists.") : !assets.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(data.sourcePath), + out _ + ) ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable($"Asset '{data.sourcePath}' already exists."); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryKinds.cs b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryKinds.cs similarity index 74% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryKinds.cs rename to src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryKinds.cs index 3a83e080a..645f029df 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryKinds.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryKinds.cs @@ -1,5 +1,4 @@ -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal static class AssetHistoryKinds { internal const string SourceOperation = "inno.assets/source-operation"; diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryOperationKind.cs b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryOperationKind.cs similarity index 75% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryOperationKind.cs rename to src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryOperationKind.cs index 90f971f5c..a9a478a7e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetHistoryOperationKind.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/History/AssetHistoryOperationKind.cs @@ -1,5 +1,4 @@ -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal enum AssetHistoryOperationKind : byte { Move = 1, diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetSourceArchive.cs b/src/composition/editor/features/Inno.Editor.Assets/History/AssetSourceArchive.cs similarity index 91% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetSourceArchive.cs rename to src/composition/editor/features/Inno.Editor.Assets/History/AssetSourceArchive.cs index e671d8264..ea5a253ae 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/History/AssetSourceArchive.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/History/AssetSourceArchive.cs @@ -2,17 +2,14 @@ using System.IO; using System.IO.Compression; using System.Threading; - using Inno.Assets; using Inno.Assets.Pipeline; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; internal static class AssetSourceArchive { private const string C_SOURCE_ENTRY = "source"; private const string C_META_ENTRY = "source.imeta"; - internal static byte[] Capture( AssetPipeline assets, string relativePath, @@ -23,7 +20,6 @@ out bool isDirectory isDirectory = Directory.Exists(source); if (!isDirectory && !File.Exists(source)) throw new FileNotFoundException($"Asset source '{relativePath}' does not exist.", source); - return CapturePhysical(source, isDirectory, CancellationToken.None); } @@ -46,6 +42,7 @@ CancellationToken cancellationToken string local = Path.GetRelativePath(source, directories[i]).Replace('\\', '/'); _ = archive.CreateEntry($"{C_SOURCE_ENTRY}/{local}/"); } + string[] files = Directory.GetFiles(source, "*", SearchOption.AllDirectories); Array.Sort(files, StringComparer.Ordinal); for (int i = 0; i < files.Length; i++) @@ -63,6 +60,7 @@ CancellationToken cancellationToken if (File.Exists(meta)) WriteEntry(archive, meta, C_META_ENTRY, cancellationToken); } + return stream.ToArray(); } @@ -98,11 +96,7 @@ ReadOnlySpan data string? parent = Path.GetDirectoryName(target); if (!string.IsNullOrEmpty(parent)) Directory.CreateDirectory(parent); - - string stagingDirectory = Path.Combine( - Path.GetTempPath(), - "InnoAssetHistory", - Guid.NewGuid().ToString("N")); + string stagingDirectory = Path.Combine(Path.GetTempPath(), "InnoAssetHistory", Guid.NewGuid().ToString("N")); string stagingSource = Path.Combine(stagingDirectory, C_SOURCE_ENTRY); string stagingMeta = Path.Combine(stagingDirectory, C_META_ENTRY); bool sourceCommitted = false; @@ -117,11 +111,7 @@ ReadOnlySpan data for (int i = 0; i < archive.Entries.Count; i++) { ZipArchiveEntry entry = archive.Entries[i]; - string destination = ResolveDestination( - stagingSource, - stagingMeta, - isDirectory, - entry.FullName); + string destination = ResolveDestination(stagingSource, stagingMeta, isDirectory, entry.FullName); string fullDestination = Path.GetFullPath(destination); if (!fullDestination.StartsWith(stagingRoot, StringComparison.Ordinal)) throw new InvalidDataException("Asset history archive escapes its staging directory."); @@ -130,6 +120,7 @@ ReadOnlySpan data Directory.CreateDirectory(fullDestination); continue; } + string? destinationParent = Path.GetDirectoryName(fullDestination); if (!string.IsNullOrEmpty(destinationParent)) Directory.CreateDirectory(destinationParent); @@ -146,6 +137,7 @@ ReadOnlySpan data File.Move(stagingMeta, target + ".imeta"); metaCommitted = true; } + assets.Rescan(); assets.WaitForIdle(); } @@ -166,6 +158,7 @@ ReadOnlySpan data { } } + throw; } finally @@ -188,10 +181,9 @@ string entryName string prefix = C_SOURCE_ENTRY + "/"; if (isDirectory && entryName.StartsWith(prefix, StringComparison.Ordinal)) { - return Path.Combine( - stagingSource, - entryName[prefix.Length..].Replace('/', Path.DirectorySeparatorChar)); + return Path.Combine(stagingSource, entryName[prefix.Length..].Replace('/', Path.DirectorySeparatorChar)); } + throw new InvalidDataException($"Unknown asset history archive entry '{entryName}'."); } @@ -200,9 +192,7 @@ private static string GetSourcePath( string relativePath ) { string root = Path.GetFullPath(assets.assetRoot) + Path.DirectorySeparatorChar; - string result = Path.GetFullPath(Path.Combine( - assets.assetRoot, - relativePath.Replace('/', Path.DirectorySeparatorChar))); + string result = Path.GetFullPath(Path.Combine(assets.assetRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))); if (!result.StartsWith(root, StringComparison.Ordinal)) throw new InvalidDataException($"Asset history path '{relativePath}' escapes the asset root."); return result; diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs b/src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs similarity index 87% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs rename to src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs index 4084f55fe..28e73c575 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetImportSettingsEdits.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/ImportSettings/AssetImportSettingsEdits.cs @@ -7,8 +7,7 @@ using Inno.Editor.Interactions; using Inno.Extensibility.Types; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; /// /// Applies importer configuration through the common sidecar pipeline and stable-identity Editor history. /// @@ -76,8 +75,13 @@ string expectedFingerprint throw new InvalidOperationException("The importer settings type changed. Reload the editor."); if (!m_assets.TryGetPersistentId(path, out Guid id)) throw new InvalidOperationException("The source has no persistent identity."); - var data = new ChangeData { assetId = id, typeId = m_types.GetTypeRef(settings.GetType()).stableId, - before = Capture(before.value), after = Capture(settings) }; + var data = new ChangeData + { + assetId = id, + typeId = m_types.GetTypeRef(settings.GetType()).stableId, + before = Capture(before.value), + after = Capture(settings) + }; if (data.before.AsSpan().SequenceEqual(data.after)) return m_assets.TryGetInfo(id, out AssetInfo? info) && info?.status == AssetImportStatus.Imported; bool imported = m_assets.SaveImportSettings(path, settings, expectedFingerprint); @@ -97,8 +101,10 @@ string expectedFingerprint { throw new AggregateException(failure, rollback); } + throw; } + return imported; } @@ -144,15 +150,20 @@ EditorHistoryDirection direction return (info.assetPath, current); } - private byte[] Capture(ISerializable value) - => m_serialization.Encode(writer => writer.WriteProperties(value), AssetSerializationContext.Create(m_assets)); - + private byte[] Capture(ISerializable value) => m_serialization.Encode(writer => writer.WriteProperties(value), AssetSerializationContext.Create(m_assets)); private sealed class ChangeData : ISerializable { - [SerializableProperty] internal Guid assetId { get; set; } - [SerializableProperty] internal Guid typeId { get; set; } - [SerializableProperty] internal byte[] before { get; set; } = []; - [SerializableProperty] internal byte[] after { get; set; } = []; + [SerializableProperty] + internal Guid assetId { get; set; } + + [SerializableProperty] + internal Guid typeId { get; set; } + + [SerializableProperty] + internal byte[] before { get; set; } = []; + + [SerializableProperty] + internal byte[] after { get; set; } = []; } } @@ -174,21 +185,23 @@ internal sealed class AssetImportSettingsHistory(AssetImportSettingsEdits edits) /// /// The validated editor history availability that represents the completed operation. /// -protected override EditorHistoryAvailability Query( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryAvailability Query( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { edits.Validate(change, direction); return EditorHistoryAvailability.Available(); } - catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { return EditorHistoryAvailability.Unavailable(failure.Message); } } + /// /// Applies a validated change atomically at the caller-controlled commit point. /// @@ -204,17 +217,18 @@ EditorHistoryDirection direction /// /// The validated editor history result that represents the completed operation. /// -protected override EditorHistoryResult Apply( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryResult Apply( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { edits.ApplyHistory(change, direction); return EditorHistoryResult.Success(); } - catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { return EditorHistoryResult.Failure(failure.Message); } diff --git a/src/composition/editor/features/Inno.Editor.Assets/Inno.Editor.Assets.csproj b/src/composition/editor/features/Inno.Editor.Assets/Inno.Editor.Assets.csproj index a77201c8c..0b740982a 100644 --- a/src/composition/editor/features/Inno.Editor.Assets/Inno.Editor.Assets.csproj +++ b/src/composition/editor/features/Inno.Editor.Assets/Inno.Editor.Assets.csproj @@ -10,24 +10,22 @@ + - - - - + + + + + + + + - + - diff --git a/src/composition/editor/features/Inno.Editor.Assets/Properties/ScriptingApi.cs b/src/composition/editor/features/Inno.Editor.Assets/Properties/ScriptingApi.cs index 1aba477cf..b628355d7 100644 --- a/src/composition/editor/features/Inno.Editor.Assets/Properties/ScriptingApi.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Properties/ScriptingApi.cs @@ -1,12 +1,15 @@ using Inno.Editor.Assets; +using Inno.Assets.Pipeline; using Inno.Scripting.Api; -[assembly: ScriptingApiNamespace( - "InnoEditor.Assets", - "Inno.Editor.Assets", - ScriptingApiScope.Editor)] - +[assembly: ScriptingApiNamespace("InnoEditor.Assets", "Inno.Editor.Assets", ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(AssetCreationMenuAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(AssetCreationContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(AssetCreationTemplate), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(AssetCreationTemplate<>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetEditor), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetFileEntry), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetEditorAttribute), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetEditorContext), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetOperationValidation), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetImportSettingsEdits), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModule.cs b/src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs similarity index 68% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModule.cs rename to src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs index 77f34c6b6..71c197a5d 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModule.cs +++ b/src/composition/editor/features/Inno.Editor.Assets/Samples/AssetSampleImportModule.cs @@ -1,8 +1,8 @@ +using Inno.Editor.Assets; using System; using System.IO; using System.Linq; using System.Threading.Tasks; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Core.Execution; @@ -12,13 +12,15 @@ using Inno.Editor.Scripting; using Inno.Scripting.Compiler; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets; +/// +/// Owns sample import validation, publication and rollback independently of its progress window. +/// [EditorModule("asset-sample-import", order: 110)] -internal sealed class AssetSampleImportModule : EditorModule, IProgress +public sealed class AssetSampleImportModule : EditorModule, IProgress { private readonly object m_progressLock = new(); - private readonly AssetEditorModule m_assets; + private readonly AssetEditingModule m_assets; private readonly ScriptCompiler m_compiler; private readonly IEditorScriptCompilation m_scripting; private readonly Logger m_log; @@ -30,9 +32,8 @@ internal sealed class AssetSampleImportModule : EditorModule, IProgress public override bool blocksFollowingUpdates => isBusy || m_faulted; + /// + /// Gets whether an import still owns validation or publication work. + /// + public bool isBusy => !m_faulted && m_import is not null; + /// + /// Gets whether the current authoring generation admits a new sample import. + /// + public bool canImport => !m_faulted && !isBusy && m_scripting.state is EditorScriptCompilationState.Ready or EditorScriptCompilationState.Failed; - internal bool isBusy => !m_faulted && m_import is not null; - internal bool canImport => !m_faulted && !isBusy && m_scripting.state is EditorScriptCompilationState.Ready or EditorScriptCompilationState.Failed; - internal string status { get { lock (m_progressLock) return m_status; } } - internal float progress { get { lock (m_progressLock) return m_progress; } } + /// + /// Gets an immutable progress message captured under the progress owner lock. + /// + public string status + { + get + { + lock (m_progressLock) + return m_status; + } + } - internal void Request(AssetPath source) + /// + /// Gets the most recent validation progress fraction. + /// + public float progress + { + get + { + lock (m_progressLock) + return m_progress; + } + } + + /// + /// Begins validating an installed sample before its source copy can be committed. + /// + /// + /// The installed sample root to validate and import. + /// + /// + /// The authoring generation is busy or faulted. + /// + public void Request(AssetPath source) { if (!canImport) throw new InvalidOperationException("Sample import requires an idle authoring generation."); @@ -62,7 +99,10 @@ internal void Request(AssetPath source) SetProgress(0f, "Preparing sample assets..."); } - internal void Cancel() + /// + /// Requests cancellation without releasing work that has not retired. + /// + public void Cancel() { m_canceled = true; m_import?.Cancel(); @@ -75,9 +115,7 @@ internal void Cancel() /// /// The compiler stage and completion fraction. /// - public void Report(ScriptCompilationProgress value) - => SetProgress(0.2f + value.fraction * 0.6f, value.stage); - + public void Report(ScriptCompilationProgress value) => SetProgress(0.2f + value.fraction * 0.6f, value.stage); /// /// Advances completed background phases and publishes the validated import at a frame safe point. /// @@ -95,6 +133,7 @@ protected override void OnUpdate(EditorContext context) RetireImport(); return; } + AssetSampleImportTransaction import = m_import!; if (!m_validating) { @@ -109,34 +148,29 @@ protected override void OnUpdate(EditorContext context) cancellationToken ) => { - ScriptCompilationResult result = await m_compiler.CompileAuthoringGenerationAsync( - this, cancellationToken, sources).ConfigureAwait(false); + ScriptCompilationResult result = await m_compiler.CompileAuthoringGenerationAsync(this, cancellationToken, sources).ConfigureAwait(false); if (!result.success) - throw new InvalidOperationException("Sample script preflight failed:" + Environment.NewLine - + string.Join(Environment.NewLine, - result.diagnostics.Select(static diagnostic => diagnostic.message))); + throw new InvalidOperationException("Sample script preflight failed:" + Environment.NewLine + string.Join(Environment.NewLine, result.diagnostics.Select(static diagnostic => diagnostic.message))); SetProgress(0.85f, "Preparing sample history..."); m_historyPayload = await Task.Run(() => { - byte[] archive = AssetSourceArchive.CapturePhysical( - physicalSource, isDirectory: true, cancellationToken); - var data = new AssetHistoryData( - AssetHistoryOperationKind.CreateAsset, targetPath, string.Empty, - isDirectory: true, archive); + byte[] archive = AssetSourceArchive.CapturePhysical(physicalSource, isDirectory: true, cancellationToken); + var data = new AssetHistoryData(AssetHistoryOperationKind.CreateAsset, targetPath, string.Empty, isDirectory: true, archive); cancellationToken.ThrowIfCancellationRequested(); return EditorHistoryPayload.FromBytes(data.Encode()); }, cancellationToken).ConfigureAwait(false); }); return; } + if (!import.isValidationComplete) return; import.Commit(_ => { m_assets.interactions.history.RecordApplied( - "Import Sample", new EditorHistoryChange( - AssetHistoryKinds.SourceOperation, - m_historyPayload ?? throw new InvalidOperationException("Validated sample history is missing."))); + "Import Sample", + new EditorHistoryChange(AssetHistoryKinds.SourceOperation, m_historyPayload ?? throw new InvalidOperationException("Validated sample history is missing.")) + ); }); m_log.Write(LogLevel.Info, "Imported sample to '{0}'.", [import.target]); m_assets.SelectPath(import.target.ToString()); @@ -146,7 +180,7 @@ protected override void OnUpdate(EditorContext context) { if (m_import?.isFaulted == true) ReportFault(pending); - // The next frame retries retirement while the modal and dependency ownership remain intact. + // The next frame retries retirement while the modal and dependency ownership remain intact. } catch (Exception failure) { @@ -173,7 +207,6 @@ protected override void OnStop(EditorContext context) /// Retries retirement without releasing dependencies still used by background work. /// protected override void OnDispose() => RetireImport(); - private void RetireImport() { m_import?.Rollback(); diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Inno.Editor.Exporting.csproj b/src/composition/editor/features/Inno.Editor.Exporting/Inno.Editor.Exporting.csproj index 20fd4466e..82ee91974 100644 --- a/src/composition/editor/features/Inno.Editor.Exporting/Inno.Editor.Exporting.csproj +++ b/src/composition/editor/features/Inno.Editor.Exporting/Inno.Editor.Exporting.csproj @@ -4,35 +4,21 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - - - - - - - - + + + + + diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportModal.cs b/src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportModal.cs deleted file mode 100644 index 4b11fc484..000000000 --- a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportModal.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Numerics; -using Inno.Build; -using Inno.Build.Managed; -using Inno.Editor.Core; -using Inno.Editor.ImGui; -using Inno.Native.ImGui; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Exporting; - -[EditorModal("export.game", "Export as Game", order: 310)] -internal sealed unsafe class GameExportModal(ExportWindowModule window) : EditorModal -{ - private const int C_TEXT_CAPACITY = 4096; - - /// - /// Gets whether this implementation is visible. - /// - public override bool isVisible => window.isGameVisible; - - /// - /// Gets whether this implementation can move. - /// - public override bool canMove => true; - - /// - /// Gets whether this implementation can resize. - /// - public override bool canResize => true; - - /// - /// Gets the preferred initial window size in logical editor units. - /// - public override Vector2 initialSize => new(760f, 540f); - - /// - /// Gets the smallest size that keeps the export form usable. - /// - public override Vector2 minimumSize => new(620f, 460f); - - /// - /// Draws this feature using the current editor presentation context. - /// - /// - /// The context that supplies state and services for this operation. - /// - protected override void OnDraw(EditorContext context) - { - EditorWidget.WrappedText( - "Build a standalone Player from the current project. Only imported runtime content " + - "is deployed; source files stay in the project."); - NativeImGui.SeparatorText("Application"); - if (BeginFields("##application_fields")) - { - DrawReadOnly("Application ID", window.gameApplicationId); - DrawText("Product Name", "game_name", window.gameProductName, value => window.gameProductName = value); - DrawText("Persistent Data Folder", "game_data", window.gamePersistentDataPath, - value => window.gamePersistentDataPath = value); - DrawText("Startup Scene", "game_scene", window.gameStartupScene, value => window.gameStartupScene = value); - NativeImGui.EndTable(); - } - NativeImGui.SeparatorText("Window and Platform"); - if (BeginFields("##platform_fields")) - { - DrawWindowSize(); - DrawTarget(); - DrawManagedDeployment(); - NativeImGui.EndTable(); - } - NativeImGui.SeparatorText("Output"); - if (BeginFields("##output_fields")) - { - DrawText("Output Directory", "game_output", window.gameOutputDirectory, value => window.gameOutputDirectory = value); - NativeImGui.EndTable(); - } - NativeImGui.Spacing(); - EditorWidget.WrappedText("Defaults: Settings > Build > Game. Application ID: Settings > Project > Identity."); - if (!string.IsNullOrEmpty(window.error)) - { - NativeImGui.Spacing(); - EditorWidget.WrappedText(window.error); - } - NativeImGui.Separator(); - DrawButtons(); - } - - private static bool BeginFields(string id) - { - if (!NativeImGui.BeginTable(id, 2, (int)(ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoSavedSettings | - ImGuiTableFlags.NoPadOuterX), default(System.Numerics.Vector2), 0f)) - { - return false; - } - EditorWidget.SetupPropertyColumns(); - return true; - } - - private static void BeginField(string label) - { - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); - EditorWidget.PropertyLabel(label); - _ = NativeImGui.TableSetColumnIndex(1); - } - - private static void DrawReadOnly( - string label, - string value - ) { - BeginField(label); - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.textDisabled); - try - { - EditorWidget.WrappedText(value); - } - finally - { - NativeImGui.PopStyleColor(); - } - } - - private void DrawWindowSize() - { - int width = window.gameWindowWidth; - int height = window.gameWindowHeight; - BeginField("Initial Width"); - NativeImGui.SetNextItemWidth(-1f); - if (NativeImGui.InputInt("##game_window_width", ref width, 1, 100, 0)) - window.gameWindowWidth = Math.Max(1, width); - BeginField("Initial Height"); - NativeImGui.SetNextItemWidth(-1f); - if (NativeImGui.InputInt("##game_window_height", ref height, 1, 100, 0)) - window.gameWindowHeight = Math.Max(1, height); - } - - private void DrawManagedDeployment() - { - BeginField("Managed Deployment"); - NativeImGui.SetNextItemWidth(-1f); - if (!EditorWidget.BeginBoundedCombo("##game_deployment", window.gameManagedDeployment?.value ?? "Platform default")) - return; - try - { - if (NativeImGui.Selectable("Platform default", window.gameManagedDeployment is null)) - window.gameManagedDeployment = null; - foreach (ManagedDeploymentId deployment in window.availableManagedDeployments) - if (NativeImGui.Selectable(deployment.value, window.gameManagedDeployment == deployment)) - window.gameManagedDeployment = deployment; - } - finally - { - EditorWidget.EndBoundedCombo(); - } - } - - private void DrawTarget() - { - BeginField("Target"); - NativeImGui.SetNextItemWidth(-1f); - string preview = window.GetGameTargetDisplayName(window.gameTarget); - if (!EditorWidget.BeginBoundedCombo("##game_target", preview)) - return; - try - { - foreach (BuildTargetId target in window.availableGameTargets) - DrawTargetChoice(target); - } - finally - { - EditorWidget.EndBoundedCombo(); - } - } - - private void DrawTargetChoice(BuildTargetId target) - { - bool selected = window.gameTarget == target; - if (NativeImGui.Selectable(window.GetGameTargetDisplayName(target), selected)) - window.gameTarget = target; - if (selected) - NativeImGui.SetItemDefaultFocus(); - } - - private void DrawButtons() - { - NativeImGui.Spacing(); - string primaryLabel = "Export"; - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float closeWidth = NativeImGui.CalcTextSize("Close").X + style->FramePadding.X * 2f; - float primaryWidth = NativeImGui.CalcTextSize(primaryLabel).X + style->FramePadding.X * 2f; - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + - MathF.Max(0f, NativeImGui.GetContentRegionAvail().X - closeWidth - style->ItemSpacing.X - primaryWidth)); - if (NativeImGui.Button("Close")) - window.CloseGame(); - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.Button(primaryLabel)) - window.BeginGameExport(); - } - - private static void DrawText( - string label, - string id, - string value, - Action apply - ) { - BeginField(label); - NativeImGui.SetNextItemWidth(-1f); - _ = EditorImGui.InputText($"##{id}", ref value, C_TEXT_CAPACITY); - apply(value); - } -} diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/PluginExportModal.cs b/src/composition/editor/features/Inno.Editor.Exporting/Presentation/PluginExportModal.cs deleted file mode 100644 index 1d0464cce..000000000 --- a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/PluginExportModal.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Numerics; -using Inno.Editor.Core; -using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Exporting; - -[EditorModal("export.plugin", "Export as Plugin", order: 300)] -internal sealed unsafe class PluginExportModal(ExportWindowModule window) : EditorModal -{ - private const int C_TEXT_CAPACITY = 4096; - - /// - /// Gets whether this implementation is visible. - /// - public override bool isVisible => window.isPluginVisible; - - /// - /// Gets whether this implementation can move. - /// - public override bool canMove => true; - - /// - /// Gets the preferred initial window size in logical editor units. - /// - public override Vector2 initialSize => new(680f, 0f); - - /// - /// Draws this feature using the current editor presentation context. - /// - /// - /// The context that supplies state and services for this operation. - /// - protected override void OnDraw(EditorContext context) - { - EditorWidget.WrappedText( - "Packages the current project's complete authored source. Plugin.inno, dependency declarations, " + - "settings contributions, and deterministic metadata are generated automatically."); - NativeImGui.Spacing(); - NativeImGui.BeginDisabled(window.isPluginBusy); - try - { - NativeImGui.TextUnformatted("Plugin ID"); - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, $"{window.pluginId} (from Project ID)"); - DrawText("Display Name", "plugin_name", window.pluginDisplayName, value => window.pluginDisplayName = value); - DrawText("Destination IPlugin", "plugin_output", window.pluginOutputPath, value => window.pluginOutputPath = value); - bool includeDependencies = window.includePluginDependencies; - if (NativeImGui.Checkbox("Include dependency Plugin packages", ref includeDependencies)) - window.includePluginDependencies = includeDependencies; - } - finally - { - NativeImGui.EndDisabled(); - } - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Identity comes from Settings > Project > Identity. Other defaults come from Build > Plugin."); - DrawOutcome(); - DrawButtons(); - } - - private void DrawOutcome() - { - if (!string.IsNullOrEmpty(window.error)) - { - NativeImGui.Spacing(); - EditorWidget.WrappedText(window.error); - } - else if (!string.IsNullOrEmpty(window.status)) - { - NativeImGui.Spacing(); - EditorWidget.WrappedText(window.status); - } - } - - private void DrawButtons() - { - NativeImGui.Spacing(); - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float closeWidth = NativeImGui.CalcTextSize("Close").X + style->FramePadding.X * 2f; - string primaryLabel = window.isPluginBusy ? "Cancel" : "Export"; - float exportWidth = NativeImGui.CalcTextSize(primaryLabel).X + style->FramePadding.X * 2f; - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + - MathF.Max(0f, NativeImGui.GetContentRegionAvail().X - closeWidth - style->ItemSpacing.X - exportWidth)); - NativeImGui.BeginDisabled(window.isPluginBusy); - try - { - if (NativeImGui.Button("Close")) - window.ClosePlugin(); - } - finally - { - NativeImGui.EndDisabled(); - } - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.Button(primaryLabel)) - { - if (window.isPluginBusy) - window.CancelPluginExport(); - else - window.BeginPluginExport(); - } - } - - private static void DrawText( - string label, - string id, - string value, - Action apply - ) { - NativeImGui.TextUnformatted(label); - NativeImGui.SetNextItemWidth(-1f); - if (EditorImGui.InputText($"##{id}", ref value, C_TEXT_CAPACITY)) - apply(value); - else - apply(value); - } -} diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Runtime/ExportWindowModule.cs b/src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs similarity index 50% rename from src/composition/editor/features/Inno.Editor.Exporting/Runtime/ExportWindowModule.cs rename to src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs index 670e0688b..bcfdede72 100644 --- a/src/composition/editor/features/Inno.Editor.Exporting/Runtime/ExportWindowModule.cs +++ b/src/composition/editor/features/Inno.Editor.Exporting/Runtime/EditorExportService.cs @@ -13,14 +13,13 @@ using Inno.Editor.Core; namespace Inno.Editor.Exporting; - +/// +/// Owns asynchronous export, progress, cancellation and retirement independently from presentation. +/// [EditorModule("exporting", order: 300)] -internal sealed class ExportWindowModule : EditorModule +public sealed class EditorExportService : EditorModule { - private readonly EditorContext m_editor; private readonly BuildPipeline m_buildPipeline; - private readonly BuildSettingsStore m_buildSettings; - private readonly ProjectSettingsStore m_projectSettings; private readonly Logger m_log; private readonly ConcurrentQueue m_progress = new(); private CancellationTokenSource? m_cancellation; @@ -28,164 +27,103 @@ internal sealed class ExportWindowModule : EditorModule private Task? m_gameExport; private Task? m_pluginExport; private GameBuildRequest? m_pendingGameRequest; - - internal ExportWindowModule( - EditorContext editor, + internal EditorExportService( BuildPipeline buildPipeline, - BuildSettingsStore buildSettings, - ProjectSettingsStore projectSettings, LogRouter logs ) { - m_editor = editor ?? throw new ArgumentNullException(nameof(editor)); m_buildPipeline = buildPipeline ?? throw new ArgumentNullException(nameof(buildPipeline)); - m_buildSettings = buildSettings ?? throw new ArgumentNullException(nameof(buildSettings)); - m_projectSettings = projectSettings ?? throw new ArgumentNullException(nameof(projectSettings)); ArgumentNullException.ThrowIfNull(logs); - m_log = logs.CreateLogger(); - } - - internal bool isPluginVisible { get; private set; } - - internal bool isGameVisible { get; private set; } - - internal bool isGameProgressVisible { get; private set; } - - internal bool isPluginBusy => m_pluginExport is not null; - - internal bool isGameBusy => m_gamePreparation is not null || m_gameExport is not null; - - internal string pluginId => m_projectSettings.projectId.value; - - internal string pluginDisplayName { get; set; } = string.Empty; - - internal string pluginOutputPath { get; set; } = string.Empty; - - internal string gameApplicationId => m_projectSettings.projectId.value; - - internal string gameProductName { get; set; } = string.Empty; - - internal string gamePersistentDataPath { get; set; } = string.Empty; - - internal string gameStartupScene { get; set; } = string.Empty; - - internal string gameOutputDirectory { get; set; } = string.Empty; - - internal int gameWindowWidth { get; set; } = 1280; - - internal int gameWindowHeight { get; set; } = 720; - - internal BuildTargetId gameTarget { get; set; } - - internal ManagedDeploymentId? gameManagedDeployment { get; set; } - - internal IReadOnlyList availableManagedDeployments => m_buildPipeline.GetManagedDeployments(gameTarget); - - internal IReadOnlyList availableGameTargets => m_buildPipeline.availableGameTargets; - - internal string status { get; private set; } = string.Empty; - - internal string error { get; private set; } = string.Empty; - - internal float gameProgress { get; private set; } - - internal bool includePluginDependencies { get; set; } - - internal string GetGameTargetDisplayName(BuildTargetId target) - => m_buildPipeline.TryGetGameTargetDisplayName(target, out string displayName) - ? displayName - : $"Missing ({target})"; - - internal void OpenPlugin() - { - if (isPluginBusy || isGameBusy) - return; - CloseGame(); - status = string.Empty; - error = string.Empty; - BuildSettings defaults = LoadBuildSettings(); - pluginDisplayName = defaults.pluginDisplayName; - pluginOutputPath = defaults.pluginOutputPath; - includePluginDependencies = defaults.includePluginDependencies; - isPluginVisible = true; + m_log = logs.CreateLogger(); } - internal void OpenGame() - { - if (isPluginBusy || isGameBusy) - return; - ClosePlugin(); - isGameProgressVisible = false; - status = string.Empty; - error = string.Empty; - BuildSettings defaults = LoadBuildSettings(); - gameProductName = defaults.gameProductName; - gamePersistentDataPath = defaults.gamePersistentDataPath; - gameStartupScene = defaults.gameStartupScene; - gameOutputDirectory = defaults.gameOutputDirectory; - gameWindowWidth = defaults.gameWindowWidth; - gameWindowHeight = defaults.gameWindowHeight; - gameTarget = defaults.gameTarget; - gameManagedDeployment = defaults.gameManagedDeployment; - isGameVisible = true; - } + /// + /// Gets whether a Plugin source export still owns build resources. + /// + public bool isPluginBusy => m_pluginExport is not null; + /// + /// Gets whether a game export or its Support Pack preparation is active. + /// + public bool isGameBusy => m_gamePreparation is not null || m_gameExport is not null; + /// + /// Gets the latest owner-thread progress message. + /// + public string status { get; private set; } = string.Empty; + /// + /// Gets the last failure or cancellation diagnostic, or an empty string. + /// + public string error { get; private set; } = string.Empty; + /// + /// Gets monotonic progress for the current game build, from zero through one. + /// + public float gameProgress { get; private set; } + /// + /// Gets the targets registered by the active Build distribution. + /// + public IReadOnlyList availableGameTargets => m_buildPipeline.availableGameTargets; - internal void BeginPluginExport() + /// + /// Gets deployment implementations registered for a target. + /// + /// + /// The explicitly chosen build target. + /// + /// + /// The registered deployment choices; no host fallback is introduced. + /// + public IReadOnlyList GetManagedDeployments(BuildTargetId target) => m_buildPipeline.GetManagedDeployments(target); + /// + /// Gets a registered target label or a diagnostic for an unavailable target. + /// + /// + /// The target to describe. + /// + /// + /// A presentation label; never used as a routing identity. + /// + public string GetGameTargetDisplayName(BuildTargetId target) => m_buildPipeline.TryGetGameTargetDisplayName( + target, + out string displayName + ) ? displayName : $"Missing ({target})"; + /// + /// Starts a source-package export using the existing Build pipeline. + /// + /// + /// The complete, explicitly selected package request. + /// + /// + /// Another export still owns the service. + /// + public void StartPlugin(PluginBuildRequest request) { - if (isPluginBusy || isGameBusy) - return; + ArgumentNullException.ThrowIfNull(request); + EnsureIdle(); StartCancellation(); status = "Capturing the current Plugin source and dependency generation..."; error = string.Empty; - m_pluginExport = m_buildPipeline.BuildPluginAsync( - new PluginBuildRequest - { - pluginId = pluginId, - displayName = pluginDisplayName, - outputPath = ResolveOutputPath(pluginOutputPath), - includeDependencies = includePluginDependencies - }, - new BuildProgressSink(m_progress), - m_cancellation!.Token).AsTask(); + m_pluginExport = m_buildPipeline.BuildPluginAsync(request, new BuildProgressSink(m_progress), m_cancellation!.Token).AsTask(); } - internal void CancelPluginExport() - { - if (!isPluginBusy) - return; - status = "Canceling Plugin export..."; - m_cancellation?.Cancel(); - } - - internal void BeginGameExport() + /// + /// Prepares the selected Support Pack before capturing and building the game. + /// + /// + /// The complete game request; no form or window state is retained. + /// + /// + /// Another export still owns the service. + /// + public void StartGame(GameBuildRequest request) { - if (isPluginBusy || isGameBusy) - return; - var profile = new BuildProfile - { - applicationId = gameApplicationId, - productName = gameProductName, - persistentDataPath = gamePersistentDataPath, - startupScene = gameStartupScene, - target = gameTarget, - managedDeployment = gameManagedDeployment, - windowWidth = gameWindowWidth, - windowHeight = gameWindowHeight - }; + ArgumentNullException.ThrowIfNull(request); + EnsureIdle(); StartCancellation(); - isGameVisible = false; - isGameProgressVisible = true; gameProgress = 0f; status = "Checking or preparing the Player Support Pack..."; error = string.Empty; + m_pendingGameRequest = request; try { - m_pendingGameRequest = new GameBuildRequest - { - profile = profile, - outputDirectory = ResolveOutputPath(gameOutputDirectory) - }; - m_gamePreparation = m_buildPipeline.EnsurePlayerSupportPackAsync( - profile.target, profile.managedDeployment, m_cancellation!.Token).AsTask(); + m_gamePreparation = m_buildPipeline.EnsurePlayerSupportPackAsync(request.profile.target, request.profile.managedDeployment, m_cancellation!.Token).AsTask(); } catch (Exception exception) { @@ -193,34 +131,21 @@ internal void BeginGameExport() } } - internal void CancelGameExport() - { - status = "Canceling game build..."; - m_cancellation?.Cancel(); - } - - internal void ClosePlugin() + /// + /// Requests cancellation without releasing resources before their owned work drains. + /// + public void Cancel() { - if (isPluginBusy) - CancelPluginExport(); - isPluginVisible = false; - if (!isPluginBusy) - { - status = string.Empty; - error = string.Empty; - } + if (m_cancellation is null) + return; + status = "Canceling export..."; + m_cancellation.Cancel(); } - internal void CloseGame() + private void EnsureIdle() { - if (isGameBusy) - CancelGameExport(); - isGameVisible = false; - if (!isGameBusy) - { - status = string.Empty; - error = string.Empty; - } + if (isPluginBusy || isGameBusy) + throw new InvalidOperationException("An export still owns the build service."); } /// @@ -234,9 +159,10 @@ protected override void OnUpdate(EditorContext context) while (m_progress.TryDequeue(out BuildProgress progress)) { status = progress.message; - if (isGameProgressVisible) + if (isGameBusy) gameProgress = MathF.Max(gameProgress, (float)progress.fraction); } + CompletePluginExport(); CompleteGamePreparation(); CompleteGameExport(); @@ -250,9 +176,6 @@ protected override void OnUpdate(EditorContext context) /// protected override void OnStop(EditorContext context) { - isPluginVisible = false; - isGameVisible = false; - isGameProgressVisible = false; RetireExports(); } @@ -267,16 +190,14 @@ protected override void OnDispose() private void RetireExports() { m_cancellation?.Cancel(); - if (m_gamePreparation is { IsCompleted: false } - || m_gameExport is { IsCompleted: false } - || m_pluginExport is { IsCompleted: false }) + if (m_gamePreparation is { IsCompleted: false } || m_gameExport is { IsCompleted: false } || m_pluginExport is { IsCompleted: false }) { throw new RetirementPendingException("Canceled export work still owns build services; retry retirement after it drains."); } + RequireRetiredOutcome(m_gamePreparation); RequireRetiredOutcome(m_gameExport); RequireRetiredOutcome(m_pluginExport); - CompletePluginExport(); CompleteGameExport(); Task? preparation = m_gamePreparation; @@ -290,14 +211,18 @@ private void RetireExports() } catch (OperationCanceledException) { - // Cancellation during shutdown is expected; preparation must never start a new build here. + // Cancellation during shutdown is expected; preparation must never start a new build here. } catch (Exception exception) { m_log.Write(LogLevel.Error, "Game Support Pack preparation failed during shutdown: {0}", [exception]); } } - while (m_progress.TryDequeue(out _)) { } + + while (m_progress.TryDequeue(out _)) + { + } + ReleaseCancellation(); } @@ -321,16 +246,16 @@ private void CompletePluginExport() ReportBuildFailure("Plugin export", result); return; } - string outputPath = result.outputPath - ?? throw new InvalidOperationException("A successful Plugin build has no output path."); - string contentHash = result.contentHash - ?? throw new InvalidOperationException("A successful Plugin build has no content identity."); + + string outputPath = result.outputPath ?? throw new InvalidOperationException("A successful Plugin build has no output path."); + string contentHash = result.contentHash ?? throw new InvalidOperationException("A successful Plugin build has no content identity."); status = $"Exported {result.assetCount} source files to {outputPath}"; error = string.Empty; m_log.Write( LogLevel.Info, "Exported Plugin '{0}' to '{1}' ({2} assets, {3} embedded dependencies, {4}).", - [pluginId, outputPath, result.assetCount, result.embeddedPluginCount, contentHash]); + ["Project", outputPath, result.assetCount, result.embeddedPluginCount, contentHash] + ); } catch (OperationCanceledException) { @@ -364,21 +289,16 @@ private void CompleteGameExport() ReportBuildFailure("Game build", result); return; } - string outputPath = result.outputPath - ?? throw new InvalidOperationException("A successful game build has no output path."); + + string outputPath = result.outputPath ?? throw new InvalidOperationException("A successful game build has no output path."); status = $"Exported {result.assetCount} assets and {result.runtimeAssemblyCount} runtime assemblies to {outputPath}"; error = string.Empty; gameProgress = 1f; m_log.Write( LogLevel.Info, "Exported game to '{0}' ({1}, {2} assets, {3} artifact bundles, {4} runtime assemblies).", - [ - outputPath, - result.target?.ToString() ?? "unknown", - result.assetCount, - result.artifactBundleCount, - result.runtimeAssemblyCount - ]); + [outputPath, result.target?.ToString() ?? "unknown", result.assetCount, result.artifactBundleCount, result.runtimeAssemblyCount] + ); } catch (OperationCanceledException) { @@ -394,7 +314,6 @@ private void CompleteGameExport() } finally { - isGameProgressVisible = false; ReleaseCancellation(); } } @@ -409,12 +328,10 @@ private void CompleteGamePreparation() { using PlayerSupportPackLease prepared = preparation.GetAwaiter().GetResult(); m_cancellation!.Token.ThrowIfCancellationRequested(); - GameBuildRequest request = m_pendingGameRequest - ?? throw new InvalidOperationException("The prepared game build has no request."); + GameBuildRequest request = m_pendingGameRequest ?? throw new InvalidOperationException("The prepared game build has no request."); m_pendingGameRequest = null; status = "Capturing the authoring generation and compiling runtime scripts..."; - m_gameExport = m_buildPipeline.BuildGameAsync( - request, new BuildProgressSink(m_progress), m_cancellation.Token).AsTask(); + m_gameExport = m_buildPipeline.BuildGameAsync(request, new BuildProgressSink(m_progress), m_cancellation.Token).AsTask(); } catch (OperationCanceledException) { @@ -422,7 +339,6 @@ private void CompleteGamePreparation() status = string.Empty; error = "Game build was canceled; no partial output was installed."; m_log.Write(LogLevel.Info, "Game build was canceled while preparing its Support Pack."); - isGameProgressVisible = false; ReleaseCancellation(); } catch (Exception exception) @@ -431,36 +347,10 @@ private void CompleteGamePreparation() status = string.Empty; error = exception.Message; m_log.Write(LogLevel.Error, "Game Support Pack preparation failed: {0}", [exception]); - isGameProgressVisible = false; ReleaseCancellation(); } } - private BuildSettings LoadBuildSettings() - { - try - { - return m_buildSettings.Load(); - } - catch (Exception exception) when (exception is IOException - or InvalidDataException - or InvalidOperationException - or UnauthorizedAccessException) - { - error = exception.Message; - return m_buildSettings.defaultSettings; - } - } - - private string ResolveOutputPath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - return path; - return Path.IsPathFullyQualified(path) - ? Path.GetFullPath(path) - : Path.GetFullPath(path, m_editor.projectDirectory); - } - private void StartCancellation() { m_cancellation?.Dispose(); @@ -477,15 +367,10 @@ private void ReportBuildFailure( string operation, BuildResult result ) { - string message = string.Join( - Environment.NewLine, - result.diagnostics.Select(static diagnostic => $"[{diagnostic.code}] {diagnostic.message}")); + string message = string.Join(Environment.NewLine, result.diagnostics.Select(static diagnostic => $"[{diagnostic.code}] {diagnostic.message}")); status = string.Empty; error = message; - m_log.Write( - LogLevel.Error, - "{0} failed:{1}{2}", - [operation, Environment.NewLine, message]); + m_log.Write(LogLevel.Error, "{0} failed:{1}{2}", [operation, Environment.NewLine, message]); } private sealed class BuildProgressSink(ConcurrentQueue progress) : IProgress diff --git a/src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs b/src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs index 8ea232978..28127edb0 100644 --- a/src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs +++ b/src/composition/editor/features/Inno.Editor.PlayMode/IEditorPlayMode.cs @@ -30,9 +30,18 @@ public interface IEditorPlayMode LogSessionId activeSessionId { get; } /// - /// Occurs after changes. + /// Subscribes to committed state transitions through the Core event lifetime. /// - event Action? stateChanged; + /// + /// The observer invoked synchronously after a transition. + /// + /// + /// An independently disposable subscription; dispose it before retiring the observer. + /// + /// + /// The listener is null. + /// + IDisposable ListenStateChanges(Action listener); /// /// Requests entry after the active script generation becomes ready. diff --git a/src/composition/editor/features/Inno.Editor.PlayMode/Inno.Editor.PlayMode.csproj b/src/composition/editor/features/Inno.Editor.PlayMode/Inno.Editor.PlayMode.csproj index 4497d4f6e..b56a0b3ba 100644 --- a/src/composition/editor/features/Inno.Editor.PlayMode/Inno.Editor.PlayMode.csproj +++ b/src/composition/editor/features/Inno.Editor.PlayMode/Inno.Editor.PlayMode.csproj @@ -10,28 +10,15 @@ - - - - - + + + + + + - - + + diff --git a/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs b/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs index 661d0d31d..1d9ad489e 100644 --- a/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs +++ b/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeController.cs @@ -2,6 +2,7 @@ using Inno.Core.Execution; using Inno.Extensibility.Reload; using System; +using Inno.Core.Events; using System.Collections.Generic; using Inno.Core.Logging; @@ -30,6 +31,8 @@ public sealed class EditorPlayModeController : IEditorReloadGuard, IDisposable { + private readonly EventDispatcher m_stateDispatcher = new(); + private readonly EventHub m_stateEvents; private readonly IEditorHistoryIsolation m_history; private readonly IEditorAudioHost? m_audio; private readonly EngineHost m_engineHost; @@ -104,6 +107,7 @@ public EditorPlayModeController( m_audio = audio; ArgumentNullException.ThrowIfNull(logs); m_log = logs.CreateLogger(); + m_stateEvents = m_stateDispatcher.CreateHub(); } /// @@ -138,10 +142,23 @@ public EditorPlayModeController( /// public LogSessionId activeSessionId => m_activeSessionId; - /// - /// Occurs after the controller commits a state transition. - /// - public event Action? stateChanged; + /// + public IDisposable ListenStateChanges(Action listener) + { + ObjectDisposedException.ThrowIf(m_disposed, this); + ArgumentNullException.ThrowIfNull(listener); + return m_stateEvents.Listen(notification => + { + try + { + listener(notification.state); + } + catch (Exception failure) + { + notification.failures.Add(failure); + } + }); + } /// /// Requests a fresh script generation before preparing an isolated runtime session. @@ -262,6 +279,7 @@ public void Dispose() m_engineHost.generations.Fault(failure); throw; } + m_stateEvents.Dispose(); m_disposed = true; } @@ -497,24 +515,16 @@ private void SetState(EditorPlayModeState value) if (m_state == value) return; m_state = value; - Action? handlers = stateChanged; - if (handlers is null) - return; - List? failures = null; - foreach (Action handler in handlers.GetInvocationList()) - { - try - { - handler(value); - } - catch (Exception exception) - { - failures ??= []; - failures.Add(exception); - } - } - if (failures is not null) - throw new AggregateException("One or more Play Mode state observers failed.", failures); + var notification = new StateTransitionEvent(value); + m_stateDispatcher.Emit(notification); + if (notification.failures.Count != 0) + throw new AggregateException("One or more Play Mode state observers failed.", notification.failures); + } + + private sealed class StateTransitionEvent(EditorPlayModeState state) : Event + { + internal EditorPlayModeState state { get; } = state; + internal List failures { get; } = []; } private static void DisposeResource( diff --git a/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeModule.cs b/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeModule.cs index 9825c3d60..500a3fe98 100644 --- a/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeModule.cs +++ b/src/composition/editor/features/Inno.Editor.PlayMode/Runtime/EditorPlayModeModule.cs @@ -67,14 +67,8 @@ public override bool blocksFollowingUpdates /// public LogSessionId activeSessionId => m_controller.activeSessionId; - /// - /// Occurs after the Play Mode transition state changes. - /// - public event Action? stateChanged - { - add => m_controller.stateChanged += value; - remove => m_controller.stateChanged -= value; - } + /// + public IDisposable ListenStateChanges(Action listener) => m_controller.ListenStateChanges(listener); /// /// Requests entry after the active script generation becomes ready. diff --git a/src/composition/editor/features/Inno.Editor.Rendering/Inno.Editor.Rendering.csproj b/src/composition/editor/features/Inno.Editor.Rendering/Inno.Editor.Rendering.csproj index 2d275907a..802541231 100644 --- a/src/composition/editor/features/Inno.Editor.Rendering/Inno.Editor.Rendering.csproj +++ b/src/composition/editor/features/Inno.Editor.Rendering/Inno.Editor.Rendering.csproj @@ -10,44 +10,28 @@ - - - - - - - + + - - - - - - - - + + - - - + + + + + + + + + + - diff --git a/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs b/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs index c99dd9667..55c7eb5e6 100644 --- a/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs +++ b/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineDocuments.cs @@ -11,7 +11,6 @@ using Inno.Rendering.Assets; namespace Inno.Editor.Rendering; - /// /// Edits Pipeline sources and typed extension settings without changing canonical rendering assets before Save. /// @@ -20,9 +19,23 @@ public sealed class PipelineDocuments : EditorModule { internal const string C_HISTORY = "inno.pipeline/draft"; private readonly TypeCatalog m_types; - internal readonly AssetPipeline assets; - internal readonly SerializationRegistry serialization; - internal readonly AssetDraftDocuments drafts; + /// + /// Gets the authoritative asset pipeline used by source inspectors. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetPipeline assets { get; } + + /// + /// Gets the current owner serialization registry. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public SerializationRegistry serialization { get; } + + /// + /// Gets the source draft owner shared by Pipeline inspectors. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetDraftDocuments drafts { get; } internal PipelineDocuments( AssetPipeline assets, @@ -30,7 +43,9 @@ internal PipelineDocuments( TypeCatalog types, EditorInteractions interactions ) { - this.assets = assets; this.serialization = serialization; m_types = types; + this.assets = assets; + this.serialization = serialization; + m_types = types; drafts = new(assets, serialization, interactions, "inno.pipeline", C_HISTORY, ".irenderpipeline", "Pipeline"); } @@ -44,7 +59,6 @@ EditorInteractions interactions /// The persistent source identity used by draft operations. /// public Guid Open(AssetPath path) => drafts.Open(path); - /// /// Reads a detached current-generation Pipeline value; its referenced canonical assets must not be mutated. /// @@ -55,7 +69,6 @@ EditorInteractions interactions /// The unsaved native Pipeline value. /// public RenderPipelineAsset Read(Guid assetId) => drafts.Read(assetId); - /// /// Changes a draft through shared History without publishing it to Scene/Game. /// @@ -65,24 +78,14 @@ EditorInteractions interactions /// /// Detached edited value. /// - /// - /// Whether this sample completes the gesture. + /// + /// The current gesture key, or null for a discrete edit. /// public void Replace( Guid assetId, RenderPipelineAsset candidate, - bool finishGesture = true - ) - => drafts.Replace(assetId, candidate, finishGesture); - - /// - /// Completes one gesture without saving the source. - /// - /// - /// Open Pipeline identity. - /// - public void Commit(Guid assetId) => drafts.Commit(assetId); - + string? mergeKey = null + ) => drafts.Replace(assetId, candidate, mergeKey); /// /// Captures typed Pipeline settings with this source owner's complete reference and dependency context. /// @@ -95,22 +98,32 @@ public void Replace( /// /// Detached edited settings. /// - /// - /// Whether this sample completes the gesture. + /// + /// The current gesture key, or null for a discrete edit. /// public void ReplaceSettings( Guid assetId, TSettings settings, - bool finishGesture = true + string? mergeKey = null ) where TSettings : class, ISerializable { RenderPipelineAsset pipeline = Read(assetId); pipeline.pipelineState = new SerializedRenderExtensionState(assets.CaptureProperties(settings)); - Replace(assetId, pipeline, finishGesture); + Replace(assetId, pipeline, mergeKey); } - internal ISerializable? RestoreSettings(SerializedRenderExtensionState state) + /// + /// Restores detached settings using the current type and serialization generation. + /// + /// + /// Neutral settings retaining their stable type identity and properties. + /// + /// + /// The detached settings, or null when the extension is temporarily unavailable. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public ISerializable? RestoreSettings(SerializedRenderExtensionState state) { if (state.stableTypeId == Guid.Empty) return null; @@ -123,7 +136,6 @@ public void ReplaceSettings( internal static bool Recoverable(Exception error) => error is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException or FormatException && Inno.Core.Execution.RetirementPendingException.Find(error) is null; - /// /// Initializes this feature when its owning runtime becomes active. /// @@ -165,11 +177,11 @@ internal sealed class PipelineDraftHistory(PipelineDocuments documents) : Editor /// /// The validated editor history availability that represents the completed operation. /// -protected override EditorHistoryAvailability Query( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryAvailability Query( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { documents.drafts.ValidateHistory(change, direction); @@ -180,6 +192,7 @@ EditorHistoryDirection direction return EditorHistoryAvailability.Unavailable(error.Message); } } + /// /// Applies a validated change atomically at the caller-controlled commit point. /// @@ -195,11 +208,11 @@ EditorHistoryDirection direction /// /// The validated editor history result that represents the completed operation. /// -protected override EditorHistoryResult Apply( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryResult Apply( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { documents.drafts.ApplyHistory(change, direction); @@ -210,4 +223,11 @@ EditorHistoryDirection direction return EditorHistoryResult.Failure(error.Message); } } + + /// + protected override bool TryMerge( + EditorHistoryChange older, + EditorHistoryChange newer, + out EditorHistoryChange? merged + ) => documents.drafts.TryMergeHistory(older, newer, out merged); } diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneDiagnosticPublisher.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneDiagnosticPublisher.cs index 168d64e1c..6a0fe268e 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneDiagnosticPublisher.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneDiagnosticPublisher.cs @@ -61,6 +61,20 @@ Exception exception return true; } + internal void PublishSourceUnavailable( + GameScene scene, + string sourcePath + ) { + Guid sceneId = scene.identity.persistentId; + string state = "SCENE-SOURCE-MISSING:" + sourcePath; + if (m_synchronizationStates.TryGetValue(sceneId, out string? previous) && + string.Equals(previous, state, StringComparison.Ordinal)) + return; + Diagnostics.Set(sceneId, C_SYNCHRONIZATION_GROUP, + Diagnostic.Warning("SCENE-SOURCE-MISSING", $"Scene source '{sourcePath}' is unavailable; its assignment and current edits are preserved."), scene.name); + m_synchronizationStates[sceneId] = state; + } + internal void ResolveSynchronization(Guid sceneId) { if (!m_synchronizationStates.Remove(sceneId)) diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Persistence.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Persistence.cs index 29d9c7476..16cff4faa 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Persistence.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Persistence.cs @@ -48,14 +48,15 @@ public bool IsDirty(GameScene scene) SceneDocument document = GetOrCreateDocument(scene); try { - SynchronizeSource(scene, document); - m_diagnostics.ResolveSynchronization(scene.identity.persistentId); + if (!SynchronizeSource(scene, document)) + return true; } catch (Exception exception) { document.isDirty = true; if (m_diagnostics.PublishSynchronizationFailure(scene, exception)) m_log.Write(LogLevel.Error, "Scene document synchronization failed: {0}", [exception]); + return true; } if (string.IsNullOrEmpty(document.sourcePath)) return true; @@ -260,6 +261,10 @@ private void SaveSceneAtPath( GameScene scene, string relativePath ) { + SceneDocument document = GetOrCreateDocument(scene); + if (document.sourceAssetId != Guid.Empty && + string.Equals(document.sourcePath, relativePath, StringComparison.Ordinal) && !SynchronizeSource(scene, document)) + throw new InvalidOperationException($"Scene source '{relativePath}' is unavailable. Restore its source or explicitly save to another directory."); scene.name = GetAssetName(relativePath); bool exists = m_assets.TryLoad(AssetPath.Parse(relativePath), out SceneAsset? sceneAsset); sceneAsset ??= new SceneAsset(); @@ -271,7 +276,6 @@ string relativePath throw new InvalidOperationException($"No asset importer could save scene '{relativePath}'."); byte[] savedHash = ComputeSceneHash(scene); - SceneDocument document = GetOrCreateDocument(scene); document.sourcePath = relativePath; document.sourceAssetId = sceneAsset.identity.persistentId; document.savedHash = savedHash; @@ -302,19 +306,33 @@ private void EnsureCanPersist() } } - private byte[] ComputeSceneHash(GameScene scene) - { + private byte[] ComputeSceneHash( + GameScene scene, + SceneDocument? document = null + ) { var dependencies = new AssetDependencyCollection(includeLastKnownPaths: false); SerializationContext context = AssetSerializationContext.Create(m_assets, dependencies); - byte[] payload = m_serialization.Serialize(scene, context); - return SHA256.HashData(payload); + SerializationSnapshot captured = m_serialization.CaptureSnapshot(scene, context, document?.observedSnapshot); + if (document is not null && ReferenceEquals(captured, document.observedSnapshot)) + return document.observedHash!; + using var algorithm = SHA256.Create(); + using (var digest = new CryptoStream(Stream.Null, algorithm, CryptoStreamMode.Write, leaveOpen: true)) + using (var output = new BufferedStream(digest, 64 * 1024)) + captured.WriteTo(output); + byte[] hash = algorithm.Hash ?? throw new InvalidOperationException("Scene content hashing did not complete."); + if (document is not null) + { + document.observedSnapshot = captured; + document.observedHash = hash; + } + return hash; } private bool HasSerializedChanges( GameScene scene, SceneDocument document ) - => !ComputeSceneHash(scene).AsSpan().SequenceEqual(document.savedHash); + => !ComputeSceneHash(scene, document).AsSpan().SequenceEqual(document.savedHash); private string CreateUniquePath( string directory, @@ -376,6 +394,9 @@ private sealed class SceneDocument( Guid sourceAssetId, byte[] savedHash ) { + internal SerializationSnapshot? observedSnapshot; + internal byte[]? observedHash; + /// /// The scene value used as part of this type's public representation. /// diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.PlayMode.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.PlayMode.cs index cb6512da9..412083e54 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.PlayMode.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.PlayMode.cs @@ -57,12 +57,28 @@ IDisposable IEditorScenePlayMode.BeginPlayMode(RuntimeSession runtimeSession) runtimeSession, snapshots, activeSceneId, - selectedSceneObjectId); - MaterializeRuntimeSceneSet(session); - m_playModeSession = session; - if (selectedSceneObjectId is not null) - RestoreSelection(selectedSceneObjectId); - return session; + selectedSceneObjectId, + m_selection?.RegisterIdentityDomain(runtimeSession.identities)); + try + { + MaterializeRuntimeSceneSet(session); + m_playModeSession = session; + if (selectedSceneObjectId is not null) + RestoreSelection(selectedSceneObjectId); + return session; + } + catch (Exception startupFailure) + { + try + { + session.Dispose(); + } + catch (Exception cleanupFailure) + { + throw new AggregateException("Play scene preparation and identity routing retirement failed.", startupFailure, cleanupFailure); + } + throw; + } } finally { @@ -83,8 +99,16 @@ private void ReleasePlayModeLease(PlayModeLease session) m_runtimeSession.scenes.Find(currentId) is not null ? currentId : session.editSelectionId; - if (runtimeSelection is EngineObject || runtimeSelection is null && session.editSelectionId is not null) - RestoreSelection(editSelectionId, selectActiveSceneWhenMissing: false); + try + { + if (runtimeSelection is EngineObject || runtimeSelection is null && session.editSelectionId is not null) + RestoreSelection(editSelectionId, selectActiveSceneWhenMissing: false); + } + catch + { + m_playModeSession = session; + throw; + } } private void MaterializeRuntimeSceneSet(PlayModeLease session) @@ -137,7 +161,8 @@ private sealed class PlayModeLease( RuntimeSession playSession, SceneDocumentSnapshot[] sceneSnapshots, Guid? activeScene, - Guid? selectedSceneObject + Guid? selectedSceneObject, + IDisposable? identityDomain ) : IDisposable { private readonly Dictionary m_snapshotBySceneId = @@ -162,8 +187,9 @@ public void Dispose() { if (m_disposed) return; - m_disposed = true; workspace.ReleasePlayModeLease(this); + identityDomain?.Dispose(); + m_disposed = true; } internal void Capture(EditorState state) diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Reload.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Reload.cs index 24ff262d6..44924bff3 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Reload.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.Reload.cs @@ -44,8 +44,8 @@ IGenerationChange IEditorReloadParticipant.Capture(AssemblyReloadContext context GameScene scene = document.scene; if (scene.isDestroyed) continue; - SynchronizeSource(scene, document); - bool wasDirty = string.IsNullOrEmpty(document.sourcePath) || + bool sourceAvailable = SynchronizeSource(scene, document); + bool wasDirty = !sourceAvailable || string.IsNullOrEmpty(document.sourcePath) || !string.Equals( scene.name, GetAssetName(document.sourcePath), diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.SourceChanges.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.SourceChanges.cs index b13940e55..c97c7bc70 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.SourceChanges.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.SourceChanges.cs @@ -27,27 +27,30 @@ internal sealed partial class EditorSceneWorkspace { - private void SynchronizeSource( + private bool SynchronizeSource( GameScene scene, SceneDocument document ) { if (document.sourceAssetId == Guid.Empty) - return; - if (!m_assets.TryLoad(document.sourceAssetId, out SceneAsset? asset) || + { + m_diagnostics.ResolveSynchronization(scene.identity.persistentId); + return true; + } + if (!m_assets.TryGetInfo(document.sourceAssetId, out AssetInfo? asset) || asset is null || - asset.isMissing) + asset.status == AssetImportStatus.Missing) { - document.sourcePath = string.Empty; - document.sourceAssetId = Guid.Empty; document.isDirty = true; - return; + m_diagnostics.PublishSourceUnavailable(scene, document.sourcePath); + return false; } + m_diagnostics.ResolveSynchronization(scene.identity.persistentId); string sourcePath = NormalizePath(asset.assetPath.ToString()); string sourceName = GetAssetName(sourcePath); bool pathChanged = !string.Equals(document.sourcePath, sourcePath, StringComparison.Ordinal); if (!pathChanged) - return; + return true; bool wasDirty = HasSerializedChanges(scene, document); document.sourcePath = sourcePath; @@ -57,6 +60,7 @@ asset is null || document.isDirty = wasDirty; document.nextRefreshTimestamp = Stopwatch.GetTimestamp() + (long)(Stopwatch.Frequency * C_DIRTY_REFRESH_SECONDS); + return true; } private void ApplyRename( diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.cs index cf13011c0..7cb5304de 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/EditorSceneWorkspace.cs @@ -241,7 +241,6 @@ internal void Refresh() try { SynchronizeSource(document.scene, document); - m_diagnostics.ResolveSynchronization(sceneId); } catch (Exception exception) { diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs index a6bbba80c..3e4e8e3dc 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/IEditorSceneWorkspace.cs @@ -89,7 +89,7 @@ public interface IEditorSceneWorkspace /// The saved source-relative path. /// /// - /// Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. + /// Thrown while Play Mode runtime copies are active, when the asset cannot be persisted, or when saving would overwrite its unavailable assigned source. /// string Save( GameScene scene, @@ -109,7 +109,7 @@ string currentDirectory /// The saved source-relative path. /// /// - /// Thrown while Play Mode runtime copies are active or when the asset cannot be persisted. + /// Thrown while Play Mode runtime copies are active, when the asset cannot be persisted, or when saving would overwrite its unavailable assigned source. /// string SaveToDirectory( GameScene scene, @@ -143,10 +143,10 @@ string currentDirectory /// The scene whose path is requested. /// /// - /// The saved path when available. + /// The assigned source-relative path, including a temporarily unavailable source. /// /// - /// when the scene is backed by a scene asset. + /// when a source remains assigned; availability is diagnosed separately. /// bool TryGetSourcePath( GameScene scene, diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs index 264d385a9..1806ec590 100644 --- a/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Elements.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Linq; - using Inno.Extensibility.Types; using Inno.Core.Serialization; using Inno.Editor.Core; @@ -12,7 +11,6 @@ using Inno.Scene.Layers; namespace Inno.Editor.Scene; - sealed partial class SceneEdits { /// @@ -43,10 +41,7 @@ public GameComponent AddComponent( try { TypeRef typeRef = GetTypeRef(componentType); - byte[] state = SceneElementSerialization.CaptureState( - component, - m_workspace.serialization, - m_workspace.assets); + byte[] state = SceneElementSerialization.CaptureState(component, m_workspace.serialization, m_workspace.assets); RecordElement( historyName ?? $"Add {componentType.Name}", new SceneElementHistoryData( @@ -61,7 +56,9 @@ public GameComponent AddComponent( existsAfter: true, beforeState: [], afterState: state, - incomingReferences: [])); + incomingReferences: [] + ) + ); return component; } catch (Exception exception) @@ -99,14 +96,8 @@ public bool RemoveComponent( GameObject owner = component.gameObject; GameScene scene = owner.scene; TypeRef typeRef = GetElementType(component); - byte[] state = SceneElementSerialization.CaptureState( - component, - m_workspace.serialization, - m_workspace.assets); - SceneIncomingReferenceState[] incoming = SceneReferenceIndex.CaptureIncoming( - component, - scene, - m_workspace); + byte[] state = SceneElementSerialization.CaptureState(component, m_workspace.serialization, m_workspace.assets); + SceneIncomingReferenceState[] incoming = SceneReferenceIndex.CaptureIncoming(component, scene, m_workspace); int index = owner.GetComponentIndex(component); Guid componentId = component.identity.persistentId; Type componentType = component.GetType(); @@ -128,7 +119,9 @@ public bool RemoveComponent( existsAfter: false, beforeState: state, afterState: [], - incoming)); + incoming + ) + ); } catch (Exception exception) { @@ -136,20 +129,15 @@ public bool RemoveComponent( { ExceptionDispatchInfo.Capture(exception).Throw(); } + RollbackAndRethrow(exception, () => { - GameComponent restored = SceneElementSerialization.RestoreComponent( - owner, - typeRef, - componentId, - index, - state, - m_workspace.serialization, - m_workspace.assets); + GameComponent restored = SceneElementSerialization.RestoreComponent(owner, typeRef, componentId, index, state, m_workspace.serialization, m_workspace.assets); RequireElementRestore(restored, state); RequireReferenceRestore(SceneReferenceIndex.RestoreIncoming(incoming, m_workspace)); }); } + return true; } @@ -170,24 +158,19 @@ public void ResetComponent( using IDisposable presentationScope = m_workspace.EnterPresentationScope(); m_workspace.EnsureEditable(component.gameObject.scene); GameObject owner = component.gameObject; - byte[] before = SceneElementSerialization.CaptureState( - component, - m_workspace.serialization, - m_workspace.assets); + byte[] before = SceneElementSerialization.CaptureState(component, m_workspace.serialization, m_workspace.assets); byte[] after; try { owner.ResetComponent(component); - after = SceneElementSerialization.CaptureState( - component, - m_workspace.serialization, - m_workspace.assets); + after = SceneElementSerialization.CaptureState(component, m_workspace.serialization, m_workspace.assets); } catch (Exception exception) { RollbackAndRethrow(exception, () => RequireElementRestore(component, before)); throw; } + if (before.AsSpan().SequenceEqual(after)) return; int index = owner.GetComponentIndex(component); @@ -195,19 +178,22 @@ public void ResetComponent( () => RecordElement( historyName ?? $"Reset {component.GetType().Name}", new SceneElementHistoryData( - SceneElementKind.Component, - owner.scene.identity.persistentId, - owner.identity.persistentId, - component.identity.persistentId, - GetElementType(component), - index, - index, - existsBefore: true, - existsAfter: true, - before, - after, - incomingReferences: [])), - () => RequireElementRestore(component, before)); + SceneElementKind.Component, + owner.scene.identity.persistentId, + owner.identity.persistentId, + component.identity.persistentId, + GetElementType(component), + index, + index, + existsBefore: true, + existsAfter: true, + before, + after, + incomingReferences: [] + ) + ), + () => RequireElementRestore(component, before) + ); } /// @@ -244,25 +230,29 @@ public void SetComponentIndex( RollbackAndRethrow(exception, () => owner.SetComponentIndex(component, beforeIndex)); throw; } + if (beforeIndex == afterIndex) return; RecordWithRollback( () => RecordElement( historyName, new SceneElementHistoryData( - SceneElementKind.Component, - owner.scene.identity.persistentId, - owner.identity.persistentId, - component.identity.persistentId, - GetElementType(component), - beforeIndex, - afterIndex, - existsBefore: true, - existsAfter: true, - beforeState: [], - afterState: [], - incomingReferences: [])), - () => owner.SetComponentIndex(component, beforeIndex)); + SceneElementKind.Component, + owner.scene.identity.persistentId, + owner.identity.persistentId, + component.identity.persistentId, + GetElementType(component), + beforeIndex, + afterIndex, + existsBefore: true, + existsAfter: true, + beforeState: [], + afterState: [], + incomingReferences: [] + ) + ), + () => owner.SetComponentIndex(component, beforeIndex) + ); } /// @@ -305,11 +295,10 @@ public GameSystem AddSystem( existsBefore: false, existsAfter: true, beforeState: [], - afterState: SceneElementSerialization.CaptureState( - system, - m_workspace.serialization, - m_workspace.assets), - incomingReferences: [])); + afterState: SceneElementSerialization.CaptureState(system, m_workspace.serialization, m_workspace.assets), + incomingReferences: [] + ) + ); return system; } catch (Exception exception) @@ -326,9 +315,6 @@ public GameSystem AddSystem( /// /// Removes the system from its scene and records a reversible serialized history change. /// - /// - /// The scene currently owning the system. - /// /// /// The registered system to remove. /// @@ -339,24 +325,19 @@ public GameSystem AddSystem( /// when the system was removed and recorded. /// public bool RemoveSystem( - GameScene scene, GameSystem system, string? historyName = null ) { - ArgumentNullException.ThrowIfNull(scene); ArgumentNullException.ThrowIfNull(system); + if (system.isDestroyed) + return false; + GameScene scene = ResolveOwnerScene(system); using IDisposable presentationScope = m_workspace.EnterPresentationScope(); m_workspace.EnsureEditable(scene); if (system.isDestroyed) return false; - byte[] state = SceneElementSerialization.CaptureState( - system, - m_workspace.serialization, - m_workspace.assets); - SceneIncomingReferenceState[] incoming = SceneReferenceIndex.CaptureIncoming( - system, - scene, - m_workspace); + byte[] state = SceneElementSerialization.CaptureState(system, m_workspace.serialization, m_workspace.assets); + SceneIncomingReferenceState[] incoming = SceneReferenceIndex.CaptureIncoming(system, scene, m_workspace); int index = scene.GetSystemIndex(system); Guid systemId = system.identity.persistentId; TypeRef typeRef = GetElementType(system); @@ -368,18 +349,20 @@ public bool RemoveSystem( RecordElement( historyName ?? $"Remove {systemType.Name}", new SceneElementHistoryData( - SceneElementKind.System, - scene.identity.persistentId, - Guid.Empty, - systemId, - typeRef, - beforeIndex: index, - afterIndex: -1, - existsBefore: true, - existsAfter: false, - beforeState: state, + SceneElementKind.System, + scene.identity.persistentId, + Guid.Empty, + systemId, + typeRef, + beforeIndex: index, + afterIndex: -1, + existsBefore: true, + existsAfter: false, + beforeState: state, afterState: [], - incoming)); + incoming + ) + ); } catch (Exception exception) { @@ -387,29 +370,21 @@ public bool RemoveSystem( { ExceptionDispatchInfo.Capture(exception).Throw(); } + RollbackAndRethrow(exception, () => { - GameSystem restored = SceneElementSerialization.RestoreSystem( - scene, - typeRef, - systemId, - index, - state, - m_workspace.serialization, - m_workspace.assets); + GameSystem restored = SceneElementSerialization.RestoreSystem(scene, typeRef, systemId, index, state, m_workspace.serialization, m_workspace.assets); RequireElementRestore(restored, state); RequireReferenceRestore(SceneReferenceIndex.RestoreIncoming(incoming, m_workspace)); }); } + return true; } /// /// Resets one scene system and records its compact property state before and after Reset. /// - /// - /// The scene currently owning the system. - /// /// /// The registered system to reset. /// @@ -417,32 +392,26 @@ public bool RemoveSystem( /// An optional user-facing history entry name. /// public void ResetSystem( - GameScene scene, GameSystem system, string? historyName = null ) { - ArgumentNullException.ThrowIfNull(scene); ArgumentNullException.ThrowIfNull(system); + GameScene scene = ResolveOwnerScene(system); using IDisposable presentationScope = m_workspace.EnterPresentationScope(); m_workspace.EnsureEditable(scene); - byte[] before = SceneElementSerialization.CaptureState( - system, - m_workspace.serialization, - m_workspace.assets); + byte[] before = SceneElementSerialization.CaptureState(system, m_workspace.serialization, m_workspace.assets); byte[] after; try { scene.ResetSystem(system); - after = SceneElementSerialization.CaptureState( - system, - m_workspace.serialization, - m_workspace.assets); + after = SceneElementSerialization.CaptureState(system, m_workspace.serialization, m_workspace.assets); } catch (Exception exception) { RollbackAndRethrow(exception, () => RequireElementRestore(system, before)); throw; } + if (before.AsSpan().SequenceEqual(after)) return; int index = scene.GetSystemIndex(system); @@ -450,19 +419,22 @@ public void ResetSystem( () => RecordElement( historyName ?? $"Reset {system.GetType().Name}", new SceneElementHistoryData( - SceneElementKind.System, - scene.identity.persistentId, - Guid.Empty, - system.identity.persistentId, - GetElementType(system), - index, - index, - existsBefore: true, - existsAfter: true, - before, - after, - incomingReferences: [])), - () => RequireElementRestore(system, before)); + SceneElementKind.System, + scene.identity.persistentId, + Guid.Empty, + system.identity.persistentId, + GetElementType(system), + index, + index, + existsBefore: true, + existsAfter: true, + before, + after, + incomingReferences: [] + ) + ), + () => RequireElementRestore(system, before) + ); } /// @@ -503,39 +475,38 @@ public void SetSystemIndex( RollbackAndRethrow(exception, () => scene.SetSystemIndex(system, beforeIndex)); throw; } + if (beforeIndex == afterIndex) return; RecordWithRollback( () => RecordElement( historyName, new SceneElementHistoryData( - SceneElementKind.System, - scene.identity.persistentId, - Guid.Empty, - system.identity.persistentId, - GetElementType(system), - beforeIndex, - afterIndex, - existsBefore: true, - existsAfter: true, - beforeState: [], - afterState: [], - incomingReferences: [])), - () => scene.SetSystemIndex(system, beforeIndex)); + SceneElementKind.System, + scene.identity.persistentId, + Guid.Empty, + system.identity.persistentId, + GetElementType(system), + beforeIndex, + afterIndex, + existsBefore: true, + existsAfter: true, + beforeState: [], + afterState: [], + incomingReferences: [] + ) + ), + () => scene.SetSystemIndex(system, beforeIndex) + ); } - private TypeRef GetElementType(EngineObject element) - => element switch - { - MissingGameComponent missing => missing.missingType, - MissingGameSystem missing => missing.missingType, - _ => GetTypeRef(element.GetType()) - }; - + private TypeRef GetElementType(EngineObject element) => element switch + { + MissingGameComponent missing => missing.missingType, + MissingGameSystem missing => missing.missingType, + _ => GetTypeRef(element.GetType())}; private void RequireElementRestore( EngineObject element, ReadOnlySpan data - ) - => SceneElementSerialization.RestoreState(element, data, m_workspace.serialization, m_workspace.assets); - + ) => SceneElementSerialization.RestoreState(element, data, m_workspace.serialization, m_workspace.assets); } diff --git a/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs b/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs new file mode 100644 index 000000000..0443166d4 --- /dev/null +++ b/src/composition/editor/features/Inno.Editor.Scene/Documents/SceneEdits.Selection.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Inno.Core.Mathematics; +using Inno.Core.Serialization; +using Inno.Editor.Interactions; +using Inno.Scene; + +namespace Inno.Editor.Scene; +sealed partial class SceneEdits +{ + /// + /// Deletes selected subtrees atomically, omitting descendants already covered by a selected ancestor. + /// + /// + /// Live objects to delete; every owner is validated before the first mutation. + /// + /// + /// An owner is unavailable, read-only, or rollback fails. + /// + public void DeleteGameObjects(IReadOnlyList targets) + { + ArgumentNullException.ThrowIfNull(targets); + GameObject[] unique = targets.Distinct().ToArray(); + foreach (GameObject target in unique) + { + ArgumentNullException.ThrowIfNull(target); + m_workspace.EnsureEditable(target.scene); + if (!target.isRuntimeValid) + throw new InvalidOperationException("A selected GameObject is no longer available."); + } + + var selected = unique.Select(value => value.identity.persistentId).ToHashSet(); + GameObject[] roots = unique.Where(value => !HasSelectedAncestor(value, selected)).ToArray(); + using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction("Delete GameObjects"); + foreach (GameObject target in roots) + _ = DeleteGameObject(target); + transaction.Commit(); + } + + /// + /// Applies one primary property edit to a selection using owner-resolved serialized values and one atomic History transaction. + /// + /// + /// Ordered compatible scene objects; the first object supplies the edited value. + /// + /// + /// The common root serialized property. + /// + /// + /// Changes the primary value synchronously; no callback is retained. + /// + /// + /// The user-facing batch edit name. + /// + /// + /// An optional active gesture identity; null keeps discrete edits separate. + /// + /// + /// An optional public scalar value component; Euler axes use euler.x, euler.y or euler.z. + /// + /// + /// True when at least one object changed; false when all values already matched. + /// + /// + /// The selection is empty or a required common property is absent. + /// + /// + /// An owner is unavailable, read-only, or rollback fails. + /// + public bool ChangeProperties( + IReadOnlyList targets, + string propertyName, + Action mutation, + string historyName, + string? mergeKey = null, + string? componentName = null + ) { + ArgumentNullException.ThrowIfNull(targets); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + ArgumentNullException.ThrowIfNull(mutation); + ArgumentException.ThrowIfNullOrWhiteSpace(historyName); + if (targets.Count == 0) + throw new ArgumentException("A property edit requires at least one target.", nameof(targets)); + foreach (EngineObject target in targets) + { + ArgumentNullException.ThrowIfNull(target); + m_workspace.EnsureEditable(ResolveOwnerScene(target)); + if (!ScenePropertySerialization.CapturePropertySnapshots(target, m_workspace.serialization, m_workspace.assets).Any(value => value.name == propertyName)) + throw new ArgumentException($"Selected target has no serialized property '{propertyName}'.", nameof(targets)); + } + + if (componentName is not null) + foreach (EngineObject target in targets) + ValidateComponent(GetSelectionProperty(target, propertyName).propertyType, componentName); + using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction(historyName); + bool changed = ChangeProperty(targets[0], propertyName, mutation, historyName, mergeKey); + SerializationPropertySnapshot snapshot = ScenePropertySerialization.CapturePropertySnapshots( + targets[0], + m_workspace.serialization, + m_workspace.assets + ).Single(value => value.name == propertyName); + byte[] value = m_workspace.serialization.EncodePropertySnapshots([snapshot]); + for (int index = 1; index < targets.Count; index++) + { + EngineObject target = targets[index]; + changed |= ChangeProperty(target, propertyName, () => + { + if (componentName is null) + RequirePropertyRestore(target, value); + else + { + SerializedProperty property = GetSelectionProperty(target, propertyName); + object primary = GetSelectionProperty(targets[0], propertyName).GetValue()!; + object current = property.GetValue() ?? Activator.CreateInstance(Nullable.GetUnderlyingType(property.propertyType) ?? property.propertyType)!; + property.SetValue(CopyComponent(primary, current, componentName)); + } + }, historyName, mergeKey); + } + + transaction.Commit(); + return changed; + } + + /// + /// Compares common serialized property values using the complete owner serialization context. + /// + /// + /// Live compatible scene objects to compare. + /// + /// + /// The required common serialized member. + /// + /// + /// True when every encoded value matches, including an empty selection. + /// + /// + /// A selected object does not expose the requested property. + /// + public bool HaveSamePropertyValues( + IReadOnlyList targets, + string propertyName + ) { + ArgumentNullException.ThrowIfNull(targets); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + SerializationPropertySnapshot? first = null; + foreach (EngineObject target in targets) + { + SerializationPropertySnapshot current = ScenePropertySerialization.CapturePropertySnapshots( + target, + m_workspace.serialization, + m_workspace.assets + ).Single(value => value.name == propertyName); + if (first is not null && !first.data.Span.SequenceEqual(current.data.Span)) + return false; + first = current; + } + + return true; + } + + /// + /// Moves minimal selected roots together, preserving descendants and recording only affected placements. + /// + /// + /// Selected live objects in their required relative order. + /// + /// + /// The loaded editable destination scene. + /// + /// + /// The new parent, or null for scene roots. + /// + /// + /// The insertion index after removing the selected roots; the default appends them. + /// + /// + /// An owner is read-only, unavailable, or the move would form a cycle. + /// + /// + /// The parent belongs to a different scene. + /// + public void MoveGameObjects( + IReadOnlyList targets, + GameScene destination, + GameObject? parent = null, + int insertionIndex = int.MaxValue + ) { + ArgumentNullException.ThrowIfNull(targets); + ArgumentNullException.ThrowIfNull(destination); + m_workspace.EnsureEditable(destination); + if (parent is not null && !ReferenceEquals(parent.scene, destination)) + throw new ArgumentException("The parent must belong to the destination scene.", nameof(parent)); + GameObject[] unique = targets.Distinct().ToArray(); + foreach (GameObject target in unique) + { + ArgumentNullException.ThrowIfNull(target); + m_workspace.EnsureEditable(target.scene); + if (!target.isRuntimeValid) + throw new InvalidOperationException("A selected GameObject is no longer available."); + } + + var selected = unique.Select(value => value.identity.persistentId).ToHashSet(); + GameObject[] roots = unique.Where(value => !HasSelectedAncestor(value, selected)).ToArray(); + if (roots.Length == 0) + return; + for (var ancestor = parent?.transform; ancestor is not null; ancestor = ancestor.parent) + if (selected.Contains(ancestor.gameObject.identity.persistentId)) + throw new InvalidOperationException("A selection cannot be moved beneath itself."); + _ = ChangeHierarchy(roots[0], hierarchy => + { + foreach (GameObject target in roots) + { + if (!ReferenceEquals(target.scene, destination)) + hierarchy.MoveToScene(target, destination); + target.transform.SetParent(parent?.transform); + target.transform.SetSiblingIndex(int.MaxValue); + } + + int available = parent is null ? destination.GetObjects().Count(value => value.transform.parent is null) - roots.Length : parent.transform.children.Count - roots.Length; + int start = Math.Clamp(insertionIndex, 0, available); + // Place from the end so intermediate sibling shifts cannot reverse the selection. + for (int index = roots.Length - 1; index >= 0; index--) + roots[index].transform.SetSiblingIndex(start); + }, "Move GameObjects", roots); + } + + private SerializedProperty GetSelectionProperty( + EngineObject target, + string propertyName + ) => m_workspace.serialization.GetProperties((ISerializable)target).Single(value => value.name == propertyName); + + private static void ValidateComponent( + Type type, + string componentName + ) { + type = Nullable.GetUnderlyingType(type) ?? type; + if (type == typeof(Quaternion) && componentName is "euler.x" or "euler.y" or "euler.z") + return; + FieldInfo? field = type.GetField(componentName, BindingFlags.Instance | BindingFlags.Public); + if (!type.IsValueType || field is null || field.IsInitOnly || !field.FieldType.IsPrimitive) + throw new ArgumentException("A batch component must be a writable public scalar value field.", nameof(componentName)); + } + + private static object CopyComponent( + object primary, + object current, + string componentName + ) { + if (primary is Quaternion edited && current is Quaternion existing && componentName.StartsWith("euler.", StringComparison.Ordinal)) + { + Vector3 next = edited.ToEulerAnglesXYZDegrees(); + Vector3 value = existing.ToEulerAnglesXYZDegrees(); + switch (componentName) + { + case "euler.x": value.x = next.x; break; + case "euler.y": value.y = next.y; break; + case "euler.z": value.z = next.z; break; + } + return Quaternion.FromEulerAnglesXYZDegrees(value).normalized; + } + // Only the current public value is boxed and edited; no private state or plugin metadata is retained. + FieldInfo field = primary.GetType().GetField(componentName, BindingFlags.Instance | BindingFlags.Public)!; + field.SetValue(current, field.GetValue(primary)); + return current; + } + + private static bool HasSelectedAncestor( + GameObject target, + ISet selected + ) { + for (var parent = target.transform.parent; parent is not null; parent = parent.parent) + if (selected.Contains(parent.gameObject.identity.persistentId)) + return true; + return false; + } +} diff --git a/src/composition/editor/features/Inno.Editor.Scripting/Inno.Editor.Scripting.csproj b/src/composition/editor/features/Inno.Editor.Scripting/Inno.Editor.Scripting.csproj index c2dd4cd6f..c5a0e1242 100644 --- a/src/composition/editor/features/Inno.Editor.Scripting/Inno.Editor.Scripting.csproj +++ b/src/composition/editor/features/Inno.Editor.Scripting/Inno.Editor.Scripting.csproj @@ -10,36 +10,16 @@ - - + - - - - - - - + + + + + + - + diff --git a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/EditorScripting.cs b/src/composition/editor/features/Inno.Editor.Scripting/Runtime/EditorScripting.cs index 73e042490..c15d4b23d 100644 --- a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/EditorScripting.cs +++ b/src/composition/editor/features/Inno.Editor.Scripting/Runtime/EditorScripting.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Plugins.Authoring; @@ -12,7 +11,6 @@ using Inno.Scripting.Reload; namespace Inno.Editor.Scripting; - /// /// Owns script compilation and activation for one editor project. /// @@ -39,7 +37,6 @@ internal sealed class EditorScripting : EditorModule, IEditorScriptCompilation private bool m_reportedReloadWait; private string? m_activationFailure; private long m_nextRequestId; - internal EditorScripting( AssetPipeline assets, PluginEnvironment plugins, @@ -65,25 +62,20 @@ Func moduleSourceFactory /// Gets whether blocks following updates is enabled for this implementation. /// public override bool blocksFollowingUpdates => m_blockFollowingUpdates; - /// /// Gets whether script compilation is currently active. /// - internal bool isCompiling - => m_manager?.isFaulted != true && - (m_showCompilation || - m_compilation is not null || - m_pendingCompletion is not null || - m_manager?.isCompiling == true || - m_manager?.isUnloadVerificationPending == true || - m_manager?.isCompilationPending == true); - + internal bool isCompiling => m_manager?.isFaulted != true + && (m_showCompilation + || m_compilation is not null + || m_pendingCompletion is not null + || m_manager?.isCompiling == true + || m_manager?.isUnloadVerificationPending == true + || m_manager?.isCompilationPending == true); internal bool isAvailable => m_manager is not null; - - internal bool requiresExclusiveProgress => isCompiling && m_reloads.CanActivateReload(out _); - - internal bool canReload => m_manager is not null && !isCompiling && !m_manager.isFaulted - && m_reloads.CanRequestReload(out _); + /// + public bool requiresExclusiveProgress => isCompiling && m_reloads.CanActivateReload(out _); + internal bool canReload => m_manager is not null && !isCompiling && !m_manager.isFaulted && m_reloads.CanRequestReload(out _); /// /// Gets the current lifecycle state observed by callers. @@ -113,15 +105,11 @@ public EditorScriptCompilationState state /// /// Gets the current compiler progress. /// - internal float progress => m_manager?.compilationProgress ?? 0f; - + public float progress => m_manager?.compilationProgress ?? 0f; /// /// Gets the current compiler stage. /// - public string status => m_activationFailure ?? (!m_reloads.CanActivateReload(out string? reason) - ? reason ?? "Waiting for Play Mode to stop." - : m_manager?.compilationStatus ?? "Initializing project scripting."); - + public string status => m_activationFailure ?? (!m_reloads.CanActivateReload(out string? reason) ? reason ?? "Waiting for Play Mode to stop." : m_manager?.compilationStatus ?? "Initializing project scripting."); /// /// Gets the most recently completed compiler result, which may still await activation and retirement verification. /// @@ -135,22 +123,23 @@ public EditorScriptCompilationState state /// public IScriptCompilationTicket RequestCompilation() { - var ticket = new ScriptCompilationTicket( - ++m_nextRequestId, - "Script compilation is queued."); + var ticket = new ScriptCompilationTicket(++m_nextRequestId, "Script compilation is queued."); ScriptReloadHost? manager = m_manager; if (!m_reloads.CanRequestReload(out string? reason)) { ticket.MarkFailed(result: null, reason ?? "Stop Play Mode before requesting compilation."); return ticket; } + if (manager is null || manager.isFaulted) { - ticket.MarkFailed(result: null, manager?.isFaulted == true - ? "Generation retirement failed. Restart the host before requesting compilation." - : "Project scripting is not available."); + ticket.MarkFailed( + result: null, + manager?.isFaulted == true ? "Generation retirement failed. Restart the host before requesting compilation." : "Project scripting is not available." + ); return ticket; } + m_currentTicket?.MarkSuperseded(); m_currentTicket = ticket; QueueReload(static value => value.RecompileScripting(), supersedeCurrentTicket: false); @@ -174,8 +163,8 @@ internal void ReloadPlugins() QueueReload(static manager => manager.ReloadPlugins(), supersedeCurrentTicket: true); } - internal void CancelCompilation() => m_manager?.CancelCompilation(); - + /// + public void CancelCompilation() => m_manager?.CancelCompilation(); /// /// Initializes this feature when its owning runtime becomes active. /// @@ -184,15 +173,7 @@ internal void ReloadPlugins() /// protected override void OnStart(EditorContext context) { - m_manager = new ScriptReloadHost( - new ScriptReloadOptions(), - m_compiler, - m_assets, - m_plugins, - m_modules, - m_settings, - m_reloads, - m_moduleSourceFactory); + m_manager = new ScriptReloadHost(new ScriptReloadOptions(), m_compiler, m_assets, m_plugins, m_modules, m_settings, m_reloads, m_moduleSourceFactory); m_manager.Start(); if (m_manager.TryCompilePending(out Task? compilation)) { @@ -216,11 +197,13 @@ protected override void OnUpdate(EditorContext context) m_hideCompilationOnNextUpdate = false; m_showCompilation = false; } + if (m_manager?.isFaulted == true) { AdvanceUnloadVerification(); return; } + if (!m_reloads.CanActivateReload(out string? waitingReason)) { if (m_manager?.isCompilationPending == true && !m_reportedReloadWait) @@ -228,8 +211,10 @@ protected override void OnUpdate(EditorContext context) m_log.Write(LogLevel.Info, "{0}", [waitingReason ?? "Script changes are queued until Play Mode stops."]); m_reportedReloadWait = true; } + return; } + m_reportedReloadWait = false; CompleteCompilation(); if (m_manager is null || m_compilation is not null) @@ -239,6 +224,7 @@ protected override void OnUpdate(EditorContext context) AdvanceUnloadVerification(); return; } + if (m_manager.isFaulted) return; if (!context.isFocused && !m_explicitReloadPending) @@ -259,12 +245,10 @@ protected override void OnUpdate(EditorContext context) /// The context that supplies state and services for this operation. /// protected override void OnStop(EditorContext context) => DisposeManager(); - /// /// Releases resources retained by this feature after it has stopped. /// protected override void OnDispose() => DisposeManager(); - private void CompleteCompilation() { Task? compilation = m_compilation; @@ -286,10 +270,10 @@ private void CompleteCompilation() ticket?.MarkSuperseded(); return; } + m_activationFailure = null; ScriptDiagnosticPublisher.ClearReload(); - CompleteAfterRetirement(new PendingCompletion(ticket, result, true, - "Script generation compiled, activated, and retired successfully.")); + CompleteAfterRetirement(new PendingCompletion(ticket, result, true, "Script generation compiled, activated, and retired successfully.")); } else { @@ -299,9 +283,7 @@ private void CompleteCompilation() m_blockFollowingUpdates = true; m_activationFailure = null; ScriptDiagnosticPublisher.ClearReload(); - CompleteAfterRetirement(new PendingCompletion(ticket, result, false, - "Script compilation failed after a Plugin availability change; unavailable types were " + - "preserved as Missing.")); + CompleteAfterRetirement(new PendingCompletion(ticket, result, false, "Script compilation failed after a Plugin availability change; unavailable types were " + "preserved as Missing.")); } else { @@ -315,18 +297,14 @@ private void CompleteCompilation() { m_plugins.RollbackPending(); ticket?.MarkCanceled("Script compilation was canceled; the active generation was retained."); - m_log.Write( - LogLevel.Info, - "Script compilation was canceled; the active generation was retained."); + m_log.Write(LogLevel.Info, "Script compilation was canceled; the active generation was retained."); } catch (Exception exception) { if (!m_manager.isFaulted) m_plugins.RollbackPending(); m_activationFailure = $"Script generation activation failed: {exception.Message}"; - ticket?.MarkFailed( - ticket.result, - $"Script generation activation failed: {exception.Message}"); + ticket?.MarkFailed(ticket.result, $"Script generation activation failed: {exception.Message}"); ScriptDiagnosticPublisher.PublishReloadFailure(exception); m_log.Write(LogLevel.Error, "Script assembly reload failed: {0}", [exception]); } @@ -334,9 +312,7 @@ private void CompleteCompilation() { m_compilation = null; m_compilationTicket = null; - m_hideCompilationOnNextUpdate = m_pendingCompletion is null && - m_manager?.isUnloadVerificationPending != true && - m_manager?.isCompilationPending != true; + m_hideCompilationOnNextUpdate = m_pendingCompletion is null && m_manager?.isUnloadVerificationPending != true && m_manager?.isCompilationPending != true; } } @@ -347,6 +323,7 @@ private void CompleteAfterRetirement(PendingCompletion completion) m_pendingCompletion = completion; return; } + FinishCompletion(completion); } @@ -357,9 +334,7 @@ private void FinishCompletion(PendingCompletion completion) bool projected = GenerateIdeProjection(); if (completion.succeeded) { - completion.ticket?.MarkSucceeded(completion.result, projected - ? completion.status - : "Script generation retired successfully, but IDE project generation failed."); + completion.ticket?.MarkSucceeded(completion.result, projected ? completion.status : "Script generation retired successfully, but IDE project generation failed."); } else { @@ -383,11 +358,13 @@ private void AdvanceUnloadVerification() { ScriptDiagnosticPublisher.PublishUnloadFailure(failure); } + m_activationFailure = status; m_showCompilation = false; m_hideCompilationOnNextUpdate = false; return; } + ScriptDiagnosticPublisher.ClearUnload(); if (completion is not null) FinishCompletion(completion); @@ -398,8 +375,7 @@ private bool GenerateIdeProjection() { try { - ScriptReloadHost manager = m_manager - ?? throw new InvalidOperationException("Project scripting is not available."); + ScriptReloadHost manager = m_manager ?? throw new InvalidOperationException("Project scripting is not available."); manager.GenerateProjectFiles(); ScriptDiagnosticPublisher.ClearIdeProjection(); return true; @@ -407,10 +383,7 @@ private bool GenerateIdeProjection() catch (Exception exception) { ScriptDiagnosticPublisher.PublishIdeProjectionFailure(exception); - m_log.Write( - LogLevel.Warn, - "Script generation activated, but IDE project generation failed: {0}", - [exception]); + m_log.Write(LogLevel.Warn, "Script generation activated, but IDE project generation failed: {0}", [exception]); return false; } } @@ -447,6 +420,7 @@ private void DisposeManager() ScriptDiagnosticPublisher.ClearAll(); return; } + m_manager.Dispose(); m_currentTicket?.MarkCanceled("Project scripting stopped before this request could activate."); m_manager = null; diff --git a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs b/src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs index e1111252d..937d7d159 100644 --- a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs +++ b/src/composition/editor/features/Inno.Editor.Scripting/Runtime/IEditorScriptCompilation.cs @@ -1,7 +1,6 @@ using Inno.Scripting.Compiler; namespace Inno.Editor.Scripting; - /// /// Identifies whether the active project script generation can safely start game simulation. /// @@ -11,17 +10,14 @@ public enum EditorScriptCompilationState /// The script service has not produced its initial active generation. /// Initializing, - /// /// A requested script generation is compiling, activating, or completing unload verification. /// Compiling, - /// /// The most recent compilation succeeded, its generation is active, and retired generations passed GC verification. /// Ready, - /// /// Compilation, activation, or generation retirement failed, so game simulation must not start. /// @@ -33,6 +29,20 @@ public enum EditorScriptCompilationState /// public interface IEditorScriptCompilation { + /// + /// Gets whether compiler or activation work currently requires a blocking progress presentation. + /// + bool requiresExclusiveProgress { get; } + + /// + /// Gets the most recent compiler progress fraction in the inclusive range zero to one. + /// + float progress { get; } + + /// + /// Requests cancellation while retaining ownership until compilation and retirement finish. + /// + void CancelCompilation(); /// /// Queues a fresh cache-aware compilation for a build or simulation workflow. /// @@ -41,7 +51,6 @@ public interface IEditorScriptCompilation /// a faulted host returns a failed ticket and requires restart. /// IScriptCompilationTicket RequestCompilation(); - /// /// Gets the most recently requested compilation ticket, or when no explicit request exists. /// diff --git a/src/composition/editor/features/Inno.Editor.Scripting/ScriptingReloadActions.cs b/src/composition/editor/features/Inno.Editor.Scripting/ScriptingReloadActions.cs index 96ed83d87..b910234d3 100644 --- a/src/composition/editor/features/Inno.Editor.Scripting/ScriptingReloadActions.cs +++ b/src/composition/editor/features/Inno.Editor.Scripting/ScriptingReloadActions.cs @@ -1,9 +1,8 @@ using Inno.Editor.Interactions; namespace Inno.Editor.Scripting; - [EditorAction(ScriptingInteractionIds.C_RELOAD_SCRIPTING, ScriptingInteractionIds.C_MAIN_MENU_AREA)] -[EditorMenu(ScriptingInteractionIds.C_MAIN_MENU_AREA, "Scripting/Reload Scripts", order: 100)] +[EditorMenu(ScriptingInteractionIds.C_MAIN_MENU_AREA, "Tools/Scripting/Reload Scripts", order: 100)] internal sealed class ReloadScriptingAction(EditorScripting scripting) : EditorAction { /// @@ -15,9 +14,7 @@ internal sealed class ReloadScriptingAction(EditorScripting scripting) : EditorA /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => scripting.canReload ? EditorActionState.enabled : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => scripting.canReload ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -28,7 +25,7 @@ protected override EditorActionState Query(EditorActionContext context) } [EditorAction(ScriptingInteractionIds.C_RELOAD_PLUGINS, ScriptingInteractionIds.C_MAIN_MENU_AREA)] -[EditorMenu(ScriptingInteractionIds.C_MAIN_MENU_AREA, "Scripting/Reload Plugins", order: 120)] +[EditorMenu(ScriptingInteractionIds.C_MAIN_MENU_AREA, "Tools/Scripting/Reload Plugins", order: 120)] internal sealed class ReloadPluginsAction(EditorScripting scripting) : EditorAction { /// @@ -40,9 +37,7 @@ internal sealed class ReloadPluginsAction(EditorScripting scripting) : EditorAct /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => scripting.canReload ? EditorActionState.enabled : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => scripting.canReload ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Inno.Editor.Shaders.csproj b/src/composition/editor/features/Inno.Editor.Shaders/Inno.Editor.Shaders.csproj index e806c2c6f..6d3ced8a9 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Inno.Editor.Shaders.csproj +++ b/src/composition/editor/features/Inno.Editor.Shaders/Inno.Editor.Shaders.csproj @@ -10,47 +10,27 @@ - + - - - - - - - - + + + + - - - + - - + - - + + + + + - diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs b/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs index 62ade737f..6829869d1 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs +++ b/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialDocuments.cs @@ -16,7 +16,6 @@ using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Shaders; - /// /// Owns Material authoring sessions; native source editing uses the shared asset draft lifecycle. /// @@ -27,9 +26,18 @@ public sealed class MaterialDocuments : EditorModule private readonly AssetDraftDocuments m_documents; private readonly SerializationRegistry m_serialization; private readonly ConditionalWeakTable m_presentations = new(); - internal readonly AssetPipeline assets; + /// + /// Gets the authoritative asset catalog used by source-aware inspectors. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetPipeline assets { get; } + internal readonly EditorInteractions interactions; - internal readonly IEditorPreviewService previews; + /// + /// Gets the generation-scoped preview service shared by material presentations. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public IEditorPreviewService previews { get; } internal MaterialDocuments( AssetPipeline assets, @@ -37,7 +45,9 @@ internal MaterialDocuments( EditorInteractions interactions, IEditorPreviewService previews ) { - this.assets = assets; this.interactions = interactions; this.previews = previews; + this.assets = assets; + this.interactions = interactions; + this.previews = previews; m_serialization = serialization; m_documents = new(assets, serialization, interactions, "inno.material", C_HISTORY, ".imaterial", "Material"); } @@ -52,7 +62,6 @@ IEditorPreviewService previews /// The persistent identity used for subsequent draft operations. /// public Guid Open(AssetPath path) => m_documents.Open(path); - /// /// Reads a detached current-generation Material; referenced assets remain canonical read-only inputs. /// @@ -63,7 +72,6 @@ IEditorPreviewService previews /// The current unsaved Material value. /// public MaterialAsset Read(Guid assetId) => m_documents.Read(assetId); - /// /// Changes a draft through shared History without modifying its source or Scene/Game. /// @@ -73,56 +81,72 @@ IEditorPreviewService previews /// /// Detached edited Material. /// - /// - /// Whether this sample finishes the active gesture. + /// + /// The current gesture key, or null for a discrete edit. /// public void Replace( Guid assetId, MaterialAsset candidate, - bool finishGesture = true - ) - => m_documents.Replace(assetId, candidate, finishGesture); - - /// - /// Finishes a draft gesture without saving its source. - /// - /// - /// Open Material source identity. - /// - public void Commit(Guid assetId) => m_documents.Commit(assetId); - + string? mergeKey = null + ) => m_documents.Replace(assetId, candidate, mergeKey); /// /// Changes compatible selected drafts within one shared gesture. /// /// /// Detached Material values keyed by persistent source identity. /// - /// - /// Whether this sample finishes the shared gesture. + /// + /// The current gesture key, or null for a discrete edit. /// public void ReplaceMany( IReadOnlyDictionary candidates, - bool finishGesture = true - ) - => m_documents.ReplaceMany(candidates, finishGesture); - + string? mergeKey = null + ) => m_documents.ReplaceMany(candidates, mergeKey); /// - /// Completes a multi-document gesture as one shared History transaction. + /// Opens the current source draft without publishing it. /// - /// - /// Participating persistent source identities. + /// + /// The isolated Material source path. /// - public void CommitMany(IEnumerable assetIds) => m_documents.CommitMany(assetIds); - - internal Draft OpenDraft(AssetPath path) => m_documents.GetDraft(Open(path)); - internal Draft Open(MaterialAsset material) + /// + /// The generation-scoped draft; callers must not retain it across a reload. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public Draft OpenDraft(AssetPath path) => m_documents.GetDraft(Open(path)); + /// + /// Resolves the source draft for a canonical Material identity. + /// + /// + /// The current canonical Material whose source is edited. + /// + /// + /// The generation-scoped editable source draft. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public Draft Open(MaterialAsset material) { if (!assets.TryGetInfo(material.identity.persistentId, out AssetInfo? info) || info is null) throw new IOException("The Material source identity is unavailable."); return OpenDraft(info.assetPath); } - internal void TouchInspection(Draft draft) => m_documents.TouchInspection(draft.id); - internal ShaderParameterPresentation Presentation( + + /// + /// Reads parameter presentation from the current authoring Shader graph. + /// + /// + /// The canonical Shader used by the Material. + /// + /// + /// The stable binding identity. + /// + /// + /// Receives the source diagnostic, or an empty string when presentation is available. + /// + /// + /// Detached presentation values; an unavailable graph uses the ordinary presentation defaults. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public ShaderParameterPresentation Presentation( ShaderAsset shader, ShaderPropertyId propertyId, out string error @@ -142,16 +166,11 @@ out string error cache.error = failure.Message; } } + error = cache.error; - return cache.graph is null ? new() : ShaderParameterPresentation.Read(cache.graph, propertyId, - m_serialization, AssetSerializationContext.Create(assets)); + return cache.graph is null ? new() : ShaderParameterPresentation.Read(cache.graph, propertyId, m_serialization, AssetSerializationContext.Create(assets)); } - internal void Edit( - Draft draft, - MaterialAsset candidate, - bool finishGesture - ) => Replace(draft.id, candidate, finishGesture); - internal void Commit(Draft draft) => Commit(draft.id); + internal void ValidateHistory( EditorHistoryChange change, EditorHistoryDirection direction @@ -160,9 +179,13 @@ internal void ApplyHistory( EditorHistoryChange change, EditorHistoryDirection direction ) => m_documents.ApplyHistory(change, direction); + internal bool TryMergeHistory( + EditorHistoryChange older, + EditorHistoryChange newer, + out EditorHistoryChange? merged + ) => m_documents.TryMergeHistory(older, newer, out merged); internal static bool Recoverable(Exception error) => error is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException or FormatException && Inno.Core.Execution.RetirementPendingException.Find(error) is null; - /// /// Initializes this feature when its owning runtime becomes active. /// @@ -215,11 +238,11 @@ internal sealed class MaterialDraftHistory(MaterialDocuments documents) : Editor /// /// The validated editor history availability that represents the completed operation. /// -protected override EditorHistoryAvailability Query( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryAvailability Query( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { documents.ValidateHistory(change, direction); @@ -230,6 +253,7 @@ EditorHistoryDirection direction return EditorHistoryAvailability.Unavailable(error.Message); } } + /// /// Applies a validated change atomically at the caller-controlled commit point. /// @@ -245,11 +269,11 @@ EditorHistoryDirection direction /// /// The validated editor history result that represents the completed operation. /// -protected override EditorHistoryResult Apply( - EditorHistoryContext context, - EditorHistoryChange change, - EditorHistoryDirection direction -) { + protected override EditorHistoryResult Apply( + EditorHistoryContext context, + EditorHistoryChange change, + EditorHistoryDirection direction + ) { try { documents.ApplyHistory(change, direction); @@ -260,4 +284,11 @@ EditorHistoryDirection direction return EditorHistoryResult.Failure(error.Message); } } + + /// + protected override bool TryMerge( + EditorHistoryChange older, + EditorHistoryChange newer, + out EditorHistoryChange? merged + ) => documents.TryMergeHistory(older, newer, out merged); } diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Properties/ScriptingApi.cs b/src/composition/editor/features/Inno.Editor.Shaders/Properties/ScriptingApi.cs index 5ca180c11..e02b2788e 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Properties/ScriptingApi.cs +++ b/src/composition/editor/features/Inno.Editor.Shaders/Properties/ScriptingApi.cs @@ -2,13 +2,8 @@ using Inno.Scripting.Api; [assembly: ScriptingApiNamespace("InnoEditor.Shaders", "Inno.Editor.Shaders", ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(ShaderNodeDrawerAttribute), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(ShaderNodeDrawer), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(ShaderNodeDrawContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(MaterialDocuments), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(ShaderPropertyInspector), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ShaderParameterPresentation), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ShaderPreviewProviderAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ShaderPreviewProvider), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ShaderPreviewContext), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(ShaderPreviews), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorTypeSpecificity.cs b/src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorTypeSpecificity.cs new file mode 100644 index 000000000..9cdc7b9ab --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Core/Extensions/EditorTypeSpecificity.cs @@ -0,0 +1,59 @@ +using System; + +namespace Inno.Editor.Core; +/// +/// Ranks assignable editor extension targets by class ancestry, with interface registrations one step away. +/// +public static class EditorTypeSpecificity +{ + /// + /// Determines whether a registration accepts a concrete target and returns its specificity distance. + /// + /// + /// The target type supplied by the active extension generation. + /// + /// + /// The registered class or interface accepted by the extension. + /// + /// + /// Zero for an exact match, ancestry distance for classes, or one for an interface. + /// + /// + /// True when the registration accepts the target; otherwise false and the maximum distance. + /// + /// + /// Either type is null. + /// + public static bool TryGet( + Type concreteType, + Type candidateType, + out int distance + ) { + ArgumentNullException.ThrowIfNull(concreteType); + ArgumentNullException.ThrowIfNull(candidateType); + if (!candidateType.IsAssignableFrom(concreteType)) + { + distance = int.MaxValue; + return false; + } + + if (concreteType == candidateType) + { + distance = 0; + return true; + } + + distance = 1; + Type? current = concreteType.BaseType; + while (current is not null) + { + if (current == candidateType) + return true; + distance++; + current = current.BaseType; + } + + distance = candidateType.IsInterface ? 1 : distance; + return true; + } +} diff --git a/src/composition/editor/framework/Inno.Editor.Core/Inno.Editor.Core.csproj b/src/composition/editor/framework/Inno.Editor.Core/Inno.Editor.Core.csproj index d8b053d66..0a98bf14d 100644 --- a/src/composition/editor/framework/Inno.Editor.Core/Inno.Editor.Core.csproj +++ b/src/composition/editor/framework/Inno.Editor.Core/Inno.Editor.Core.csproj @@ -10,6 +10,9 @@ + diff --git a/src/composition/editor/framework/Inno.Editor.Core/Presentation/EditorDisplayNames.cs b/src/composition/editor/framework/Inno.Editor.Core/Presentation/EditorDisplayNames.cs new file mode 100644 index 000000000..dd4fa1331 --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Core/Presentation/EditorDisplayNames.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; + +namespace Inno.Editor.Core; + +/// +/// Formats readable Editor labels independently from a native UI context or session. +/// +public static class EditorDisplayNames +{ + /// + /// Converts an identifier into a readable editor label. + /// + /// + /// Source identifier. + /// + /// + /// A label with identifier separators and lower-to-upper transitions spaced; empty when the input is blank. + /// + public static string Nicify(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return string.Empty; + } + + var builder = new StringBuilder(name.Length + 8); + char previous = '\0'; + for (int i = 0; i < name.Length; i++) + { + char current = name[i]; + if (current == '_' || current == '-') + { + if (builder.Length > 0 && builder[builder.Length - 1] != ' ') + { + builder.Append(' '); + } + + previous = current; + continue; + } + + if (i > 0 && char.IsUpper(current) && char.IsLower(previous) && builder[builder.Length - 1] != ' ') + { + builder.Append(' '); + } + + builder.Append(builder.Length == 0 ? char.ToUpperInvariant(current) : current); + previous = current; + } + + return builder.ToString(); + } +} diff --git a/src/composition/editor/framework/Inno.Editor.Core/Properties/ScriptingApi.cs b/src/composition/editor/framework/Inno.Editor.Core/Properties/ScriptingApi.cs index a603a8e26..694a08389 100644 --- a/src/composition/editor/framework/Inno.Editor.Core/Properties/ScriptingApi.cs +++ b/src/composition/editor/framework/Inno.Editor.Core/Properties/ScriptingApi.cs @@ -20,3 +20,5 @@ [assembly: ScriptingApiExport(typeof(EditorModal), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorModalAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(IEditorPanelReloadState), ScriptingApiScope.Editor)] + +[assembly: ScriptingApiExport(typeof(EditorDisplayNames), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs index 65fe34898..c52027aa5 100644 --- a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs +++ b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorContext.cs @@ -21,23 +21,34 @@ public sealed class EditorContext /// /// The explicit immutable conventions selected by product composition. /// + /// + /// An optional host-owned initial settings document, installed atomically only when + /// editor.ini does not exist. Existing documents, including empty files, are preserved. + /// /// /// The keyboard policy is null. /// /// /// Thrown when is empty. /// + /// + /// The current document cannot be read or its initial document cannot be installed. + /// + /// + /// The caller cannot access or initialize the project settings document. + /// [ScriptingApiIgnore] public EditorContext( string projectDirectory, - EditorKeyboardPolicy keyboard + EditorKeyboardPolicy keyboard, + string? initialLayoutDocument = null ) { if (string.IsNullOrWhiteSpace(projectDirectory)) throw new ArgumentException("A project directory is required.", nameof(projectDirectory)); ArgumentNullException.ThrowIfNull(keyboard); this.keyboard = keyboard; this.projectDirectory = Path.GetFullPath(projectDirectory); - layout = new EditorLayoutSettings(this.projectDirectory); + layout = new EditorLayoutSettings(this.projectDirectory, initialLayoutDocument); statistics = new EditorStatistics(); } diff --git a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorLayoutSettings.cs b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorLayoutSettings.cs index 04169f06a..03d5bf18f 100644 --- a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorLayoutSettings.cs +++ b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorLayoutSettings.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; +using Inno.Core.IO; namespace Inno.Editor.Core; @@ -26,14 +27,19 @@ internal sealed class EditorLayoutSettings /// /// The project root that owns editor.ini. /// + /// + /// The optional initial document supplied by the composition owner, never a replacement for an existing file. + /// /// /// Thrown when is empty. /// - internal EditorLayoutSettings(string projectDirectory) - { + internal EditorLayoutSettings( + string projectDirectory, + string? initialDocument + ) { ArgumentException.ThrowIfNullOrWhiteSpace(projectDirectory); m_path = Path.Combine(Path.GetFullPath(projectDirectory), "editor.ini"); - string document = File.Exists(m_path) ? File.ReadAllText(m_path) : string.Empty; + string document = ReadDocument(initialDocument); Parse(document, out m_imguiLayout, out m_sections); m_lastSavedDocument = Compose(m_imguiLayout, m_sections); } @@ -200,24 +206,25 @@ internal void Save() private void WriteDocument(string document) { - string? directory = Path.GetDirectoryName(m_path); - if (!string.IsNullOrEmpty(directory)) - Directory.CreateDirectory(directory); - string temporaryPath = m_path + ".tmp"; - using (var stream = new FileStream( - temporaryPath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - 4096, - FileOptions.WriteThrough)) + AtomicFile.WriteAllBytes(m_path, Encoding.UTF8.GetBytes(document)); + m_lastSavedDocument = document; + } + + private string ReadDocument(string? initialDocument) + { + if (File.Exists(m_path)) + return File.ReadAllText(m_path); + if (initialDocument is null) + return string.Empty; + try { - byte[] bytes = Encoding.UTF8.GetBytes(document); - stream.Write(bytes); - stream.Flush(flushToDisk: true); + AtomicFile.WriteAllBytes(m_path, Encoding.UTF8.GetBytes(initialDocument), overwrite: false); } - File.Move(temporaryPath, m_path, overwrite: true); - m_lastSavedDocument = document; + catch (IOException) when (File.Exists(m_path)) + { + // A concurrent creator owns the completed document; never overwrite its settings. + } + return File.ReadAllText(m_path); } private static void Parse( diff --git a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs index a31c8a076..4b46f6946 100644 --- a/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs +++ b/src/composition/editor/framework/Inno.Editor.Core/Runtime/EditorStatistics.cs @@ -185,8 +185,10 @@ public EditorStatistic( public sealed class EditorStatistics { private readonly object m_sync = new(); - private readonly Dictionary m_current = []; + private Dictionary m_current = []; private Dictionary m_completed = []; + private IReadOnlyList? m_snapshot; + private bool m_snapshotDirty = true; /// /// Publishes or replaces one statistic for the current editor frame. @@ -201,7 +203,12 @@ public void Publish(EditorStatistic statistic) { ArgumentNullException.ThrowIfNull(statistic); lock (m_sync) + { + EditorStatistic? previous = m_current.GetValueOrDefault(statistic.id) ?? m_completed.GetValueOrDefault(statistic.id); + if (previous is null || !SameValue(previous, statistic)) + m_snapshotDirty = true; m_current[statistic.id] = statistic; + } } /// @@ -225,22 +232,28 @@ public void Publish(IEnumerable statistics) /// frame for providers that draw after their consumer. /// /// - /// A detached, sorted snapshot containing at most one value for each stable statistic ID. + /// An immutable, sorted snapshot containing at most one value for each stable statistic ID. + /// Unchanged content reuses the same snapshot; previously returned snapshots remain valid. /// public IReadOnlyList GetSnapshot() { lock (m_sync) { + if (!m_snapshotDirty && m_snapshot is not null) + return m_snapshot; var merged = new Dictionary(m_completed); foreach ((EditorStatisticId id, EditorStatistic statistic) in m_current) merged[id] = statistic; - return merged.Values + EditorStatistic[] values = merged.Values .OrderBy(static value => value.groupOrder) .ThenBy(static value => value.groupName, StringComparer.OrdinalIgnoreCase) .ThenBy(static value => value.order) .ThenBy(static value => value.label, StringComparer.OrdinalIgnoreCase) .ThenBy(static value => value.id.value, StringComparer.Ordinal) .ToArray(); + m_snapshot = Array.AsReadOnly(values); + m_snapshotDirty = false; + return m_snapshot; } } @@ -248,8 +261,26 @@ internal void AdvanceFrame() { lock (m_sync) { - m_completed = new Dictionary(m_current); + foreach (EditorStatisticId id in m_completed.Keys) + { + if (m_current.ContainsKey(id)) + continue; + m_snapshotDirty = true; + break; + } + (m_completed, m_current) = (m_current, m_completed); m_current.Clear(); } } + + private static bool SameValue( + EditorStatistic first, + EditorStatistic second + ) => ReferenceEquals(first, second) + || first.groupId == second.groupId + && first.groupOrder == second.groupOrder + && first.order == second.order + && string.Equals(first.groupName, second.groupName, StringComparison.Ordinal) + && string.Equals(first.label, second.label, StringComparison.Ordinal) + && string.Equals(first.value, second.value, StringComparison.Ordinal); } diff --git a/src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs b/src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs index db403e152..d34efea5a 100644 --- a/src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs +++ b/src/composition/editor/framework/Inno.Editor.Diagnostics/EditorConsole.cs @@ -24,6 +24,7 @@ public sealed class EditorConsole : IEditorConsole, ILogSink, IDiagnosticSink, I private int m_capacity = 1024; private long m_nextSequence; private long m_revision; + private IDisposable? m_playModeSubscription; private bool m_started; private bool m_disposed; @@ -93,10 +94,21 @@ public void Start() ObjectDisposedException.ThrowIf(m_disposed, this); if (m_started) return; - m_logRouter.RegisterSink(this); - m_diagnosticHub.RegisterSink(this); - m_playMode.stateChanged += OnPlayModeStateChanged; - m_started = true; + IDisposable subscription = m_playMode.ListenStateChanges(OnPlayModeStateChanged); + try + { + m_logRouter.RegisterSink(this); + m_diagnosticHub.RegisterSink(this); + m_playModeSubscription = subscription; + m_started = true; + } + catch + { + m_diagnosticHub.UnregisterSink(this); + m_logRouter.UnregisterSink(this); + subscription.Dispose(); + throw; + } } /// @@ -195,7 +207,8 @@ public void Dispose() return; if (m_started) { - m_playMode.stateChanged -= OnPlayModeStateChanged; + m_playModeSubscription?.Dispose(); + m_playModeSubscription = null; m_logRouter.UnregisterSink(this); m_diagnosticHub.UnregisterSink(this); m_started = false; diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/BuiltInInspectorAttributeDrawers.cs b/src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/BuiltInInspectorAttributeDrawers.cs index c73c00f6d..26ac1188f 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/BuiltInInspectorAttributeDrawers.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/AttributeDrawing/BuiltInInspectorAttributeDrawers.cs @@ -1,17 +1,15 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections; using System.Globalization; using System.Reflection; -using Inno.Adapter.Presentation.ImGui; using Inno.Core.Serialization; using Inno.Editor.Annotations; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [InspectorAttributeDrawer(typeof(HeaderAttribute))] internal sealed class HeaderInspectorAttributeDrawer : IInspectorAttributeDrawer { @@ -24,7 +22,7 @@ internal sealed class HeaderInspectorAttributeDrawer : IInspectorAttributeDrawer public void DrawBefore(InspectorAttributeDrawContext context) { var header = (HeaderAttribute)context.attribute; - ImGuiWidget.SectionHeader(header.title, header.description); + EditorUI.current.widgets.SectionHeader(header.title, header.description); } } @@ -40,14 +38,14 @@ internal sealed class TextInspectorAttributeDrawer : IInspectorAttributeDrawer public void DrawBefore(InspectorAttributeDrawContext context) { string text = ((TextAttribute)context.attribute).text; - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.textDisabled); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.textDisabled); try { - ImGuiWidget.WrappedText(text); + EditorUI.current.widgets.WrappedText(text); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } } @@ -64,7 +62,7 @@ internal sealed class SpaceInspectorAttributeDrawer : IInspectorAttributeDrawer public void DrawBefore(InspectorAttributeDrawContext context) { float height = ((SpaceAttribute)context.attribute).height; - NativeImGui.Dummy(new System.Numerics.Vector2(0f, height)); + Ui.Dummy(new System.Numerics.Vector2(0f, height)); } } @@ -133,10 +131,7 @@ internal sealed class ShowIfInspectorAttributeDrawer : IInspectorAttributeDrawer public void Update(InspectorAttributeDrawContext context) { var condition = (ShowIfAttribute)context.attribute; - context.isVisible &= InspectorConditionEvaluator.Evaluate( - context.GetSiblingValue(condition.memberName), - condition.condition, - condition.expectedValue); + context.isVisible &= InspectorConditionEvaluator.Evaluate(context.GetSiblingValue(condition.memberName), condition.condition, condition.expectedValue); } } @@ -152,10 +147,7 @@ internal sealed class HideIfInspectorAttributeDrawer : IInspectorAttributeDrawer public void Update(InspectorAttributeDrawContext context) { var condition = (HideIfAttribute)context.attribute; - context.isVisible &= !InspectorConditionEvaluator.Evaluate( - context.GetSiblingValue(condition.memberName), - condition.condition, - condition.expectedValue); + context.isVisible &= !InspectorConditionEvaluator.Evaluate(context.GetSiblingValue(condition.memberName), condition.condition, condition.expectedValue); } } @@ -173,25 +165,26 @@ public void DrawBefore(InspectorAttributeDrawContext context) var help = (HelpBoxAttribute)context.attribute; if (!string.IsNullOrWhiteSpace(help.conditionMember) && !InspectorConditionEvaluator.Evaluate( - context.GetSiblingValue(help.conditionMember), - help.condition, - help.expectedValue)) + context.GetSiblingValue(help.conditionMember), + help.condition, + help.expectedValue + )) { return; } + System.Numerics.Vector4 color = help.messageType switch { InspectorMessageType.Warning => EditorPalette.warning, InspectorMessageType.Error => EditorPalette.error, - _ => new System.Numerics.Vector4(0.42f, 0.66f, 0.88f, EditorPalette.opacityOpaque) - }; + _ => new System.Numerics.Vector4(0.42f, 0.66f, 0.88f, EditorPalette.opacityOpaque)}; string icon = help.messageType switch { InspectorMessageType.Warning => ImGuiIcon.TriangleExclamation, InspectorMessageType.Error => ImGuiIcon.CircleXmark, _ => ImGuiIcon.CircleInfo }; - ImGuiWidget.HelpBox(help.text, icon, color); + EditorUI.current.widgets.HelpBox(help.text, icon, color); } } @@ -212,8 +205,7 @@ internal static bool Evaluate( InspectorCondition.NotNull => value is not null, InspectorCondition.Assigned => IsAssigned(value), InspectorCondition.NotAssigned => !IsAssigned(value), - _ => throw new ArgumentOutOfRangeException(nameof(condition), condition, "Unknown Inspector condition.") - }; + _ => throw new ArgumentOutOfRangeException(nameof(condition), condition, "Unknown Inspector condition.")}; } private static bool IsTruthy(object? value) @@ -232,12 +224,8 @@ private static bool IsAssigned(object? value) { if (value is null) return false; - PropertyInfo? property = value.GetType().GetProperty( - "isAssigned", - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - return property?.PropertyType == typeof(bool) && property.GetIndexParameters().Length == 0 - ? (bool)(property.GetValue(value) ?? false) - : true; + PropertyInfo? property = value.GetType().GetProperty("isAssigned", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return property?.PropertyType == typeof(bool) && property.GetIndexParameters().Length == 0 ? (bool)(property.GetValue(value) ?? false) : true; } private static bool AreEqual( @@ -251,11 +239,18 @@ private static bool AreEqual( Type valueType = value.GetType(); try { - object converted = valueType.IsEnum - ? expected is string name - ? Enum.Parse(valueType, name, ignoreCase: false) - : Enum.ToObject(valueType, expected) - : Convert.ChangeType(expected, valueType, CultureInfo.InvariantCulture); + object converted = valueType.IsEnum ? expected is string name ? Enum.Parse( + valueType, + name, + ignoreCase: false + ) : Enum.ToObject( + valueType, + expected + ) : Convert.ChangeType( + expected, + valueType, + CultureInfo.InvariantCulture + ); return Equals(value, converted); } catch (Exception exception) when (exception is ArgumentException or InvalidCastException or FormatException or OverflowException) diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/Inno.Editor.Inspection.csproj b/src/composition/editor/framework/Inno.Editor.Inspection/Inno.Editor.Inspection.csproj index f00abc403..5558e7286 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/Inno.Editor.Inspection.csproj +++ b/src/composition/editor/framework/Inno.Editor.Inspection/Inno.Editor.Inspection.csproj @@ -12,21 +12,9 @@ - - - - - + + + diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs b/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs deleted file mode 100644 index 43d3d2cb4..000000000 --- a/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/AssetInspectionSelection.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Inno.Editor.Inspection; - -/// -/// Represents an ordered multi-asset selection without retaining asset or plugin-generation objects. -/// -public sealed class AssetInspectionSelection -{ - /// - /// Captures unique persistent identities in selection order. - /// - /// - /// Nonempty identities; the last identity is the primary selection. - /// - public AssetInspectionSelection(IEnumerable assetIds) - { - ArgumentNullException.ThrowIfNull(assetIds); - Guid[] values = assetIds.Distinct().ToArray(); - if (values.Length == 0 || values.Contains(Guid.Empty)) - throw new ArgumentException("Select at least one persistent asset.", nameof(assetIds)); - this.assetIds = Array.AsReadOnly(values); - } - /// - /// Gets stable asset identities in selection order. - /// - public IReadOnlyList assetIds { get; } - /// - /// Gets the most recently selected asset identity. - /// - public Guid primaryAssetId => assetIds[^1]; -} diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/DefaultInspectionDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/DefaultInspectionDrawer.cs index 37a8d6b92..561a43b2e 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/DefaultInspectionDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/ObjectDrawing/DefaultInspectionDrawer.cs @@ -1,10 +1,8 @@ +using Inno.Editor.ImGui; using System; - -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [InspectionDrawer(typeof(object), useForChildren: true, priority: int.MinValue)] internal sealed class DefaultInspectionDrawer : InspectionDrawer { @@ -28,9 +26,7 @@ internal sealed class DefaultInspectionDrawer : InspectionDrawer protected override (string name, Action? setter) BindName( InspectionDrawContext context, object target - ) - => (target.GetType().Name, null); - + ) => (target.GetType().Name, null); /// /// Renders the header presentation for the current editor frame. /// @@ -43,9 +39,7 @@ object target protected override void DrawHeader( InspectionDrawContext context, object target - ) - => NativeImGui.TextUnformatted(target.GetType().FullName ?? target.GetType().Name); - + ) => Ui.Text(target.GetType().FullName ?? target.GetType().Name); /// /// Renders the value presentation for the current editor frame. /// diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/Properties/ScriptingApi.cs b/src/composition/editor/framework/Inno.Editor.Inspection/Properties/ScriptingApi.cs index 4e433c9bb..a81860133 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/Properties/ScriptingApi.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/Properties/ScriptingApi.cs @@ -1,16 +1,11 @@ using Inno.Scripting.Api; using Inno.Editor.Inspection; -[assembly: ScriptingApiNamespace( - "InnoEditor.Inspection", - "Inno.Editor.Inspection", - ScriptingApiScope.Editor)] - +[assembly: ScriptingApiNamespace("InnoEditor.Inspection", "Inno.Editor.Inspection", ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(InspectionDrawer<>), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(IInspectionIconProvider<>), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(IPropertyDrawer), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(InspectionDrawContext), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetInspectionSelection), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(InspectionDrawerAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(PropertyDrawContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(PropertyDrawerAttribute), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/BooleanPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/BooleanPropertyDrawer.cs index 602e720cc..8040d4c5c 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/BooleanPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/BooleanPropertyDrawer.cs @@ -1,7 +1,6 @@ -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(bool))] internal sealed class BooleanPropertyDrawer : IPropertyDrawer { @@ -14,7 +13,7 @@ internal sealed class BooleanPropertyDrawer : IPropertyDrawer public void Draw(PropertyDrawContext context) { bool value = context.GetValue() is bool current && current; - if (EditorWidget.Checkbox($"##{context.path}", ref value, context.tooltip)) + if (EditorUI.current.widgets.Checkbox($"##{context.path}", ref value, context.tooltip)) { context.SetValue(value); } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/CollectionPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/CollectionPropertyDrawer.cs index fbc04ca41..7f5fca8b7 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/CollectionPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/CollectionPropertyDrawer.cs @@ -1,19 +1,16 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; - using Inno.Core.Serialization; using Inno.Editor.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(IEnumerable<>), useForChildren: true, priority: -20)] -[PropertyDrawer(typeof(IDictionary<,>), useForChildren: true, priority: 20)] -[PropertyDrawer(typeof(IReadOnlyDictionary<,>), useForChildren: true, priority: 20)] +[PropertyDrawer(typeof(IDictionary<, >), useForChildren: true, priority: 20)] +[PropertyDrawer(typeof(IReadOnlyDictionary<, >), useForChildren: true, priority: 20)] internal sealed class CollectionPropertyDrawer : IPropertyDrawer { /// @@ -36,7 +33,7 @@ public void Draw(PropertyDrawContext context) return; } - NativeImGui.TextUnformatted($"Unsupported collection: {context.propertyType.Name}"); + Ui.Text($"Unsupported collection: {context.propertyType.Name}"); } private static void DrawSequence( @@ -44,14 +41,12 @@ private static void DrawSequence( Type elementType ) { List values = EditorCollectionUtility.EnumerateSequence(context.GetValue()); - if (!NativeImGui.TreeNodeEx( - $"Count: {values.Count}##{context.path}_sequence", - (int)(ImGuiTreeNodeFlags.SpanAvailWidth))) + if (!Ui.TreeNode($"Count: {values.Count}##{context.path}_sequence", (ImGuiTreeNodeFlags.SpanAvailWidth))) { return; } - if (!context.isReadOnly && NativeImGui.SmallButton($"+##{context.path}_add")) + if (!context.isReadOnly && Ui.SmallButton($"+##{context.path}_add")) { values.Add(CreateDefault(elementType)); context.SetValue(EditorCollectionUtility.BuildSequence(context.propertyType, elementType, values)); @@ -65,19 +60,15 @@ Type elementType break; } - context.DrawChild( - $"Element {index}", - elementType, - () => EditorCollectionUtility.EnumerateSequence(context.GetValue())[index], - value => - { - List updated = EditorCollectionUtility.EnumerateSequence(context.GetValue()); - updated[index] = value; - context.SetValue(EditorCollectionUtility.BuildSequence(context.propertyType, elementType, updated)); - }); + context.DrawChild($"Element {index}", elementType, () => EditorCollectionUtility.EnumerateSequence(context.GetValue())[index], value => + { + List updated = EditorCollectionUtility.EnumerateSequence(context.GetValue()); + updated[index] = value; + context.SetValue(EditorCollectionUtility.BuildSequence(context.propertyType, elementType, updated)); + }); } - NativeImGui.TreePop(); + Ui.TreePop(); } private static bool DrawSequenceActions( @@ -87,7 +78,7 @@ private static bool DrawSequenceActions( int count ) { bool changed = false; - if (NativeImGui.SmallButton($"Up##{context.path}_{index}_up") && index > 0) + if (Ui.SmallButton($"Up##{context.path}_{index}_up") && index > 0) { List updated = EditorCollectionUtility.EnumerateSequence(context.GetValue()); (updated[index - 1], updated[index]) = (updated[index], updated[index - 1]); @@ -95,8 +86,8 @@ int count changed = true; } - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.SmallButton($"Down##{context.path}_{index}_down") && index < count - 1) + Ui.SameLine(0f, -1f); + if (Ui.SmallButton($"Down##{context.path}_{index}_down") && index < count - 1) { List updated = EditorCollectionUtility.EnumerateSequence(context.GetValue()); (updated[index + 1], updated[index]) = (updated[index], updated[index + 1]); @@ -104,8 +95,8 @@ int count changed = true; } - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.SmallButton($"Remove##{context.path}_{index}_remove")) + Ui.SameLine(0f, -1f); + if (Ui.SmallButton($"Remove##{context.path}_{index}_remove")) { List updated = EditorCollectionUtility.EnumerateSequence(context.GetValue()); updated.RemoveAt(index); @@ -122,24 +113,18 @@ private static void DrawMap( Type valueType ) { List> entries = EnumerateMap(context); - if (!NativeImGui.TreeNodeEx( - $"Count: {entries.Count}##{context.path}_map", - (int)(ImGuiTreeNodeFlags.SpanAvailWidth))) + if (!Ui.TreeNode($"Count: {entries.Count}##{context.path}_map", (ImGuiTreeNodeFlags.SpanAvailWidth))) { return; } - if (!context.isReadOnly && NativeImGui.SmallButton($"+##{context.path}_map_add")) + if (!context.isReadOnly && Ui.SmallButton($"+##{context.path}_map_add")) { object? defaultKey = CreateDefault(keyType); if (defaultKey is not null && !entries.Any(entry => Equals(entry.Key, defaultKey))) { entries.Add(new KeyValuePair(defaultKey, CreateDefault(valueType))); - context.SetValue(EditorCollectionUtility.BuildMap( - context.propertyType, - keyType, - valueType, - entries)); + context.SetValue(EditorCollectionUtility.BuildMap(context.propertyType, keyType, valueType, entries)); } } @@ -147,80 +132,55 @@ Type valueType { int index = i; string errorKey = $"map-error-{index}"; - context.DrawChild( - $"Key {index}", - keyType, - () => EnumerateMap(context)[index].Key, - key => + context.DrawChild($"Key {index}", keyType, () => EnumerateMap(context)[index].Key, key => + { + List> updated = EnumerateMap(context); + bool duplicate = updated.Where(( + _, + candidateIndex + ) => candidateIndex != index).Any(entry => Equals(entry.Key, key)); + if (duplicate) { - List> updated = EnumerateMap(context); - bool duplicate = updated.Where(( - _, - candidateIndex - ) => candidateIndex != index) - .Any(entry => Equals(entry.Key, key)); - if (duplicate) - { - context.SetTextState(errorKey, "Duplicate map keys are not allowed."); - return; - } - - context.ClearTextState(errorKey); - updated[index] = new KeyValuePair(key, updated[index].Value); - context.SetValue(EditorCollectionUtility.BuildMap( - context.propertyType, - keyType, - valueType, - updated)); - }); + context.SetTextState(errorKey, "Duplicate map keys are not allowed."); + return; + } + + context.ClearTextState(errorKey); + updated[index] = new KeyValuePair(key, updated[index].Value); + context.SetValue(EditorCollectionUtility.BuildMap(context.propertyType, keyType, valueType, updated)); + }); if (context.TryGetTextState(errorKey, out string? error)) { - EditorWidget.ColoredText(EditorPalette.error, error!); + EditorUI.current.widgets.ColoredText(EditorPalette.error, error!); } - context.DrawChild( - $"Value {index}", - valueType, - () => EnumerateMap(context)[index].Value, - value => - { - List> updated = EnumerateMap(context); - updated[index] = new KeyValuePair(updated[index].Key, value); - context.SetValue(EditorCollectionUtility.BuildMap( - context.propertyType, - keyType, - valueType, - updated)); - }); - - if (!context.isReadOnly && NativeImGui.SmallButton($"Remove##{context.path}_{index}_map_remove")) + context.DrawChild($"Value {index}", valueType, () => EnumerateMap(context)[index].Value, value => + { + List> updated = EnumerateMap(context); + updated[index] = new KeyValuePair(updated[index].Key, value); + context.SetValue(EditorCollectionUtility.BuildMap(context.propertyType, keyType, valueType, updated)); + }); + if (!context.isReadOnly && Ui.SmallButton($"Remove##{context.path}_{index}_map_remove")) { List> updated = EnumerateMap(context); updated.RemoveAt(index); context.ClearTextState(errorKey); - context.SetValue(EditorCollectionUtility.BuildMap( - context.propertyType, - keyType, - valueType, - updated)); + context.SetValue(EditorCollectionUtility.BuildMap(context.propertyType, keyType, valueType, updated)); break; } } - NativeImGui.TreePop(); + Ui.TreePop(); } private static List> EnumerateMap(PropertyDrawContext context) { - if (EditorCollectionUtility.TryEnumerateMap( - context.GetValue(), - context.propertyType, - out List> entries)) + if (EditorCollectionUtility.TryEnumerateMap(context.GetValue(), context.propertyType, out List> entries)) { return entries; } - return []; + return[]; } private static object? CreateDefault(Type type) diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/EnumPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/EnumPropertyDrawer.cs index 238a34b5b..e6da674b5 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/EnumPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/EnumPropertyDrawer.cs @@ -1,12 +1,10 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(Enum), useForChildren: true, priority: 100)] internal sealed class EnumPropertyDrawer : IPropertyDrawer { @@ -27,7 +25,7 @@ public void Draw(PropertyDrawContext context) } string preview = Enum.GetName(enumType, value) ?? value.ToString() ?? "Unknown"; - if (!EditorWidget.BeginBoundedCombo($"##{context.path}", preview)) + if (!EditorUI.current.widgets.BeginBoundedCombo($"##{context.path}", preview)) { return; } @@ -37,13 +35,13 @@ public void Draw(PropertyDrawContext context) { object candidate = values.GetValue(i)!; string name = Enum.GetName(enumType, candidate) ?? candidate.ToString()!; - if (NativeImGui.Selectable(name, Equals(candidate, value))) + if (Ui.Selectable(name, Equals(candidate, value))) { context.SetValue(candidate); } } - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } private static void DrawFlags( @@ -53,7 +51,7 @@ object value ) { ulong currentBits = ToBits(enumType, value); string preview = value.ToString() ?? currentBits.ToString(); - if (!EditorWidget.BeginBoundedCombo($"##{context.path}", preview)) + if (!EditorUI.current.widgets.BeginBoundedCombo($"##{context.path}", preview)) { return; } @@ -63,24 +61,18 @@ object value { object candidate = values.GetValue(i)!; ulong candidateBits = ToBits(enumType, candidate); - bool selected = candidateBits == 0 - ? currentBits == 0 - : (currentBits & candidateBits) == candidateBits; + bool selected = candidateBits == 0 ? currentBits == 0 : (currentBits & candidateBits) == candidateBits; string name = Enum.GetName(enumType, candidate) ?? candidate.ToString()!; - if (!NativeImGui.Selectable(name, selected, (int)(ImGuiSelectableFlags.NoAutoClosePopups))) + if (!Ui.Selectable(name, selected, (ImGuiSelectableFlags.NoAutoClosePopups))) { continue; } - currentBits = candidateBits == 0 - ? 0 - : selected - ? currentBits & ~candidateBits - : currentBits | candidateBits; + currentBits = candidateBits == 0 ? 0 : selected ? currentBits & ~candidateBits : currentBits | candidateBits; context.SetValue(Enum.ToObject(enumType, currentBits)); } - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } private static ulong ToBits( @@ -98,7 +90,6 @@ object value TypeCode.UInt16 => Convert.ToUInt16(value), TypeCode.UInt32 => Convert.ToUInt32(value), TypeCode.UInt64 => Convert.ToUInt64(value), - _ => throw new InvalidOperationException($"Unsupported enum underlying type '{underlyingType.FullName}'.") - }; + _ => throw new InvalidOperationException($"Unsupported enum underlying type '{underlyingType.FullName}'.")}; } } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/GuidPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/GuidPropertyDrawer.cs index c5b172f44..3ffb93f87 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/GuidPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/GuidPropertyDrawer.cs @@ -1,15 +1,13 @@ +using Inno.Editor.ImGui; using System; using EditorImGui = Inno.Editor.ImGui.ImGui; -using Inno.Native.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(Guid))] internal sealed class GuidPropertyDrawer : IPropertyDrawer { private const int C_BUFFER_SIZE = 64; private const string C_TEXT_STATE = "guid"; - /// /// Renders the value presentation for the current editor frame. /// @@ -19,15 +17,8 @@ internal sealed class GuidPropertyDrawer : IPropertyDrawer public void Draw(PropertyDrawContext context) { Guid value = context.GetValue() is Guid current ? current : Guid.Empty; - string text = context.TryGetTextState(C_TEXT_STATE, out string? editing) - ? editing! - : value.ToString("D"); - if (EditorImGui.InputText( - $"##{context.path}", - ref text, - C_BUFFER_SIZE, - ImGuiInputTextFlags.EnterReturnsTrue) && - Guid.TryParse(text, out Guid parsed)) + string text = context.TryGetTextState(C_TEXT_STATE, out string? editing) ? editing! : value.ToString("D"); + if (EditorImGui.InputText($"##{context.path}", ref text, C_BUFFER_SIZE, ImGuiInputTextFlags.EnterReturnsTrue) && Guid.TryParse(text, out Guid parsed)) { context.SetValue(parsed); context.ClearTextState(C_TEXT_STATE); diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs index f307cbba5..052087445 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/IInspectionPropertyEditService.cs @@ -1,7 +1,6 @@ using System; namespace Inno.Editor.Inspection; - /// /// Applies one root serialized-property mutation through the history mechanism owned by a feature. /// @@ -22,21 +21,29 @@ public interface IInspectionPropertyEditService /// /// The user-facing name for the resulting history entry. /// + /// + /// The current shared gesture key, or null for a discrete edit. + /// + /// + /// An optional public value component; null replaces the complete root value. + /// /// /// when the value changed and the owning feature recorded the edit; /// otherwise, . /// /// - /// Thrown when or is + /// Thrown when or is /// . /// /// - /// Thrown when or is empty. + /// Thrown when or is empty. /// bool ChangeProperty( object owner, string propertyName, Action mutation, - string historyName + string historyName, + string? mergeKey, + string? componentName ); } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/MathematicsPropertyDrawers.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/MathematicsPropertyDrawers.cs index 049b9792a..8a3b1b51e 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/MathematicsPropertyDrawers.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/MathematicsPropertyDrawers.cs @@ -1,23 +1,18 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - using Inno.Core.Mathematics; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; using NumericsVector4 = System.Numerics.Vector4; namespace Inno.Editor.Inspection; - internal static unsafe class ComponentFieldDrawer { internal static float GetWidth(int count) { - float spacing = NativeImGui.Raw.GetStyle()->ItemSpacing.X; - return MathF.Max( - 1f, - (NativeImGui.GetContentRegionAvail().X - spacing * (count - 1)) / count); + float spacing = Ui.GetLayout().itemSpacing.X; + return MathF.Max(1f, (Ui.GetContentRegionAvailable().X - spacing * (count - 1)) / count); } internal static bool Float( @@ -26,7 +21,7 @@ internal static bool Float( ref float value, float width ) { - return EditorWidget.AxisDragFloat(path, label, ref value, width); + return EditorUI.current.widgets.AxisDragFloat(path, label, ref value, width); } internal static bool Int( @@ -35,10 +30,10 @@ internal static bool Int( ref int value, float width ) { - return EditorWidget.AxisDragInt(path, label, ref value, width); + return EditorUI.current.widgets.AxisDragInt(path, label, ref value, width); } - internal static void Next() => NativeImGui.SameLine(0f, -1f); + internal static void Next() => Ui.SameLine(0f, -1f); } [PropertyDrawer(typeof(Vector2))] @@ -54,13 +49,11 @@ public void Draw(PropertyDrawContext context) { Vector2 value = context.GetValue() is Vector2 current ? current : default; float width = ComponentFieldDrawer.GetWidth(2); - bool changed = ComponentFieldDrawer.Float(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Float(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); } } @@ -77,15 +70,14 @@ public void Draw(PropertyDrawContext context) { Vector3 value = context.GetValue() is Vector3 current ? current : default; float width = ComponentFieldDrawer.GetWidth(3); - bool changed = ComponentFieldDrawer.Float(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Float(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Z", ref value.z, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Float(context.path, "Z", ref value.z, width)) + context.SetValue(value, "z"); } } @@ -102,17 +94,17 @@ public void Draw(PropertyDrawContext context) { Vector4 value = context.GetValue() is Vector4 current ? current : default; float width = ComponentFieldDrawer.GetWidth(4); - bool changed = ComponentFieldDrawer.Float(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Float(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Z", ref value.z, width); + if (ComponentFieldDrawer.Float(context.path, "Z", ref value.z, width)) + context.SetValue(value, "z"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "W", ref value.w, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Float(context.path, "W", ref value.w, width)) + context.SetValue(value, "w"); } } @@ -129,13 +121,11 @@ public void Draw(PropertyDrawContext context) { Vector2Int value = context.GetValue() is Vector2Int current ? current : default; float width = ComponentFieldDrawer.GetWidth(2); - bool changed = ComponentFieldDrawer.Int(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Int(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); } } @@ -152,15 +142,14 @@ public void Draw(PropertyDrawContext context) { Vector3Int value = context.GetValue() is Vector3Int current ? current : default; float width = ComponentFieldDrawer.GetWidth(3); - bool changed = ComponentFieldDrawer.Int(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Int(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Z", ref value.z, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Int(context.path, "Z", ref value.z, width)) + context.SetValue(value, "z"); } } @@ -177,17 +166,17 @@ public void Draw(PropertyDrawContext context) { Vector4Int value = context.GetValue() is Vector4Int current ? current : default; float width = ComponentFieldDrawer.GetWidth(4); - bool changed = ComponentFieldDrawer.Int(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Int(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Z", ref value.z, width); + if (ComponentFieldDrawer.Int(context.path, "Z", ref value.z, width)) + context.SetValue(value, "z"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "W", ref value.w, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Int(context.path, "W", ref value.w, width)) + context.SetValue(value, "w"); } } @@ -204,17 +193,17 @@ public void Draw(PropertyDrawContext context) { Rect value = context.GetValue() is Rect current ? current : default; float width = ComponentFieldDrawer.GetWidth(4); - bool changed = ComponentFieldDrawer.Float(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Float(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Float(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "W", ref value.width, width); + if (ComponentFieldDrawer.Float(context.path, "W", ref value.width, width)) + context.SetValue(value, "width"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "H", ref value.height, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Float(context.path, "H", ref value.height, width)) + context.SetValue(value, "height"); } } @@ -231,17 +220,17 @@ public void Draw(PropertyDrawContext context) { RectInt value = context.GetValue() is RectInt current ? current : default; float width = ComponentFieldDrawer.GetWidth(4); - bool changed = ComponentFieldDrawer.Int(context.path, "X", ref value.x, width); + if (ComponentFieldDrawer.Int(context.path, "X", ref value.x, width)) + context.SetValue(value, "x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width); + if (ComponentFieldDrawer.Int(context.path, "Y", ref value.y, width)) + context.SetValue(value, "y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "W", ref value.width, width); + if (ComponentFieldDrawer.Int(context.path, "W", ref value.width, width)) + context.SetValue(value, "width"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Int(context.path, "H", ref value.height, width); - if (changed) - { - context.SetValue(value); - } + if (ComponentFieldDrawer.Int(context.path, "H", ref value.height, width)) + context.SetValue(value, "height"); } } @@ -258,11 +247,8 @@ public void Draw(PropertyDrawContext context) { Color value = context.GetValue() is Color current ? current : default; var nativeValue = new NumericsVector4(value.r, value.g, value.b, value.a); - var flags = context.hdrColor - ? Inno.Native.ImGui.ImGuiColorEditFlags.Hdr | Inno.Native.ImGui.ImGuiColorEditFlags.Float - | Inno.Native.ImGui.ImGuiColorEditFlags.InputRgb | Inno.Native.ImGui.ImGuiColorEditFlags.DisplayRgb - : Inno.Native.ImGui.ImGuiColorEditFlags.None; - if (EditorImGui.ColorEditLinear4($"##{context.path}", ref nativeValue, flags)) + var flags = context.hdrColor ? Inno.Editor.ImGui.ImGuiColorEditFlags.Hdr | Inno.Editor.ImGui.ImGuiColorEditFlags.Float | Inno.Editor.ImGui.ImGuiColorEditFlags.InputRgb | Inno.Editor.ImGui.ImGuiColorEditFlags.DisplayRgb : Inno.Editor.ImGui.ImGuiColorEditFlags.None; + if (Ui.ColorEditLinear4($"##{context.path}", ref nativeValue, flags)) { context.SetValue(new Color(nativeValue.X, nativeValue.Y, nativeValue.Z, nativeValue.W)); } @@ -283,14 +269,13 @@ public void Draw(PropertyDrawContext context) Quaternion value = context.GetValue() is Quaternion current ? current : Quaternion.identity; Vector3 eulerDegrees = value.ToEulerAnglesXYZDegrees(); float width = ComponentFieldDrawer.GetWidth(3); - bool changed = ComponentFieldDrawer.Float(context.path, "X", ref eulerDegrees.x, width); + if (ComponentFieldDrawer.Float(context.path, "X", ref eulerDegrees.x, width)) + context.SetValue(Quaternion.FromEulerAnglesXYZDegrees(eulerDegrees).normalized, "euler.x"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Y", ref eulerDegrees.y, width); + if (ComponentFieldDrawer.Float(context.path, "Y", ref eulerDegrees.y, width)) + context.SetValue(Quaternion.FromEulerAnglesXYZDegrees(eulerDegrees).normalized, "euler.y"); ComponentFieldDrawer.Next(); - changed |= ComponentFieldDrawer.Float(context.path, "Z", ref eulerDegrees.z, width); - if (changed) - { - context.SetValue(Quaternion.FromEulerAnglesXYZDegrees(eulerDegrees).normalized); - } + if (ComponentFieldDrawer.Float(context.path, "Z", ref eulerDegrees.z, width)) + context.SetValue(Quaternion.FromEulerAnglesXYZDegrees(eulerDegrees).normalized, "euler.z"); } } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NullablePropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NullablePropertyDrawer.cs index f4cea9c51..950b3f7c9 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NullablePropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NullablePropertyDrawer.cs @@ -1,10 +1,8 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(Nullable<>))] internal sealed class NullablePropertyDrawer : IPropertyDrawer { @@ -16,15 +14,11 @@ internal sealed class NullablePropertyDrawer : IPropertyDrawer /// public void Draw(PropertyDrawContext context) { - Type underlyingType = Nullable.GetUnderlyingType(context.propertyType) - ?? throw new InvalidOperationException("Nullable drawer requires a nullable property type."); + Type underlyingType = Nullable.GetUnderlyingType(context.propertyType) ?? throw new InvalidOperationException("Nullable drawer requires a nullable property type."); object? value = context.GetValue(); bool hasValue = value is not null; bool previousHasValue = hasValue; - _ = EditorWidget.Checkbox( - $"##{context.path}_has_value", - ref hasValue, - context.tooltip); + _ = EditorUI.current.widgets.Checkbox($"##{context.path}_has_value", ref hasValue, context.tooltip); if (hasValue != previousHasValue) { value = hasValue ? Activator.CreateInstance(underlyingType) : null; @@ -33,16 +27,12 @@ public void Draw(PropertyDrawContext context) if (!hasValue) { - NativeImGui.SameLine(0f, -1f); - NativeImGui.TextUnformatted("Null"); + Ui.SameLine(0f, -1f); + Ui.Text("Null"); return; } - NativeImGui.SameLine(0f, -1f); - context.DrawInlineChild( - "Value", - underlyingType, - context.GetValue, - context.SetValue); + Ui.SameLine(0f, -1f); + context.DrawInlineChild("Value", underlyingType, context.GetValue, value => context.SetValue(value)); } } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NumericPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NumericPropertyDrawer.cs index 3c0b08ff7..c2feafdc9 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NumericPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/NumericPropertyDrawer.cs @@ -1,13 +1,10 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Globalization; -using EditorImGui = Inno.Editor.ImGui.ImGui; - -using Inno.Native.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(byte))] [PropertyDrawer(typeof(sbyte))] [PropertyDrawer(typeof(short))] @@ -23,7 +20,6 @@ internal sealed class NumericPropertyDrawer : IPropertyDrawer { private const int C_BUFFER_SIZE = 128; private const string C_TEXT_STATE = "numeric"; - /// /// Renders the value presentation for the current editor frame. /// @@ -37,13 +33,21 @@ public void Draw(PropertyDrawContext context) if (type == typeof(int)) { int value = rawValue is int current ? current : 0; - bool changed = context.minimum is double minimum && context.maximum is double maximum - ? NativeImGui.SliderInt( - $"##{context.path}", - ref value, - (int)Math.Clamp(Math.Ceiling(minimum), int.MinValue, int.MaxValue), - (int)Math.Clamp(Math.Floor(maximum), int.MinValue, int.MaxValue)) - : NativeImGui.DragInt($"##{context.path}", ref value, 1f, 0, 0, "%d", 0); + bool changed = context.minimum is double minimum + && context.maximum is double maximum ? Ui.SliderInt( + $"##{context.path}", + ref value, + (int)Math.Clamp(Math.Ceiling(minimum), int.MinValue, int.MaxValue), + (int)Math.Clamp(Math.Floor(maximum), int.MinValue, int.MaxValue) + ) : Ui.DragInt( + $"##{context.path}", + ref value, + 1f, + 0, + 0, + "%d", + 0 + ); if (changed) { context.SetValue(value); @@ -55,13 +59,17 @@ public void Draw(PropertyDrawContext context) if (type == typeof(float)) { float value = rawValue is float current ? current : 0f; - bool changed = context.minimum is double minimum && context.maximum is double maximum - ? EditorWidget.CompactSliderFloat( - $"##{context.path}", - ref value, - (float)minimum, - (float)maximum) - : EditorWidget.CompactDragFloat($"##{context.path}", ref value, 0.1f); + bool changed = context.minimum is double minimum + && context.maximum is double maximum ? EditorUI.current.widgets.CompactSliderFloat( + $"##{context.path}", + ref value, + (float)minimum, + (float)maximum + ) : EditorUI.current.widgets.CompactDragFloat( + $"##{context.path}", + ref value, + 0.1f + ); if (changed) { context.SetValue(value); @@ -72,12 +80,7 @@ public void Draw(PropertyDrawContext context) _ = context.TryGetTextState(C_TEXT_STATE, out string? text); text ??= Convert.ToString(rawValue, CultureInfo.InvariantCulture) ?? "0"; - - if (EditorImGui.InputText( - $"##{context.path}", - ref text, - C_BUFFER_SIZE, - ImGuiInputTextFlags.EnterReturnsTrue)) + if (Ui.InputText($"##{context.path}", ref text, C_BUFFER_SIZE, ImGuiInputTextFlags.EnterReturnsTrue)) { if (TryConvert(text, type, out object? converted)) { diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs index 65249476a..7e9f742dd 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/PropertyDrawContext.cs @@ -3,9 +3,9 @@ using Inno.Core.Serialization; using Inno.Editor.Core; using Inno.Editor.Interactions; +using Inno.Editor.ImGui; namespace Inno.Editor.Inspection; - /// /// Encapsulates one editable property path and its drawing services. /// @@ -18,62 +18,50 @@ public sealed class PropertyDrawContext private readonly object m_owner; private readonly string m_rootPropertyName; private readonly bool m_isReadOnly; - /// /// Gets the shared editor context. /// public EditorContext editorContext { get; } - /// /// Gets the active editor interaction entry point. /// public EditorInteractions interactions { get; } - /// /// Gets the stable control path. /// public string path { get; } - /// /// Gets the display label. /// public string label { get; } - /// /// Gets the declared property type. /// public Type propertyType { get; } - /// /// Gets the serialization visibility flags. /// public PropertyVisibility visibility { get; } - /// /// Gets whether assignments are disabled. /// public bool isReadOnly => m_isReadOnly; - /// /// Gets the optional inclusive numeric lower bound supplied by Inspector metadata. /// public double? minimum { get; } - /// /// Gets the optional inclusive numeric upper bound supplied by Inspector metadata. /// public double? maximum { get; } - /// /// Gets optional hover help supplied by Inspector metadata. /// public string? tooltip { get; } - /// /// Gets whether Color values are unclamped linear floating-point channels. /// public bool hdrColor { get; } - internal object owner => m_owner; internal PropertyDrawContext( @@ -99,9 +87,10 @@ internal PropertyDrawContext( this.interactions = interactions ?? throw new ArgumentNullException(nameof(interactions)); m_edits = edits ?? throw new ArgumentNullException(nameof(edits)); m_owner = owner ?? throw new ArgumentNullException(nameof(owner)); - m_rootPropertyName = string.IsNullOrWhiteSpace(rootPropertyName) - ? throw new ArgumentException("The root property name is required.", nameof(rootPropertyName)) - : rootPropertyName; + m_rootPropertyName = string.IsNullOrWhiteSpace(rootPropertyName) ? throw new ArgumentException( + "The root property name is required.", + nameof(rootPropertyName) + ) : rootPropertyName; this.path = path; this.label = label; this.propertyType = propertyType; @@ -123,7 +112,6 @@ internal PropertyDrawContext( /// The latest value returned by the serialized property's getter. /// public object? GetValue() => m_getter(); - /// /// Tries to read text-edit state scoped to this renderer, inspected owner, and property path. /// @@ -140,7 +128,6 @@ public bool TryGetTextState( string key, out string? value ) => m_renderer.TryGetTextState(m_owner, path, key, out value); - /// /// Stores text-edit state scoped to this renderer, inspected owner, and property path. /// @@ -154,7 +141,6 @@ public void SetTextState( string key, string value ) => m_renderer.SetTextState(m_owner, path, key, value); - /// /// Removes text-edit state for this renderer, inspected owner, and property path. /// @@ -162,24 +148,34 @@ string value /// Drawer-local state identity within the current property path. /// public void ClearTextState(string key) => m_renderer.ClearTextState(m_owner, path, key); - /// /// Assigns a value when the property is writable. /// /// /// New value. /// - public void SetValue(object? value) - { + /// + /// An optional public scalar component, or an Euler axis such as euler.x, for precise batch value edits. + /// + public void SetValue( + object? value, + string? componentName = null + ) { if (isReadOnly) return; - if (Equals(m_getter(), value)) + EditorUiSession ui = EditorUiSession.current; + if (Equals(m_getter(), value) && !ui.widgets.isMixedValue) return; - _ = m_edits.ChangeProperty( - m_owner, - m_rootPropertyName, - () => m_setter(value), - $"Change {label}"); + ui.widgets.BeginPropertyEdit(interactions.history, path, $"Change {label}"); + try + { + _ = m_edits.ChangeProperty(m_owner, m_rootPropertyName, () => m_setter(value), $"Change {label}", ui.widgets.editMergeKey, componentName); + } + catch + { + ui.widgets.CancelEdits(); + throw; + } } /// @@ -207,23 +203,8 @@ public void DrawChild( Action setter, bool readOnly = false ) { - PropertyVisibility childVisibility = readOnly || isReadOnly - ? PropertyVisibility.Readonly - : PropertyVisibility.Show; - m_renderer.Draw( - editorContext, - m_owner, - m_owner, - null, - null, - m_rootPropertyName, - $"{path}.{childName}", - childName, - childType, - childVisibility, - getter, - setter, - m_edits); + PropertyVisibility childVisibility = readOnly || isReadOnly ? PropertyVisibility.Readonly : PropertyVisibility.Show; + m_renderer.Draw(editorContext, m_owner, m_owner, null, null, m_rootPropertyName, $"{path}.{childName}", childName, childType, childVisibility, getter, setter, m_edits); } /// @@ -233,7 +214,6 @@ public void DrawChild( /// Nested property. /// public void DrawChild(SerializedProperty property) => DrawChild(GetValue() ?? m_owner, property); - /// /// Draws a nested serialized property with presentation metadata resolved from its immediate owner. /// @@ -262,7 +242,8 @@ SerializedProperty property isReadOnly || !property.canWrite ? PropertyVisibility.Readonly : property.visibility, property.GetValue, property.SetValue, - m_edits); + m_edits + ); } internal void DrawChildMember( @@ -275,9 +256,7 @@ bool readOnly ) { ArgumentNullException.ThrowIfNull(metadataOwner); ArgumentNullException.ThrowIfNull(member); - PropertyVisibility childVisibility = readOnly || isReadOnly - ? PropertyVisibility.Readonly - : PropertyVisibility.Show; + PropertyVisibility childVisibility = readOnly || isReadOnly ? PropertyVisibility.Readonly : PropertyVisibility.Show; m_renderer.Draw( editorContext, m_owner, @@ -291,7 +270,8 @@ bool readOnly childVisibility, getter, setter, - m_edits); + m_edits + ); } /// @@ -319,21 +299,7 @@ public void DrawInlineChild( Action setter, bool readOnly = false ) { - PropertyVisibility childVisibility = readOnly || isReadOnly - ? PropertyVisibility.Readonly - : PropertyVisibility.Show; - m_renderer.DrawInline( - editorContext, - m_owner, - m_owner, - null, - m_rootPropertyName, - $"{path}.{childName}", - childName, - childType, - childVisibility, - getter, - setter, - m_edits); + PropertyVisibility childVisibility = readOnly || isReadOnly ? PropertyVisibility.Readonly : PropertyVisibility.Show; + m_renderer.DrawInline(editorContext, m_owner, m_owner, null, m_rootPropertyName, $"{path}.{childName}", childName, childType, childVisibility, getter, setter, m_edits); } } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializableObjectPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializableObjectPropertyDrawer.cs index d2868a39d..c5d365de8 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializableObjectPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializableObjectPropertyDrawer.cs @@ -1,21 +1,18 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - using Inno.Extensibility.Types; using Inno.Core.Serialization; using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(ISerializable), useForChildren: true, priority: 20)] internal sealed class SerializableObjectPropertyDrawer : IPropertyDrawer { private readonly SerializationRegistry m_serialization; private readonly TypeCacheSnapshot m_types; - internal SerializableObjectPropertyDrawer( TypeCacheSnapshot types, SerializationRegistry serialization @@ -38,26 +35,25 @@ public void Draw(PropertyDrawContext context) value = context.GetValue(); if (value is not ISerializable serializable) { - NativeImGui.TextUnformatted("Null"); + Ui.Text("Null"); return; } string header = $"{value.GetType().Name}##{context.path}_foldout"; - if (!NativeImGui.TreeNodeEx(header, (int)(ImGuiTreeNodeFlags.SpanAvailWidth))) + if (!Ui.TreeNode(header, (ImGuiTreeNodeFlags.SpanAvailWidth))) { return; } IReadOnlyList properties = m_serialization.GetProperties(serializable); - EditorWidget.SectionLayout(() => + EditorUI.current.widgets.SectionLayout(() => { for (int i = 0; i < properties.Count; i++) { context.DrawChild(serializable, properties[i]); } }); - - NativeImGui.TreePop(); + Ui.TreePop(); } private void DrawTypeSelector( @@ -65,14 +61,12 @@ private void DrawTypeSelector( Type runtimeType, object? value ) { - if (!EditorWidget.BeginBoundedCombo( - $"##{context.path}_runtime_type", - value is null ? "Null" : runtimeType.Name)) + if (!EditorUI.current.widgets.BeginBoundedCombo($"##{context.path}_runtime_type", value is null ? "Null" : runtimeType.Name)) { return; } - if (NativeImGui.Selectable("Null", value is null)) + if (Ui.Selectable("Null", value is null)) { context.SetValue(null); } @@ -86,12 +80,12 @@ private void DrawTypeSelector( continue; } - if (NativeImGui.Selectable(candidate.Name, candidate == runtimeType && value is not null)) + if (Ui.Selectable(candidate.Name, candidate == runtimeType && value is not null)) { context.SetValue(Activator.CreateInstance(candidate, nonPublic: true)); } } - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs index cf324c94b..1300a96e0 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/SerializedPropertyRenderer.cs @@ -1,3 +1,4 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Reflection; @@ -9,12 +10,9 @@ using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Interactions; -using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.Inspection; - /// /// Resolves drawers and renders serialized property paths with isolated error handling. /// @@ -27,7 +25,6 @@ public sealed class SerializedPropertyRenderer private readonly EditorInteractions m_interactions; private readonly IInspectionPropertyEditService m_edits; private readonly Logger m_logger; - /// /// Creates a serialized property renderer over one drawer registry and feature-owned edit service. /// @@ -47,7 +44,7 @@ public sealed class SerializedPropertyRenderer /// The host-owned log router used to report isolated drawer failures. /// /// - /// Thrown when , , or + /// Thrown when , , or /// is . /// [ScriptingApiIgnore] @@ -102,7 +99,8 @@ SerializedProperty property property.propertyType, property.visibility, property.GetValue, - property.SetValue); + property.SetValue + ); } /// @@ -163,8 +161,25 @@ public void DrawValue( double? maximum = null ) { ArgumentNullException.ThrowIfNull(edits); - Draw(editorContext, owner, owner, null, null, path, path, label, propertyType, - readOnly ? PropertyVisibility.Readonly : PropertyVisibility.Show, getter, setter, edits, hdrColor, tooltip, minimum, maximum); + Draw( + editorContext, + owner, + owner, + null, + null, + path, + path, + label, + propertyType, + readOnly ? PropertyVisibility.Readonly : PropertyVisibility.Show, + getter, + setter, + edits, + hdrColor, + tooltip, + minimum, + maximum + ); } /// @@ -205,10 +220,49 @@ public void DrawDraftProperty( ArgumentNullException.ThrowIfNull(stateOwner); ArgumentNullException.ThrowIfNull(property); ArgumentNullException.ThrowIfNull(edits); - Draw(editorContext, stateOwner, valueOwner, InspectorMemberMetadata.Resolve(valueOwner.GetType(), property.name), - property, property.name, ownerPath + "." + property.name, property.name, property.propertyType, + Draw( + editorContext, + stateOwner, + valueOwner, + InspectorMemberMetadata.Resolve(valueOwner.GetType(), property.name), + property, + property.name, + ownerPath + "." + property.name, + property.name, + property.propertyType, readOnly ? property.visibility & ~PropertyVisibility.RuntimeSet : property.visibility, - property.GetValue, property.SetValue, edits); + property.GetValue, + property.SetValue, + edits + ); + } + + /// + /// Evaluates the same current-generation presentation attributes used by drawing without creating any UI state. + /// + /// + /// The current object declaring the property's conditional metadata. + /// + /// + /// The root serialized property to inspect. + /// + /// + /// Visibility and editability for this exact owner; callers must combine all selected owners before a batch edit. + /// + [ScriptingApiIgnore] + public (bool isVisible, bool isReadOnly) GetPropertyAvailability( + object owner, + SerializedProperty property + ) { + ArgumentNullException.ThrowIfNull(owner); + ArgumentNullException.ThrowIfNull(property); + bool readOnly = (property.visibility & PropertyVisibility.RuntimeSet) == 0; + MemberInfo? member = InspectorMemberMetadata.Resolve(owner.GetType(), property.name); + if (member is null) + return (true, readOnly); + var context = new InspectorAttributeDrawContext(owner, member, property, property.name, property.name, readOnly); + m_attributes.Update(context, InspectorMemberMetadata.GetAttributes(member)); + return (context.isVisible, context.isReadOnly); } internal void Draw( @@ -230,39 +284,32 @@ internal void Draw( double? minimum = null, double? maximum = null ) { - string displayLabel = EditorWidget.NicifyName(label); + string displayLabel = EditorDisplayNames.Nicify(label); bool isReadOnly = (visibility & PropertyVisibility.RuntimeSet) == 0; Attribute[] attributes = member is null ? [] : InspectorMemberMetadata.GetAttributes(member); bool beginsSection = Array.Exists(attributes, static attribute => attribute is HeaderAttribute); InspectorAttributeDrawContext? attributeContext = null; if (member is not null && attributes.Length > 0) { - attributeContext = new InspectorAttributeDrawContext( - metadataOwner, - member, - property, - path, - displayLabel, - isReadOnly); + attributeContext = new InspectorAttributeDrawContext(metadataOwner, member, property, path, displayLabel, isReadOnly); m_attributes.Update(attributeContext, attributes); if (!attributeContext.isVisible) return; displayLabel = attributeContext.label; isReadOnly = attributeContext.isReadOnly; - if (!beginsSection && !EditorWidget.isSectionContentVisible) + if (!beginsSection && !EditorUI.current.widgets.isSectionContentVisible) return; m_attributes.DrawBefore(attributeContext, attributes); - if (!EditorWidget.isSectionContentVisible) + if (!EditorUI.current.widgets.isSectionContentVisible) return; } - else if (!EditorWidget.isSectionContentVisible) + else if (!EditorUI.current.widgets.isSectionContentVisible) { return; } - if (!beginsSection && !EditorWidget.EnsureSection()) + if (!beginsSection && !EditorUI.current.widgets.EnsureSection()) return; - var context = new PropertyDrawContext( editorContext, m_interactions, @@ -279,13 +326,10 @@ internal void Draw( attributeContext?.tooltip ?? tooltip, getter, setter, - this, hdrColor); - - EditorWidget.PropertyRow( - path, - context.label, - () => DrawContent(context), - tooltip: attributeContext?.tooltip ?? tooltip); + this, + hdrColor + ); + EditorUI.current.widgets.PropertyRow(path, context.label, () => DrawContent(context), tooltip: attributeContext?.tooltip ?? tooltip); if (attributeContext is not null) m_attributes.DrawAfter(attributeContext, attributes); } @@ -311,7 +355,7 @@ internal void DrawInline( owner, rootPropertyName, path, - EditorWidget.NicifyName(label), + EditorDisplayNames.Nicify(label), propertyType, visibility, (visibility & PropertyVisibility.RuntimeSet) == 0, @@ -320,7 +364,8 @@ internal void DrawInline( null, getter, setter, - this); + this + ); DrawContent(context); } @@ -330,20 +375,17 @@ private void DrawContent(PropertyDrawContext context) try { IPropertyDrawer drawer = m_drawers.Resolve(context.propertyType); - EditorWidget.Disabled(context.isReadOnly, () => drawer.Draw(context)); + EditorUI.current.widgets.Disabled(context.isReadOnly, () => drawer.Draw(context)); + EditorUI.current.widgets.CompletePropertyEdit(context.path); failureStates.Remove(context.path); } catch (Exception exception) { - EditorWidget.ColoredText(EditorPalette.error, $"Error: {exception.Message}"); + EditorUI.current.widgets.ColoredText(EditorPalette.error, $"Error: {exception.Message}"); string failureState = $"{exception.GetType().FullName}|{exception.Message}"; - if (!failureStates.TryGetValue(context.path, out string? previous) || - !string.Equals(previous, failureState, StringComparison.Ordinal)) + if (!failureStates.TryGetValue(context.path, out string? previous) || !string.Equals(previous, failureState, StringComparison.Ordinal)) { - m_logger.Write( - LogLevel.Error, - "Inspector failed to draw property '{0}': {1}", - [context.path, exception]); + m_logger.Write(LogLevel.Error, "Inspector failed to draw property '{0}': {1}", [context.path, exception]); failureStates[context.path] = failureState; } } @@ -357,8 +399,7 @@ out string? value ) { ArgumentNullException.ThrowIfNull(owner); value = null; - return m_textStates.TryGetValue(owner, out Dictionary? states) - && states.TryGetValue(CreateStateKey(path, key), out value); + return m_textStates.TryGetValue(owner, out Dictionary? states) && states.TryGetValue(CreateStateKey(path, key), out value); } internal void SetTextState( diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StringPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StringPropertyDrawer.cs index 5d0e86dcc..83697be97 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StringPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StringPropertyDrawer.cs @@ -1,13 +1,11 @@ +using Inno.Editor.ImGui; using EditorImGui = Inno.Editor.ImGui.ImGui; -using Inno.Native.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(string))] internal sealed class StringPropertyDrawer : IPropertyDrawer { private const int C_BUFFER_SIZE = 1024; - /// /// Renders the value presentation for the current editor frame. /// diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StructPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StructPropertyDrawer.cs index 800b1c58b..43b0f0361 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StructPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/StructPropertyDrawer.cs @@ -1,21 +1,18 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; - using Inno.Core.Serialization; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Inspection; - [PropertyDrawer(typeof(ValueType), useForChildren: true, priority: -100)] internal sealed class StructPropertyDrawer : IPropertyDrawer { private static readonly ConditionalWeakTable S_MEMBER_CACHE = new(); - /// /// Renders the value presentation for the current editor frame. /// @@ -28,77 +25,61 @@ public void Draw(PropertyDrawContext context) MemberInfo[] members = GetMembers(context.propertyType); if (members.Length == 0) { - NativeImGui.TextUnformatted(boxedValue.ToString() ?? context.propertyType.Name); + Ui.Text(boxedValue.ToString() ?? context.propertyType.Name); return; } - if (!NativeImGui.TreeNodeEx($"{context.propertyType.Name}##{context.path}", (int)(ImGuiTreeNodeFlags.SpanAvailWidth))) + if (!Ui.TreeNode($"{context.propertyType.Name}##{context.path}", (ImGuiTreeNodeFlags.SpanAvailWidth))) { return; } - EditorWidget.SectionLayout(() => + EditorUI.current.widgets.SectionLayout(() => { for (int i = 0; i < members.Length; i++) { MemberInfo member = members[i]; Type memberType = GetMemberType(member); - bool memberReadOnly = !CanWrite(member) || - (GetVisibility(member) & PropertyVisibility.RuntimeSet) == 0; - context.DrawChildMember( - context.GetValue() ?? boxedValue, - member, - memberType, - () => GetMemberValue(member, context.GetValue() ?? boxedValue), - value => - { - object updated = context.GetValue() ?? Activator.CreateInstance(context.propertyType)!; - SetMemberValue(member, updated, value); - context.SetValue(updated); - }, - memberReadOnly); + bool memberReadOnly = !CanWrite(member) || (GetVisibility(member) & PropertyVisibility.RuntimeSet) == 0; + context.DrawChildMember(context.GetValue() ?? boxedValue, member, memberType, () => GetMemberValue(member, context.GetValue() ?? boxedValue), value => + { + object updated = context.GetValue() ?? Activator.CreateInstance(context.propertyType)!; + SetMemberValue(member, updated, value); + context.SetValue(updated); + }, memberReadOnly); } }); - - NativeImGui.TreePop(); + Ui.TreePop(); } private static MemberInfo[] GetMembers(Type type) { - return S_MEMBER_CACHE.GetValue(type, static value => new MembersBox( - value - .GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(IsRuntimeVisibleMember) - .OrderBy(static member => member.MetadataToken) - .ToArray())).members; + return S_MEMBER_CACHE.GetValue( + type, + static value => new MembersBox(value.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(IsRuntimeVisibleMember).OrderBy(static member => member.MetadataToken).ToArray()) + ).members; } private static bool IsRuntimeVisibleMember(MemberInfo member) { - SerializablePropertyAttribute? attribute = - member.GetCustomAttribute(inherit: true); + SerializablePropertyAttribute? attribute = member.GetCustomAttribute(inherit: true); PropertyVisibility visibility = attribute?.propertyVisibility ?? PropertyVisibility.Show; if ((visibility & PropertyVisibility.RuntimeGet) == 0) return false; - return member switch { - FieldInfo field => - !field.IsStatic && - (attribute is not null || field.IsPublic && !field.IsInitOnly), - PropertyInfo property => - property.GetIndexParameters().Length == 0 && - (attribute is not null || - property.GetGetMethod(nonPublic: true)?.IsPublic == true && - property.GetSetMethod(nonPublic: true)?.IsPublic == true), + FieldInfo field => !field.IsStatic && (attribute is not null || field.IsPublic && !field.IsInitOnly), + PropertyInfo property => property.GetIndexParameters().Length == 0 + && (attribute is not null + || property.GetGetMethod(nonPublic: true)?.IsPublic == true + && property.GetSetMethod(nonPublic: true)?.IsPublic == true), _ => false }; } private static PropertyVisibility GetVisibility(MemberInfo member) { - return member.GetCustomAttribute(true)?.propertyVisibility - ?? PropertyVisibility.Show; + return member.GetCustomAttribute(true)?.propertyVisibility ?? PropertyVisibility.Show; } private static Type GetMemberType(MemberInfo member) @@ -107,8 +88,7 @@ private static Type GetMemberType(MemberInfo member) { FieldInfo field => field.FieldType, PropertyInfo property => property.PropertyType, - _ => throw new InvalidOperationException($"Unsupported struct member '{member.MemberType}'.") - }; + _ => throw new InvalidOperationException($"Unsupported struct member '{member.MemberType}'.")}; } private static object? GetMemberValue( diff --git a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/UnsupportedPropertyDrawer.cs b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/UnsupportedPropertyDrawer.cs index cafc344ac..ad5daf22f 100644 --- a/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/UnsupportedPropertyDrawer.cs +++ b/src/composition/editor/framework/Inno.Editor.Inspection/PropertyDrawing/UnsupportedPropertyDrawer.cs @@ -1,8 +1,7 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using Inno.Editor.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; namespace Inno.Editor.Inspection; - internal sealed class UnsupportedPropertyDrawer : IPropertyDrawer { internal static UnsupportedPropertyDrawer instance { get; } = new(); @@ -16,10 +15,9 @@ internal sealed class UnsupportedPropertyDrawer : IPropertyDrawer public void Draw(PropertyDrawContext context) { object? value = context.GetValue(); - EditorWidget.ColoredText( + EditorUI.current.widgets.ColoredText( EditorPalette.warning, - value is null - ? $"Unsupported {context.propertyType.Name} (null)" - : $"Unsupported {context.propertyType.Name}: {value}"); + value is null ? $"Unsupported {context.propertyType.Name} (null)" : $"Unsupported {context.propertyType.Name}: {value}" + ); } } diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs index 9e5beef28..d758c28d6 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorAction.cs @@ -1,26 +1,24 @@ +using Inno.Editor.Core; using System; namespace Inno.Editor.Interactions; - /// /// Defines one automatically discovered editor operation and owns its complete multi-frame lifecycle. /// public abstract class EditorAction : IDisposable { - private object? m_activeTarget; + private EditorTargetReference? m_activeTarget; + private EditorInteractions? m_activeInteractions; private bool m_isActive; private bool m_isDisposed; - /// /// Gets whether the action currently owns an active multi-frame operation. /// public bool isActive => m_isActive; - /// /// Gets the required target type, or for a targetless action. /// public virtual Type? targetType => null; - internal virtual Type? argumentType => null; internal EditorActionState QueryInternal(EditorActionContext context) @@ -38,11 +36,10 @@ internal void ExecuteInternal(EditorActionContext context) internal bool PresentInternal(EditorActionContext context) { ObjectDisposedException.ThrowIf(m_isDisposed, this); - return m_isActive && Equals(m_activeTarget, context.target) && Present(context); + return m_isActive && m_activeTarget is not null && m_activeTarget.Matches(context.interactions, context.target) && Present(context); } internal void CancelInternal() => Cancel(); - internal void LosePresentationInternal() { if (!m_isActive) @@ -58,7 +55,24 @@ internal void LosePresentationInternal() } } - internal bool IsActiveFor(object? target) => m_isActive && Equals(m_activeTarget, target); + internal bool IsActiveFor(object? target) => m_isActive + && m_activeTarget is not null + && m_activeInteractions is not null + && m_activeTarget.Matches( + m_activeInteractions, + target + ); + /// + /// Resolves the active target afresh for this callback; null means the identity is unavailable. + /// + /// + /// Do not retain the result across callbacks. Activation stores a domain-qualified reference rather than the live object. + /// + protected object? activeTarget => m_activeInteractions is null ? null : m_activeTarget?.Resolve(m_activeInteractions); + /// + /// Gets the host-owned passive context of the active operation, or null after completion. + /// + protected EditorContext? activeEditorContext { get; private set; } /// /// Activates this action for a target and cancels any operation it previously owned. @@ -73,7 +87,9 @@ protected void Activate(EditorActionContext context) { ArgumentNullException.ThrowIfNull(context); Cancel(); - m_activeTarget = context.target; + m_activeTarget = EditorTargetReference.Capture(context.target); + m_activeInteractions = context.interactions; + activeEditorContext = context.editor; m_isActive = true; } @@ -85,6 +101,8 @@ protected void Complete() if (!m_isActive) return; m_activeTarget = null; + m_activeInteractions = null; + activeEditorContext = null; m_isActive = false; OnCompleted(); } @@ -97,6 +115,8 @@ protected void Cancel() if (!m_isActive) return; m_activeTarget = null; + m_activeInteractions = null; + activeEditorContext = null; m_isActive = false; OnCancelled(); } @@ -111,7 +131,6 @@ protected void Cancel() /// The current presentation and availability state. /// protected virtual EditorActionState Query(EditorActionContext context) => EditorActionState.enabled; - /// /// Executes the action for the supplied context. /// @@ -119,7 +138,6 @@ protected void Cancel() /// The editor, area, target, and optional argument for the operation. /// protected abstract void Execute(EditorActionContext context); - /// /// Presents an active action at the current target location. /// @@ -130,7 +148,6 @@ protected void Cancel() /// when the action replaced the target's normal content; otherwise, . /// protected virtual bool Present(EditorActionContext context) => false; - /// /// Runs after an active operation completes successfully. /// @@ -151,10 +168,9 @@ protected virtual void OnCancelled() /// /// The default behavior cancels the active operation. An action that owns an editable value may override this /// method to validate and commit that value. The operation is cancelled automatically when an override returns - /// without calling or . + /// without calling or . /// protected virtual void OnPresentationLost() => Cancel(); - /// /// Cancels active work and releases this action instance. /// @@ -178,13 +194,11 @@ public void Dispose() /// The command argument type accepted by the action. /// public abstract class EditorAction : EditorAction - where TTarget : class { /// /// Gets the concrete type handled by this extension implementation. /// public sealed override Type targetType => typeof(TTarget); - internal sealed override Type argumentType => typeof(TArgument); /// @@ -196,11 +210,10 @@ public abstract class EditorAction : EditorAction /// /// The context that supplies state and services for this operation. /// - protected sealed override EditorActionState Query(EditorActionContext context) - => TryCreate(context, out EditorActionContext typed) - ? Query(typed) - : EditorActionState.hidden; - + protected sealed override EditorActionState Query(EditorActionContext context) => TryCreate( + context, + out EditorActionContext typed + ) ? Query(typed) : EditorActionState.hidden; /// /// Applies the editor action to the supplied interaction context. /// @@ -211,10 +224,9 @@ protected sealed override void Execute(EditorActionContext context) { if (!TryCreate(context, out EditorActionContext typed)) { - throw new InvalidOperationException( - $"Editor action '{GetType().FullName}' requires target '{typeof(TTarget).FullName}' " + - $"and argument '{typeof(TArgument).FullName}'."); + throw new InvalidOperationException($"Editor action '{GetType().FullName}' requires target '{typeof(TTarget).FullName}' " + $"and argument '{typeof(TArgument).FullName}'."); } + Execute(typed); } @@ -227,9 +239,7 @@ protected sealed override void Execute(EditorActionContext context) /// /// The context that supplies state and services for this operation. /// - protected sealed override bool Present(EditorActionContext context) - => TryCreate(context, out EditorActionContext typed) && Present(typed); - + protected sealed override bool Present(EditorActionContext context) => TryCreate(context, out EditorActionContext typed) && Present(typed); /// /// Evaluates the action for a typed target and argument. /// @@ -240,7 +250,6 @@ protected sealed override bool Present(EditorActionContext context) /// The current presentation and availability state. /// protected virtual EditorActionState Query(EditorActionContext context) => EditorActionState.enabled; - /// /// Executes the action for a typed target and argument. /// @@ -248,7 +257,6 @@ protected sealed override bool Present(EditorActionContext context) /// The typed contextual request. /// protected abstract void Execute(EditorActionContext context); - /// /// Presents an active action for a typed target and argument. /// @@ -259,7 +267,6 @@ protected sealed override bool Present(EditorActionContext context) /// when content was presented. /// protected virtual bool Present(EditorActionContext context) => false; - private static bool TryCreate( EditorActionContext context, out EditorActionContext typed @@ -269,6 +276,7 @@ out EditorActionContext typed typed = new EditorActionContext(context, target, argument); return true; } + typed = null!; return false; } @@ -293,11 +301,8 @@ public abstract class EditorArgumentAction : EditorAction /// /// The validated editor action state that represents the completed operation. /// - protected sealed override EditorActionState Query(EditorActionContext context) - => context.target is null && context.argument is TArgument argument - ? Query(new EditorActionArgumentContext(context, argument)) - : EditorActionState.hidden; - + protected sealed override EditorActionState Query(EditorActionContext context) => context.target is null + && context.argument is TArgument argument ? Query(new EditorActionArgumentContext(context, argument)) : EditorActionState.hidden; /// /// Applies the editor action to the supplied interaction context. /// @@ -308,9 +313,9 @@ protected sealed override void Execute(EditorActionContext context) { if (context.target is not null || context.argument is not TArgument argument) { - throw new InvalidOperationException( - $"Editor action '{GetType().FullName}' requires argument '{typeof(TArgument).FullName}'."); + throw new InvalidOperationException($"Editor action '{GetType().FullName}' requires argument '{typeof(TArgument).FullName}'."); } + Execute(new EditorActionArgumentContext(context, argument)); } @@ -324,7 +329,6 @@ protected sealed override void Execute(EditorActionContext context) /// The current presentation and availability state. /// protected virtual EditorActionState Query(EditorActionArgumentContext context) => EditorActionState.enabled; - /// /// Executes the action for a typed argument. /// @@ -344,7 +348,6 @@ protected sealed override void Execute(EditorActionContext context) /// The presentation data type. /// public abstract class EditorPresentationAction : EditorAction - where TTarget : class { /// /// Presents this action through the current editor interaction surface. @@ -355,10 +358,8 @@ public abstract class EditorPresentationAction : EditorA /// /// when the operation succeeds or its condition is satisfied; otherwise, . /// - protected sealed override bool Present(EditorActionContext context) - => context.argument is TPresentation presentation && Present( - new EditorActionContext(context, context.target, presentation)); - + protected sealed override bool Present(EditorActionContext context) => context.argument is TPresentation presentation + && Present(new EditorActionContext(context, context.target, presentation)); /// /// Presents active content using strongly typed presentation data. /// @@ -378,7 +379,6 @@ protected sealed override bool Present(EditorActionContext context) /// The target type accepted by the action. /// public abstract class EditorAction : EditorAction - where TTarget : class { /// /// Gets the concrete type handled by this extension implementation. @@ -397,9 +397,7 @@ public abstract class EditorAction : EditorAction protected sealed override EditorActionState Query(EditorActionContext context) { ArgumentNullException.ThrowIfNull(context); - return context.target is TTarget target - ? Query(new EditorActionContext(context, target)) - : EditorActionState.hidden; + return context.target is TTarget target ? Query(new EditorActionContext(context, target)) : EditorActionState.hidden; } /// @@ -413,9 +411,9 @@ protected sealed override void Execute(EditorActionContext context) ArgumentNullException.ThrowIfNull(context); if (context.target is not TTarget target) { - throw new InvalidOperationException( - $"Editor action '{GetType().FullName}' requires target '{typeof(TTarget).FullName}'."); + throw new InvalidOperationException($"Editor action '{GetType().FullName}' requires target '{typeof(TTarget).FullName}'."); } + Execute(new EditorActionContext(context, target)); } @@ -431,8 +429,7 @@ protected sealed override void Execute(EditorActionContext context) protected sealed override bool Present(EditorActionContext context) { ArgumentNullException.ThrowIfNull(context); - return context.target is TTarget target && - Present(new EditorActionContext(context, target)); + return context.target is TTarget target && Present(new EditorActionContext(context, target)); } /// @@ -445,7 +442,6 @@ protected sealed override bool Present(EditorActionContext context) /// The current presentation and availability state. /// protected virtual EditorActionState Query(EditorActionContext context) => EditorActionState.enabled; - /// /// Executes the action for a strongly typed target. /// @@ -453,7 +449,6 @@ protected sealed override bool Present(EditorActionContext context) /// The contextual request containing the typed target. /// protected abstract void Execute(EditorActionContext context); - /// /// Presents an active action for a strongly typed target. /// diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs index 665bf0cca..505563fa6 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionContext.cs @@ -1,9 +1,7 @@ using System; - using Inno.Editor.Core; namespace Inno.Editor.Interactions; - /// /// Provides contextual state to an editor action. /// @@ -28,7 +26,7 @@ public class EditorActionContext /// An optional placement-specific argument. /// /// - /// Thrown when or is . + /// Thrown when or is . /// /// /// Thrown when is empty. @@ -52,27 +50,22 @@ internal EditorActionContext( /// Gets the shared passive editor context. /// public EditorContext editor { get; } - /// /// Gets the active interaction entry point. /// public EditorInteractions interactions { get; } - /// /// Gets the transactional history used to record reversible mutations performed by this action. /// public IEditorHistory history => interactions.history; - /// /// Gets the stable interaction area. /// public string area { get; } - /// /// Gets the contextual action target. /// public object? target { get; } - /// /// Gets the optional action argument. /// @@ -86,13 +79,11 @@ internal EditorActionContext( /// The target type required by the action. /// public sealed class EditorActionContext : EditorActionContext - where TTarget : class { internal EditorActionContext( EditorActionContext context, TTarget target - ) - : base(context.editor, context.interactions, context.area, target, context.argument) + ) : base(context.editor, context.interactions, context.area, target, context.argument) { this.target = target; } @@ -113,14 +104,12 @@ TTarget target /// The action argument type required by the action. /// public sealed class EditorActionContext : EditorActionContext - where TTarget : class { internal EditorActionContext( EditorActionContext context, TTarget target, TArgument argument - ) - : base(context.editor, context.interactions, context.area, target, argument) + ) : base(context.editor, context.interactions, context.area, target, argument) { this.target = target; this.argument = argument; @@ -130,7 +119,6 @@ TArgument argument /// Gets the strongly typed action target. /// public new TTarget target { get; } - /// /// Gets the strongly typed action argument. /// @@ -148,8 +136,7 @@ public sealed class EditorActionArgumentContext : EditorActionContext internal EditorActionArgumentContext( EditorActionContext context, TArgument argument - ) - : base(context.editor, context.interactions, context.area, target: null, argument: argument) + ) : base(context.editor, context.interactions, context.area, target: null, argument: argument) { this.argument = argument; } diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionRouter.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionRouter.cs index fd8a465c2..7c18f3996 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionRouter.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionRouter.cs @@ -1,23 +1,24 @@ using System; using System.Collections.Generic; using System.Linq; - using Inno.Core.Events; +using Inno.Core.Input; using Inno.Core.Logging; using Inno.Editor.Core; namespace Inno.Editor.Interactions; - internal sealed class EditorActionRouter( EditorExtensionCatalog catalog, EditorContext editor, EditorInteractions interactions, Logger log -) { - private readonly Queue<(string Action, EditorActionContext Context)> m_pending = []; +) +{ + private readonly Queue m_pending = []; private readonly HashSet m_presentationFailures = new(StringComparer.Ordinal); private readonly HashSet m_queryFailures = new(StringComparer.Ordinal); - + private IReadOnlyDictionary? m_byId; + private Dictionary<(KeyCode key, KeyModifier modifiers, string area), string[]>? m_shortcutIndex; internal EditorActionState Query( string action, EditorActionContext context @@ -55,6 +56,7 @@ EditorActionContext context EditorActionState state = registration.action.QueryInternal(context); if (!state.isVisible || !state.isEnabled) return false; + interactions.PrepareOperation(); registration.action.ExecuteInternal(context); return true; } @@ -85,10 +87,7 @@ EditorActionContext context { TryCancel(registration.action, action, "presentation cleanup"); if (m_presentationFailures.Add(action)) - log.Write( - LogLevel.Error, - "Editor action '{0}' presentation failed: {1}", - [action, exception]); + log.Write(LogLevel.Error, "Editor action '{0}' presentation failed: {1}", [action, exception]); return false; } } @@ -109,13 +108,19 @@ EditorActionContext context ) { ArgumentException.ThrowIfNullOrWhiteSpace(action); ArgumentNullException.ThrowIfNull(context); - m_pending.Enqueue((action, context)); + m_pending.Enqueue(new PendingAction(action, context.area, EditorTargetReference.Capture(context.target), context.target is not null, EditorTargetReference.Capture(context.argument), context.argument is not null)); } internal void Flush() { - while (m_pending.TryDequeue(out (string Action, EditorActionContext Context) request)) - _ = Execute(request.Action, request.Context); + while (m_pending.TryDequeue(out PendingAction? request)) + { + object? target = request.target.Resolve(interactions); + object? argument = request.argument.Resolve(interactions); + if (request.hasTarget && target is null || request.hasArgument && argument is null) + continue; + _ = Execute(request.action, new EditorActionContext(editor, interactions, request.area, target, argument)); + } } internal void Clear(IReadOnlyList registrations) @@ -124,6 +129,7 @@ internal void Clear(IReadOnlyList reg m_pending.Clear(); m_presentationFailures.Clear(); m_queryFailures.Clear(); + InvalidateDiscovery(); for (int i = 0; i < registrations.Count; i++) { EditorExtensionCatalog.ActionRegistration registration = registrations[i]; @@ -136,8 +142,14 @@ internal void ResetTransientState() m_pending.Clear(); m_presentationFailures.Clear(); m_queryFailures.Clear(); + InvalidateDiscovery(); } + internal void InvalidateDiscovery() + { + m_byId = null; + m_shortcutIndex = null; + } internal void LosePresentationExcept(object? target) { var visited = new HashSet(ReferenceEqualityComparer.Instance); @@ -152,10 +164,7 @@ internal void LosePresentationExcept(object? target) } catch (Exception exception) { - log.Write( - LogLevel.Error, - "Editor action '{0}' failed while losing presentation focus: {1}", - [registration.attribute.action, exception]); + log.Write(LogLevel.Error, "Editor action '{0}' failed while losing presentation focus: {1}", [registration.attribute.action, exception]); } } } @@ -178,29 +187,43 @@ internal bool DispatchShortcut( string area, object? target ) { - var actionIds = new HashSet(StringComparer.Ordinal); - foreach (EditorExtensionCatalog.ActionRegistration registration in catalog.extensions.actions) - _ = actionIds.Add(registration.id); + if (keyEvent.repeat) + return false; + m_shortcutIndex ??= BuildShortcutIndex(); + KeyModifier modifiers = new HotKeyGesture(keyEvent.key, keyEvent.modifiers).normalizedModifiers; + _ = m_shortcutIndex.TryGetValue((keyEvent.key, modifiers, area), out string[]? exact); + _ = m_shortcutIndex.TryGetValue((keyEvent.key, modifiers, string.Empty), out string[]? global); + if (exact is null && global is null) + return false; var context = new EditorActionContext(editor, interactions, area, target); var candidates = new List(); - foreach (string action in actionIds) + for (int pass = 0; pass < 2; pass++) { - ResolvedAction? resolved = ResolveWithSpecificity(action, context); - if (resolved is null) - { + string[]? actionIds = pass == 0 ? exact : global; + if (actionIds is null) continue; - } - foreach (EditorShortcutAttribute shortcut in GetApplicableShortcuts( - resolved.Value.registration, - area)) + foreach (string action in actionIds) { - HotKeyGesture gesture = CreateGesture(shortcut); - if (!gesture.Matches(keyEvent)) + if (pass == 1 && exact is not null && Array.IndexOf(exact, action) >= 0) + continue; + ResolvedAction? resolved = ResolveWithSpecificity(action, context); + if (resolved is null) continue; - candidates.Add(new ShortcutCandidate(action, resolved.Value, gesture)); - break; + EditorExtensionCatalog.ActionRegistration registration = resolved.Value.registration; + bool hasExact = HasExactShortcut(registration, area); + foreach (EditorShortcutAttribute shortcut in registration.shortcuts) + { + if (hasExact ? !string.Equals(shortcut.area, area, StringComparison.Ordinal) : !string.IsNullOrEmpty(shortcut.area)) + continue; + HotKeyGesture gesture = CreateGesture(shortcut); + if (!gesture.Matches(keyEvent)) + continue; + candidates.Add(new ShortcutCandidate(action, resolved.Value, gesture)); + break; + } } } + candidates.Sort(static ( left, right @@ -212,8 +235,7 @@ internal bool DispatchShortcut( int distanceComparison = left.resolved.targetDistance.CompareTo(right.resolved.targetDistance); if (distanceComparison != 0) return distanceComparison; - int priorityComparison = right.resolved.registration.attribute.priority.CompareTo( - left.resolved.registration.attribute.priority); + int priorityComparison = right.resolved.registration.attribute.priority.CompareTo(left.resolved.registration.attribute.priority); if (priorityComparison != 0) return priorityComparison; return string.Compare(left.action, right.action, StringComparison.Ordinal); @@ -224,6 +246,7 @@ internal bool DispatchShortcut( if (Execute(candidate.action, context)) return true; } + return false; } @@ -232,37 +255,37 @@ private bool TryResolveShortcut( string area, out HotKeyGesture gesture ) { - EditorShortcutAttribute? best = registration is null - ? null - : GetApplicableShortcuts(registration, area).FirstOrDefault(); - if (best is null) + if (registration is not null) { - gesture = default; - return false; + bool hasExact = HasExactShortcut(registration, area); + foreach (EditorShortcutAttribute shortcut in registration.shortcuts) + { + if (hasExact ? !string.Equals(shortcut.area, area, StringComparison.Ordinal) : !string.IsNullOrEmpty(shortcut.area)) + continue; + gesture = CreateGesture(shortcut); + return true; + } } - gesture = CreateGesture(best); - return true; + gesture = default; + return false; } private EditorExtensionCatalog.ActionRegistration? Resolve( string action, EditorActionContext context - ) - => ResolveWithSpecificity(action, context)?.registration; - + ) => ResolveWithSpecificity(action, context)?.registration; private ResolvedAction? ResolveWithSpecificity( string action, EditorActionContext context ) { EditorExtensionCatalog.ActionRegistration? best = null; int bestDistance = int.MaxValue; - foreach (EditorExtensionCatalog.ActionRegistration registration in catalog.extensions.actions) + if (!Index.TryGetValue(action, out EditorExtensionCatalog.ActionRegistration[]? registrations)) + return null; + foreach (EditorExtensionCatalog.ActionRegistration registration in registrations) { - if (!string.Equals(registration.id, action, StringComparison.Ordinal)) - continue; bool exactArea = registration.area is not null; - if (exactArea && - !string.Equals(registration.area, context.area, StringComparison.Ordinal)) + if (exactArea && !string.Equals(registration.area, context.area, StringComparison.Ordinal)) { continue; } @@ -271,52 +294,112 @@ EditorActionContext context Type? targetType = registration.targetType; if (targetType is not null) { - if (context.target is null || - !EditorTypeDistance.TryGet(context.target.GetType(), targetType, out distance)) + if (context.target is null || !EditorTypeDistance.TryGet(context.target.GetType(), targetType, out distance)) { continue; } } + Type? argumentType = registration.argumentType; - if (argumentType is not null && - (context.argument is null || !argumentType.IsInstanceOfType(context.argument))) + if (argumentType is not null && (context.argument is null || !argumentType.IsInstanceOfType(context.argument))) { continue; } bool bestExactArea = best?.area is not null; - if (best is null || - exactArea && !bestExactArea || - exactArea == bestExactArea && - (distance < bestDistance || - distance == bestDistance && registration.attribute.priority > best.attribute.priority || - distance == bestDistance && registration.attribute.priority == best.attribute.priority && - string.Compare(registration.type.FullName, best.type.FullName, StringComparison.Ordinal) < 0)) + if (best is null + || exactArea + && !bestExactArea + || exactArea == bestExactArea + && (distance < bestDistance + || distance == bestDistance + && registration.attribute.priority > best.attribute.priority + || distance == bestDistance + && registration.attribute.priority == best.attribute.priority + && string.Compare( + registration.type.FullName, + best.type.FullName, + StringComparison.Ordinal + ) < 0)) { best = registration; bestDistance = distance; } } - return best is null - ? null - : new ResolvedAction(best, bestDistance, best.area is not null); + + return best is null ? null : new ResolvedAction(best, bestDistance, best.area is not null); } - private HotKeyGesture CreateGesture(EditorShortcutAttribute shortcut) - => shortcut.primary - ? HotKeyGesture.Primary(shortcut.key, editor.keyboard, shortcut.modifiers) - : new HotKeyGesture(shortcut.key, shortcut.modifiers, editor.keyboard.superModifierLabel); + internal IReadOnlyList BuildUnplacedCommands( + EditorActionContext context, + IReadOnlySet placed + ) { + var result = new List(); + foreach (string id in Index.Keys) + { + if (placed.Contains(id)) + continue; + EditorActionState state = Query(id, context); + if (!state.isVisible) + continue; + string label = state.displayName ?? id; + result.Add(new EditorMenuItem(label, id, 0, false, state)); + } - private static IEnumerable GetApplicableShortcuts( + result.Sort(static ( + left, + right + ) => StringComparer.OrdinalIgnoreCase.Compare(left.label, right.label)); + return result; + } + + private IReadOnlyDictionary Index => m_byId ??= catalog.extensions.actions.GroupBy( + static registration => registration.id, + StringComparer.Ordinal + ).ToDictionary( + static group => group.Key, + static group => group.ToArray(), + StringComparer.Ordinal + ); + + private HotKeyGesture CreateGesture(EditorShortcutAttribute shortcut) => shortcut.primary ? HotKeyGesture.Primary( + shortcut.key, + editor.keyboard, + shortcut.modifiers + ) : new HotKeyGesture( + shortcut.key, + shortcut.modifiers, + editor.keyboard.superModifierLabel + ); + private Dictionary<(KeyCode key, KeyModifier modifiers, string area), string[]> BuildShortcutIndex() + { + var candidates = new Dictionary<(KeyCode key, KeyModifier modifiers, string area), HashSet>(); + foreach (EditorExtensionCatalog.ActionRegistration registration in catalog.extensions.actions) + { + foreach (EditorShortcutAttribute shortcut in registration.shortcuts) + { + HotKeyGesture gesture = CreateGesture(shortcut); + string area = string.IsNullOrEmpty(shortcut.area) ? registration.area ?? string.Empty : shortcut.area; + var key = (gesture.key, gesture.normalizedModifiers, area); + if (!candidates.TryGetValue(key, out HashSet? actions)) + candidates.Add(key, actions = new(StringComparer.Ordinal)); + actions.Add(registration.id); + } + } + return candidates.ToDictionary(static pair => pair.Key, + static pair => pair.Value.OrderBy(static value => value, StringComparer.Ordinal).ToArray()); + } + + private static bool HasExactShortcut( EditorExtensionCatalog.ActionRegistration registration, string area ) { - bool hasExact = registration.shortcuts.Any(shortcut => - !string.IsNullOrEmpty(shortcut.area) && - string.Equals(shortcut.area, area, StringComparison.Ordinal)); - return registration.shortcuts.Where(shortcut => hasExact - ? string.Equals(shortcut.area, area, StringComparison.Ordinal) - : string.IsNullOrEmpty(shortcut.area)); + foreach (EditorShortcutAttribute shortcut in registration.shortcuts) + { + if (!string.IsNullOrEmpty(shortcut.area) && string.Equals(shortcut.area, area, StringComparison.Ordinal)) + return true; + } + return false; } private void TryCancel( @@ -330,19 +413,23 @@ string phase } catch (Exception exception) { - log.Write( - LogLevel.Error, - "Editor action '{0}' failed during {1}: {2}", - [actionId, phase, exception]); + log.Write(LogLevel.Error, "Editor action '{0}' failed during {1}: {2}", [actionId, phase, exception]); } } + private sealed record PendingAction( + string action, + string area, + EditorTargetReference target, + bool hasTarget, + EditorTargetReference argument, + bool hasArgument + ); private readonly record struct ResolvedAction( EditorExtensionCatalog.ActionRegistration registration, int targetDistance, bool exactArea ); - private readonly record struct ShortcutCandidate( string action, ResolvedAction resolved, diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs index 5d3fd141c..a0e834a59 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/EditorActionState.cs @@ -1,5 +1,4 @@ namespace Inno.Editor.Interactions; - /// /// Describes the current presentation and availability of an editor action. /// @@ -20,48 +19,51 @@ public readonly record struct EditorActionState /// /// An optional contextual label that replaces the static placement label. /// + /// + /// A concise explanation shown when an otherwise visible action cannot execute. + /// public EditorActionState( bool isVisible, bool isEnabled, bool isChecked = false, - string? displayName = null + string? displayName = null, + string? unavailableReason = null ) { this.isVisible = isVisible; this.isEnabled = isEnabled; this.isChecked = isChecked; this.displayName = displayName; + this.unavailableReason = isEnabled ? null : unavailableReason; } /// /// Gets whether the action should be displayed. /// public bool isVisible { get; } - /// /// Gets whether the action can execute. /// public bool isEnabled { get; } - /// /// Gets whether the action is currently checked. /// public bool isChecked { get; } - /// /// Gets an optional contextual display name. /// public string? displayName { get; } - + /// + /// Gets the current execution barrier, or null when no specific explanation is available. + /// + public string? unavailableReason { get; } /// /// Gets a visible and enabled action state. /// public static EditorActionState enabled => new(true, true); - /// /// Gets a visible but disabled action state. /// public static EditorActionState disabled => new(true, false); - /// /// Gets a hidden action state. /// diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs index 0532e6de6..834c45362 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Actions/HotKeyGesture.cs @@ -108,6 +108,11 @@ internal bool Matches(KeyPressedEvent keyEvent) keyEvent.key == key && Normalize(key, keyEvent.modifiers) == Normalize(key, modifiers); + /// + /// Gets the normalized modifiers used by generation-owned shortcut indexes. + /// + internal KeyModifier normalizedModifiers => Normalize(key, modifiers); + private static KeyModifier Normalize( KeyCode key, KeyModifier value diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Discovery/EditorExtensionCatalog.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Discovery/EditorExtensionCatalog.cs index 93247e844..b6c9fd6e5 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Discovery/EditorExtensionCatalog.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Discovery/EditorExtensionCatalog.cs @@ -2,14 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; - using Inno.Core.Execution; using Inno.Core.Logging; using Inno.Extensibility.Types; using Inno.Editor.Core; namespace Inno.Editor.Interactions; - internal sealed class EditorExtensionCatalog : TypeRegistry { private readonly EditorContext m_context; @@ -24,7 +22,6 @@ internal sealed class EditorExtensionCatalog : TypeRegistry hostServices, Action extensionsChanged - ) - : base(types) + ) : base(types) { m_context = context ?? throw new ArgumentNullException(nameof(context)); m_interactions = interactions ?? throw new ArgumentNullException(nameof(interactions)); m_log = log ?? throw new ArgumentNullException(nameof(log)); ArgumentNullException.ThrowIfNull(hostServices); - m_hostServices = hostServices.Select(static service => - service ?? throw new ArgumentException("Host services cannot contain null.", nameof(hostServices))) - .ToArray(); + m_hostServices = hostServices.Select(static service => service ?? throw new ArgumentException("Host services cannot contain null.", nameof(hostServices))).ToArray(); m_extensionsChanged = extensionsChanged ?? throw new ArgumentNullException(nameof(extensionsChanged)); m_state = new EditorExtensionStateStore(context, log); } @@ -57,18 +51,18 @@ internal Snapshot extensions } } - internal bool TryGetModule(out TModule? module) where TModule : EditorModule + internal bool TryGetModule(out TModule? module) + where TModule : EditorModule { Snapshot? snapshot = m_active; if (snapshot is not null) foreach (ModuleRegistration registration in snapshot.modules) - if (registration.module is TModule candidate - && snapshot.startedModules.Contains(candidate) - && !snapshot.quarantinedModules.Contains(candidate)) + if (registration.module is TModule candidate && snapshot.startedModules.Contains(candidate) && !snapshot.quarantinedModules.Contains(candidate)) { module = candidate; return true; } + module = null; return false; } @@ -90,12 +84,11 @@ internal void UpdateModules() catch (Exception exception) { snapshot.quarantinedModules.Add(registration.module); - LogError("Editor module '{0}' failed to update and was quarantined: {1}", - registration.attribute.id, - exception); + LogError("Editor module '{0}' failed to update and was quarantined: {1}", registration.attribute.id, exception); continue; } } + m_state.Update(m_context.frame.totalTime, GetAvailableState(snapshot), GetAvailablePanels(snapshot)); } @@ -112,11 +105,11 @@ internal bool TryTogglePanel(string panelId) for (int i = 0; i < snapshot.panels.Length; i++) { PanelRegistration registration = snapshot.panels[i]; - if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || - snapshot.quarantinedPanels.Contains(registration.panel)) + if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || snapshot.quarantinedPanels.Contains(registration.panel)) { continue; } + registration.panel.isOpen = !registration.panel.isOpen; if (registration.panel.isOpen) m_pendingPanelFocus.Add(panelId); @@ -124,6 +117,25 @@ internal bool TryTogglePanel(string panelId) m_pendingPanelFocus.Remove(panelId); return true; } + + return false; + } + + internal IReadOnlyList GetPanelDeclarations() => extensions.panelDeclarations; + internal bool TryGetPanelState( + string panelId, + out bool isOpen + ) { + Snapshot snapshot = extensions; + foreach (PanelRegistration registration in snapshot.panels) + { + if (registration.attribute.id != panelId || snapshot.quarantinedPanels.Contains(registration.panel)) + continue; + isOpen = registration.panel.isOpen; + return true; + } + + isOpen = false; return false; } @@ -134,15 +146,16 @@ internal bool TryOpenPanel(string panelId) for (int i = 0; i < snapshot.panels.Length; i++) { PanelRegistration registration = snapshot.panels[i]; - if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || - snapshot.quarantinedPanels.Contains(registration.panel)) + if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || snapshot.quarantinedPanels.Contains(registration.panel)) { continue; } + registration.panel.isOpen = true; m_pendingPanelFocus.Add(panelId); return true; } + return false; } @@ -153,20 +166,20 @@ internal bool TryClosePanel(string panelId) for (int i = 0; i < snapshot.panels.Length; i++) { PanelRegistration registration = snapshot.panels[i]; - if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || - snapshot.quarantinedPanels.Contains(registration.panel)) + if (!string.Equals(registration.attribute.id, panelId, StringComparison.Ordinal) || snapshot.quarantinedPanels.Contains(registration.panel)) { continue; } + registration.panel.isOpen = false; m_pendingPanelFocus.Remove(panelId); return true; } + return false; } internal bool TakePanelFocusRequest(string panelId) => m_pendingPanelFocus.Remove(panelId); - internal void PrepareShutdown() { if (m_active is not null) @@ -174,7 +187,6 @@ internal void PrepareShutdown() } internal ActionRegistration[] GetActionsForShutdown() => m_active?.actions ?? []; - internal void QuarantinePanel( Snapshot snapshot, PanelRegistration registration, @@ -188,9 +200,7 @@ Exception exception registration.panel.isOpen = false; m_diagnostics.ReportPanelFailure(registration.attribute.id, exception); m_diagnostics.Commit(); - LogError("Editor panel '{0}' failed to draw and was quarantined: {1}", - registration.attribute.id, - exception); + LogError("Editor panel '{0}' failed to draw and was quarantined: {1}", registration.attribute.id, exception); } internal void QuarantineModal( @@ -216,6 +226,7 @@ internal void Shutdown(bool saveState = true) AttemptCleanup(() => m_state.PrepareShutdown(GetAvailableState(active), GetAvailablePanels(active)), failures); AttemptCleanup(() => Deactivate(active), failures); } + m_active = null; NotifyExtensionsChanged(); m_diagnostics.Dispose(); @@ -235,99 +246,71 @@ internal void Shutdown(bool saveState = true) protected override Snapshot Build(TypeCacheSnapshot types) { CapturePanelStates(); - Type[] moduleTypes = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .ToArray(); + Type[] moduleTypes = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).ToArray(); var activator = new EditorExtensionActivator( m_context, m_interactions, moduleTypes, types.types.Select(typeRef => typeRef.Resolve(types)).ToArray(), m_hostServices, - m_active?.instances); - - ModuleRegistration[] modules = moduleTypes - .Where(activator.CanCreate) - .Select(type => new ModuleRegistration( - type.GetCustomAttribute(false)!, - type, - activator.CreateModule(type))) - .OrderBy(static value => value.attribute.order) - .ThenBy(static value => value.type.FullName, StringComparer.Ordinal) - .ToArray(); - - ActionRegistration[] actions = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .Select(type => CreateActionRegistration(type, activator)) - .ToArray(); + m_active?.instances + ); + ModuleRegistration[] modules = moduleTypes.Where(activator.CanCreate).Select(type => new ModuleRegistration(type.GetCustomAttribute(false)!, type, activator.CreateModule(type))).OrderBy(static value => value.attribute.order).ThenBy( + static value => value.type.FullName, + StringComparer.Ordinal + ).ToArray(); + ActionRegistration[] actions = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).Select(type => CreateActionRegistration(type, activator)).ToArray(); ValidateActions(actions); ValidateShortcuts(actions); ValidateToolbars(actions); - - MenuSourceRegistration[] menuSources = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .SelectMany(type => CreateMenuSourceRegistrations(type, activator)) - .OrderByDescending(static value => value.priority) - .ThenBy(static value => value.type.FullName, StringComparer.Ordinal) - .ToArray(); - - DropRegistration[] drops = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .SelectMany(type => CreateDropRegistrations(type, activator)) - .ToArray(); + MenuSourceRegistration[] menuSources = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).SelectMany(type => CreateMenuSourceRegistrations(type, activator)).OrderByDescending(static value => value.priority).ThenBy( + static value => value.type.FullName, + StringComparer.Ordinal + ).ToArray(); + DropRegistration[] drops = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).SelectMany(type => CreateDropRegistrations(type, activator)).ToArray(); ValidateDrops(drops); - - PanelRegistration[] panels = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .Select(type => CreatePanelRegistration(type, activator)) - .OrderBy(static value => value.attribute.order) - .ThenBy(static value => value.type.FullName, StringComparer.Ordinal) - .ToArray(); + PanelRegistration[] panels = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).Select(type => CreatePanelRegistration(type, activator)).OrderBy(static value => value.attribute.order).ThenBy( + static value => value.type.FullName, + StringComparer.Ordinal + ).ToArray(); ValidatePanels(panels); ValidateExtensionIds(modules, panels); - - ModalRegistration[] modals = types.GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .Select(type => CreateModalRegistration(type, activator)) - .OrderBy(static value => value.attribute.order) - .ThenBy(static value => value.type.FullName, StringComparer.Ordinal) - .ToArray(); + ModalRegistration[] modals = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).Select(type => CreateModalRegistration(type, activator)).OrderBy(static value => value.attribute.order).ThenBy( + static value => value.type.FullName, + StringComparer.Ordinal + ).ToArray(); ValidateModals(modals); - - HistoryHandlerRegistration[] historyHandlers = types - .GetTypesWithAttribute() - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.FullName, StringComparer.Ordinal) - .Where(activator.CanCreate) - .Select(type => CreateHistoryHandlerRegistration(type, activator)) - .OrderBy(static value => value.attribute.kind, StringComparer.Ordinal) - .ThenBy(static value => value.type.FullName, StringComparer.Ordinal) - .ToArray(); + HistoryHandlerRegistration[] historyHandlers = types.GetTypesWithAttribute().Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.FullName, + StringComparer.Ordinal + ).Where(activator.CanCreate).Select(type => CreateHistoryHandlerRegistration(type, activator)).OrderBy( + static value => value.attribute.kind, + StringComparer.Ordinal + ).ThenBy( + static value => value.type.FullName, + StringComparer.Ordinal + ).ToArray(); ValidateHistoryHandlers(historyHandlers); - StateRegistration[] state = CreateStateRegistrations(modules, panels); - - return new Snapshot( - modules, - actions, - menuSources, - drops, - panels, - modals, - historyHandlers, - state, - activator.instances.ToArray()); + return new Snapshot(modules, actions, menuSources, drops, panels, modals, historyHandlers, state, activator.instances.ToArray()); } /// @@ -345,23 +328,18 @@ Snapshot candidate ) { if (m_activation is not null) throw new InvalidOperationException("An extension generation transition is already active."); - + m_interactions.PrepareOperation(); if (previous is not null) CapturePanelStates(previous); if (previous is not null) m_state.Save(GetAvailableState(previous), GetAvailablePanels(previous)); - - var existing = previous is null - ? new HashSet(ReferenceEqualityComparer.Instance) - : new HashSet(previous.instances, ReferenceEqualityComparer.Instance); - EditorHistory.HandlerUpdate handlers = m_interactions.historyHost.PrepareHandlerUpdate( - CreateHistoryHandlerMap(candidate.historyHandlers)); + var existing = previous is null ? new HashSet(ReferenceEqualityComparer.Instance) : new HashSet(previous.instances, ReferenceEqualityComparer.Instance); + EditorHistory.HandlerUpdate handlers = m_interactions.historyHost.PrepareHandlerUpdate(CreateHistoryHandlerMap(candidate.historyHandlers)); var activation = new ActivationState(previous, candidate, existing, handlers); m_activation = activation; m_staging = candidate; m_interactions.PrepareGenerationTransition(); handlers.Activate(); - if (previous is not null) { foreach (ModuleRegistration registration in candidate.modules) @@ -369,6 +347,7 @@ Snapshot candidate if (existing.Contains(registration.module) && previous.startedModules.Contains(registration.module)) candidate.startedModules.Add(registration.module); } + foreach (PanelRegistration registration in candidate.panels) { if (existing.Contains(registration.panel) && previous.attachedPanels.Contains(registration.panel)) @@ -378,7 +357,6 @@ Snapshot candidate if (previous is not null) RetirePrevious(previous, candidate, activation); - for (int i = 0; i < candidate.modules.Length; i++) { ModuleRegistration registration = candidate.modules[i]; @@ -390,10 +368,9 @@ Snapshot candidate } var retainedPanelIds = new HashSet( - candidate.panels - .Where(registration => existing.Contains(registration.panel)) - .Select(static registration => registration.attribute.id), - StringComparer.Ordinal); + candidate.panels.Where(registration => existing.Contains(registration.panel)).Select(static registration => registration.attribute.id), + StringComparer.Ordinal + ); m_diagnostics.RetainPanels(retainedPanelIds); for (int i = 0; i < candidate.panels.Length; i++) { @@ -418,6 +395,7 @@ Snapshot candidate LogError("Editor panel '{0}' failed to attach: {1}", registration.attribute.id, exception); } } + m_diagnostics.Commit(); m_state.Restore(GetAvailableState(candidate)); m_active = candidate; @@ -441,7 +419,6 @@ Snapshot candidate m_staging = null; if (activation is null || !ReferenceEquals(activation.candidate, candidate)) return; - List failures = []; AttemptCleanup(activation.handlers.Rollback, failures); AttemptCleanup(m_interactions.RollbackGenerationTransition, failures); @@ -451,20 +428,20 @@ Snapshot candidate AttemptCleanup(() => DetachPanel(registration, "activation rollback"), failures); candidate.attachedPanels.Remove(registration.panel); } + for (int i = activation.startedModules.Count - 1; i >= 0; i--) { ModuleRegistration registration = activation.startedModules[i]; AttemptCleanup(() => StopModule(registration, "activation rollback"), failures); candidate.startedModules.Remove(registration.module); } + AttemptCleanup(() => RestorePrevious(activation), failures); m_activation = null; - - var previousPanelIds = previous is null - ? new HashSet(StringComparer.Ordinal) - : new HashSet( - GetAvailablePanels(previous).Select(static registration => registration.attribute.id), - StringComparer.Ordinal); + var previousPanelIds = previous is null ? new HashSet(StringComparer.Ordinal) : new HashSet( + GetAvailablePanels(previous).Select(static registration => registration.attribute.id), + StringComparer.Ordinal + ); m_diagnostics.RetainPanels(previousPanelIds); m_diagnostics.Commit(); ThrowCleanupFailures(failures); @@ -486,7 +463,6 @@ Snapshot currentSnapshot ActivationState? activation = m_activation; if (activation is null || !ReferenceEquals(activation.candidate, currentSnapshot)) return; - activation.handlers.Complete(); m_interactions.CompleteGenerationTransition(); NotifyExtensionsChanged(); @@ -502,9 +478,7 @@ Snapshot currentSnapshot /// protected override void DisposeSnapshot(Snapshot snapshot) { - HashSet? retained = m_active is null - ? null - : new HashSet(m_active.instances, ReferenceEqualityComparer.Instance); + HashSet? retained = m_active is null ? null : new HashSet(m_active.instances, ReferenceEqualityComparer.Instance); DisposeExtensions(snapshot.instances.Where(instance => retained?.Contains(instance) != true)); } @@ -519,6 +493,7 @@ private void Deactivate(Snapshot snapshot) AttemptCleanup(() => DetachPanel(registration, "shutdown"), failures); snapshot.attachedPanels.Remove(registration.panel); } + for (int i = snapshot.modules.Length - 1; i >= 0; i--) { ModuleRegistration registration = snapshot.modules[i]; @@ -527,6 +502,7 @@ private void Deactivate(Snapshot snapshot) AttemptCleanup(() => StopModule(registration, "shutdown"), failures); snapshot.startedModules.Remove(registration.module); } + if (ReferenceEquals(m_active, snapshot)) m_active = null; m_diagnostics.RetainPanels(new HashSet(StringComparer.Ordinal)); @@ -549,10 +525,10 @@ ActivationState activation DetachPanel(registration, "generation retirement"); previous.attachedPanels.Remove(registration.panel); } + for (int i = previous.modules.Length - 1; i >= 0; i--) { - if (!retained.Contains(previous.modules[i].module) && - previous.startedModules.Contains(previous.modules[i].module)) + if (!retained.Contains(previous.modules[i].module) && previous.startedModules.Contains(previous.modules[i].module)) { activation.stoppedPreviousModules.Add(previous.modules[i]); StopModule(previous.modules[i], "generation retirement"); @@ -579,12 +555,10 @@ private void RestorePrevious(ActivationState activation) { previous.quarantinedModules.Add(registration.module); failures.Add(exception); - LogError( - "Editor module '{0}' failed while restoring the previous generation: {1}", - registration.attribute.id, - exception); + LogError("Editor module '{0}' failed while restoring the previous generation: {1}", registration.attribute.id, exception); } } + for (int i = activation.detachedPreviousPanels.Count - 1; i >= 0; i--) { PanelRegistration registration = activation.detachedPreviousPanels[i]; @@ -599,12 +573,10 @@ private void RestorePrevious(ActivationState activation) failures.Add(exception); registration.panel.isOpen = false; m_diagnostics.ReportPanelFailure(registration.attribute.id, exception); - LogError( - "Editor panel '{0}' failed while restoring the previous generation: {1}", - registration.attribute.id, - exception); + LogError("Editor panel '{0}' failed while restoring the previous generation: {1}", registration.attribute.id, exception); } } + ThrowCleanupFailures(failures); } @@ -618,28 +590,13 @@ private void CapturePanelStates(Snapshot snapshot) { foreach (PanelRegistration registration in snapshot.panels) { - ReadOnlyMemory payload = registration.panel is IEditorPanelReloadState reloadable - ? reloadable.CaptureReloadState().ToArray() - : ReadOnlyMemory.Empty; - m_panelStates[registration.attribute.id] = new PanelState( - registration.panel.isOpen, - payload); + ReadOnlyMemory payload = registration.panel is IEditorPanelReloadState reloadable ? reloadable.CaptureReloadState().ToArray() : ReadOnlyMemory.Empty; + m_panelStates[registration.attribute.id] = new PanelState(registration.panel.isOpen, payload); } } - private static StateRegistration[] GetAvailableState(Snapshot snapshot) - => snapshot.state - .Where(registration => registration.kind != StateOwnerKind.Panel || - !snapshot.quarantinedPanels.Contains((EditorPanel)registration.owner)) - .Where(registration => registration.kind != StateOwnerKind.Module || - !snapshot.quarantinedModules.Contains((EditorModule)registration.owner)) - .ToArray(); - - private static PanelRegistration[] GetAvailablePanels(Snapshot snapshot) - => snapshot.panels - .Where(registration => !snapshot.quarantinedPanels.Contains(registration.panel)) - .ToArray(); - + private static StateRegistration[] GetAvailableState(Snapshot snapshot) => snapshot.state.Where(registration => registration.kind != StateOwnerKind.Panel || !snapshot.quarantinedPanels.Contains((EditorPanel)registration.owner)).Where(registration => registration.kind != StateOwnerKind.Module || !snapshot.quarantinedModules.Contains((EditorModule)registration.owner)).ToArray(); + private static PanelRegistration[] GetAvailablePanels(Snapshot snapshot) => snapshot.panels.Where(registration => !snapshot.quarantinedPanels.Contains(registration.panel)).ToArray(); private void StopModule( ModuleRegistration registration, string phase @@ -720,33 +677,19 @@ EditorExtensionActivator activator EditorMenuAttribute[] menus = type.GetCustomAttributes(false).ToArray(); EditorToolbarItemAttribute[] toolbars = type.GetCustomAttributes(false).ToArray(); EditorShortcutAttribute[] shortcuts = type.GetCustomAttributes(false).ToArray(); - return new ActionRegistration( - attribute, - type, - action, - action.targetType, - action.argumentType, - menus, - toolbars, - shortcuts); + return new ActionRegistration(attribute, type, action, action.targetType, action.argumentType, menus, toolbars, shortcuts); } private void LogError( string message, - params object?[] arguments + params object? [] arguments ) => m_log.Write(LogLevel.Error, message, arguments); - private static IEnumerable CreateMenuSourceRegistrations( Type type, EditorExtensionActivator activator ) { EditorMenuSource source = activator.CreateExtension(type); - return type.GetCustomAttributes(false) - .Select(attribute => new MenuSourceRegistration( - attribute.area, - attribute.priority, - type, - source)); + return type.GetCustomAttributes(false).Select(attribute => new MenuSourceRegistration(attribute.area, attribute.priority, type, source)); } private static IEnumerable CreateDropRegistrations( @@ -756,14 +699,7 @@ EditorExtensionActivator activator EditorDrop drop = activator.CreateExtension(type); Type sourceType = drop.sourceType; Type targetType = drop.targetType; - return type.GetCustomAttributes(false) - .Select(attribute => new DropRegistration( - sourceType, - targetType, - attribute.area, - attribute.priority, - type, - drop)); + return type.GetCustomAttributes(false).Select(attribute => new DropRegistration(sourceType, targetType, attribute.area, attribute.priority, type, drop)); } private PanelRegistration CreatePanelRegistration( @@ -780,46 +716,27 @@ EditorExtensionActivator activator } else { - panel.isOpen = m_state.TryGetPanelOpen(attribute.id, out bool isOpen) - ? isOpen - : attribute.defaultOpen; + panel.isOpen = m_state.TryGetPanelOpen(attribute.id, out bool isOpen) ? isOpen : attribute.defaultOpen; } + return new PanelRegistration(attribute, type, panel); } private static ModalRegistration CreateModalRegistration( Type type, EditorExtensionActivator activator - ) - => new( - type.GetCustomAttribute(false)!, - type, - activator.CreateExtension(type)); - + ) => new(type.GetCustomAttribute(false)!, type, activator.CreateExtension(type)); private static HistoryHandlerRegistration CreateHistoryHandlerRegistration( Type type, EditorExtensionActivator activator - ) - => new( - type.GetCustomAttribute(false)!, - type, - activator.CreateExtension(type)); - + ) => new(type.GetCustomAttribute(false)!, type, activator.CreateExtension(type)); private static void ValidateActions(ActionRegistration[] actions) { - foreach (IGrouping<(string Action, string Area, Type? Target, int Priority), ActionRegistration> group in - actions.GroupBy(static value => ( - value.attribute.action, - value.attribute.area, - value.targetType, - value.attribute.priority))) + foreach (IGrouping<(string Action, string Area, Type? Target, int Priority), ActionRegistration> group in actions.GroupBy(static value => (value.attribute.action, value.attribute.area, value.targetType, value.attribute.priority))) { if (group.Count() > 1) { - throw new InvalidOperationException( - $"Editor action '{group.Key.Action}' has conflicting registrations for area " + - $"'{(string.IsNullOrEmpty(group.Key.Area) ? "*" : group.Key.Area)}' and target " + - $"'{group.Key.Target?.FullName ?? "*"}'."); + throw new InvalidOperationException($"Editor action '{group.Key.Action}' has conflicting registrations for area " + $"'{(string.IsNullOrEmpty(group.Key.Area) ? "*" : group.Key.Area)}' and target " + $"'{group.Key.Target?.FullName ?? "*"}'."); } } } @@ -833,21 +750,19 @@ private void ValidateShortcuts(ActionRegistration[] actions) for (int shortcutIndex = 0; shortcutIndex < registration.shortcuts.Length; shortcutIndex++) { EditorShortcutAttribute shortcut = registration.shortcuts[shortcutIndex]; - if (!string.IsNullOrEmpty(registration.attribute.area) && - !string.IsNullOrEmpty(shortcut.area) && - !string.Equals(registration.attribute.area, shortcut.area, StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(registration.attribute.area) + && !string.IsNullOrEmpty(shortcut.area) + && !string.Equals( + registration.attribute.area, + shortcut.area, + StringComparison.Ordinal + )) { - throw new InvalidOperationException( - $"Editor shortcut on '{registration.id}' targets area '{shortcut.area}', " + - $"outside its action area '{registration.attribute.area}'."); + throw new InvalidOperationException($"Editor shortcut on '{registration.id}' targets area '{shortcut.area}', " + $"outside its action area '{registration.attribute.area}'."); } - string effectiveArea = string.IsNullOrEmpty(shortcut.area) - ? registration.attribute.area - : shortcut.area; - shortcuts.Add(new ShortcutValidationEntry( - registration, - effectiveArea, - CreateShortcutGesture(shortcut))); + + string effectiveArea = string.IsNullOrEmpty(shortcut.area) ? registration.attribute.area : shortcut.area; + shortcuts.Add(new ShortcutValidationEntry(registration, effectiveArea, CreateShortcutGesture(shortcut))); } } @@ -857,31 +772,28 @@ private void ValidateShortcuts(ActionRegistration[] actions) for (int rightIndex = leftIndex + 1; rightIndex < shortcuts.Count; rightIndex++) { ShortcutValidationEntry right = shortcuts[rightIndex]; - bool sameAction = string.Equals( - left.registration.id, - right.registration.id, - StringComparison.Ordinal); - if (left.gesture != right.gesture || - left.registration.attribute.priority != right.registration.attribute.priority || - !string.Equals(left.area, right.area, StringComparison.Ordinal) || - !MayHaveEqualTargetSpecificity( - left.registration.targetType, - right.registration.targetType)) + bool sameAction = string.Equals(left.registration.id, right.registration.id, StringComparison.Ordinal); + if (left.gesture != right.gesture + || left.registration.attribute.priority != right.registration.attribute.priority + || !string.Equals( + left.area, + right.area, + StringComparison.Ordinal + ) + || !MayHaveEqualTargetSpecificity( + left.registration.targetType, + right.registration.targetType + )) { continue; } if (sameAction) { - throw new InvalidOperationException( - $"Editor action '{left.registration.id}' declares duplicate shortcut " + - $"'{left.gesture}' for area '{(string.IsNullOrEmpty(left.area) ? "*" : left.area)}'."); + throw new InvalidOperationException($"Editor action '{left.registration.id}' declares duplicate shortcut " + $"'{left.gesture}' for area '{(string.IsNullOrEmpty(left.area) ? "*" : left.area)}'."); } - throw new InvalidOperationException( - $"Editor shortcut '{left.gesture}' is ambiguous between actions " + - $"'{left.registration.id}' and '{right.registration.id}' for area " + - $"'{(string.IsNullOrEmpty(left.area) ? "*" : left.area)}'."); + throw new InvalidOperationException($"Editor shortcut '{left.gesture}' is ambiguous between actions " + $"'{left.registration.id}' and '{right.registration.id}' for area " + $"'{(string.IsNullOrEmpty(left.area) ? "*" : left.area)}'."); } } } @@ -906,46 +818,39 @@ private static void ValidateToolbars(IReadOnlyList actions) continue; if (registration.targetType is not null || registration.argumentType is not null) { - throw new InvalidOperationException( - $"Editor toolbar action '{registration.id}' must not require a target or argument."); + throw new InvalidOperationException($"Editor toolbar action '{registration.id}' must not require a target or argument."); } + for (int toolbarIndex = 0; toolbarIndex < registration.toolbars.Length; toolbarIndex++) { EditorToolbarItemAttribute toolbar = registration.toolbars[toolbarIndex]; - if (!string.IsNullOrEmpty(registration.attribute.area) && - !string.Equals(registration.attribute.area, toolbar.area, StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(registration.attribute.area) && !string.Equals(registration.attribute.area, toolbar.area, StringComparison.Ordinal)) { - throw new InvalidOperationException( - $"Editor toolbar placement on '{registration.id}' targets area '{toolbar.area}', " + - $"outside its action area '{registration.attribute.area}'."); + throw new InvalidOperationException($"Editor toolbar placement on '{registration.id}' targets area '{toolbar.area}', " + $"outside its action area '{registration.attribute.area}'."); } } } - IGrouping<(string id, string area), (string id, string area)>? duplicate = actions - .SelectMany(static registration => registration.toolbars.Select(toolbar => ( - registration.id, - toolbar.area))) - .GroupBy(static placement => placement, EqualityComparer<(string id, string area)>.Default) - .FirstOrDefault(static group => group.Count() > 1); + IGrouping<(string id, string area), (string id, string area)>? duplicate = actions.SelectMany(static registration => registration.toolbars.Select(toolbar => (registration.id, toolbar.area))).GroupBy( + static placement => placement, + EqualityComparer<(string id, string area)>.Default + ).FirstOrDefault(static group => group.Count() > 1); if (duplicate is not null) - throw new InvalidOperationException( - $"Editor action '{duplicate.Key.id}' has duplicate toolbar placements."); + throw new InvalidOperationException($"Editor action '{duplicate.Key.id}' has duplicate toolbar placements."); } - private HotKeyGesture CreateShortcutGesture(EditorShortcutAttribute shortcut) - => shortcut.primary - ? HotKeyGesture.Primary(shortcut.key, m_context.keyboard, shortcut.modifiers) - : new HotKeyGesture(shortcut.key, shortcut.modifiers, m_context.keyboard.superModifierLabel); - + private HotKeyGesture CreateShortcutGesture(EditorShortcutAttribute shortcut) => shortcut.primary ? HotKeyGesture.Primary( + shortcut.key, + m_context.keyboard, + shortcut.modifiers + ) : new HotKeyGesture( + shortcut.key, + shortcut.modifiers, + m_context.keyboard.superModifierLabel + ); private static void ValidateDrops(DropRegistration[] drops) { - foreach (IGrouping<(Type Source, Type Target, string Area, int Priority), DropRegistration> group in - drops.GroupBy(static value => ( - value.sourceType, - value.targetType, - value.area, - value.priority))) + foreach (IGrouping<(Type Source, Type Target, string Area, int Priority), DropRegistration> group in drops.GroupBy(static value => (value.sourceType, value.targetType, value.area, value.priority))) { if (group.Count() > 1) throw new InvalidOperationException("Editor drop registrations contain an ambiguous match."); @@ -954,36 +859,28 @@ private static void ValidateDrops(DropRegistration[] drops) private static void ValidatePanels(PanelRegistration[] panels) { - string? duplicate = panels - .GroupBy(static value => value.attribute.id, StringComparer.Ordinal) - .FirstOrDefault(static group => group.Count() > 1)?.Key; + string? duplicate = panels.GroupBy(static value => value.attribute.id, StringComparer.Ordinal).FirstOrDefault(static group => group.Count() > 1)?.Key; if (duplicate is not null) throw new InvalidOperationException($"Editor panel id '{duplicate}' is registered more than once."); } private static void ValidateModals(ModalRegistration[] modals) { - string? duplicate = modals - .GroupBy(static value => value.attribute.id, StringComparer.Ordinal) - .FirstOrDefault(static group => group.Count() > 1)?.Key; + string? duplicate = modals.GroupBy(static value => value.attribute.id, StringComparer.Ordinal).FirstOrDefault(static group => group.Count() > 1)?.Key; if (duplicate is not null) throw new InvalidOperationException($"Editor modal id '{duplicate}' is registered more than once."); } private static void ValidateHistoryHandlers(HistoryHandlerRegistration[] handlers) { - string? duplicate = handlers - .GroupBy(static value => value.attribute.kind, StringComparer.Ordinal) - .FirstOrDefault(static group => group.Count() > 1)?.Key; + string? duplicate = handlers.GroupBy(static value => value.attribute.kind, StringComparer.Ordinal).FirstOrDefault(static group => group.Count() > 1)?.Key; if (duplicate is not null) { - throw new InvalidOperationException( - $"Editor history handler kind '{duplicate}' is registered more than once."); + throw new InvalidOperationException($"Editor history handler kind '{duplicate}' is registered more than once."); } } - private static IReadOnlyDictionary CreateHistoryHandlerMap( - IReadOnlyList handlers) + private static IReadOnlyDictionary CreateHistoryHandlerMap(IReadOnlyList handlers) { var result = new Dictionary(handlers.Count, StringComparer.Ordinal); for (int i = 0; i < handlers.Count; i++) @@ -995,14 +892,12 @@ private static void ValidateExtensionIds( IReadOnlyList modules, IReadOnlyList panels ) { - string? duplicate = modules - .Select(static value => value.attribute.id) - .Concat(panels.Select(static value => value.attribute.id)) - .GroupBy(static id => id, StringComparer.Ordinal) - .FirstOrDefault(static group => group.Count() > 1)?.Key; + string? duplicate = modules.Select(static value => value.attribute.id).Concat(panels.Select(static value => value.attribute.id)).GroupBy( + static id => id, + StringComparer.Ordinal + ).FirstOrDefault(static group => group.Count() > 1)?.Key; if (duplicate is not null) - throw new InvalidOperationException( - $"Editor extension id '{duplicate}' is registered more than once."); + throw new InvalidOperationException($"Editor extension id '{duplicate}' is registered more than once."); } private static StateRegistration[] CreateStateRegistrations( @@ -1013,30 +908,20 @@ IReadOnlyList panels for (int i = 0; i < modules.Count; i++) { ModuleRegistration module = modules[i]; - StateRegistration? registration = CreateStateRegistration( - module.attribute.id, - StateOwnerKind.Module, - module.type, - module.module, - typeof(EditorModule)); + StateRegistration? registration = CreateStateRegistration(module.attribute.id, StateOwnerKind.Module, module.type, module.module, typeof(EditorModule)); if (registration is not null) result.Add(registration); } + for (int i = 0; i < panels.Count; i++) { PanelRegistration panel = panels[i]; - StateRegistration? registration = CreateStateRegistration( - panel.attribute.id, - StateOwnerKind.Panel, - panel.type, - panel.panel, - typeof(EditorPanel)); + StateRegistration? registration = CreateStateRegistration(panel.attribute.id, StateOwnerKind.Panel, panel.type, panel.panel, typeof(EditorPanel)); if (registration is not null) result.Add(registration); } - return result - .OrderBy(static value => value.id, StringComparer.Ordinal) - .ToArray(); + + return result.OrderBy(static value => value.id, StringComparer.Ordinal).ToArray(); } private static StateRegistration? CreateStateRegistration( @@ -1052,8 +937,8 @@ Type baseType flags, binder: null, [typeof(EditorState)], - modifiers: null) ?? throw new InvalidOperationException( - $"Editor extension '{type.FullName}' has no state capture hook."); + modifiers: null + ) ?? throw new InvalidOperationException($"Editor extension '{type.FullName}' has no state capture hook."); if (capture.DeclaringType == baseType) return null; MethodInfo restore = type.GetMethod( @@ -1061,14 +946,9 @@ Type baseType flags, binder: null, [typeof(EditorState)], - modifiers: null) ?? throw new InvalidOperationException( - $"Editor extension '{type.FullName}' has no state restore hook."); - return new StateRegistration( - id, - kind, - owner, - capture.CreateDelegate>(owner), - restore.CreateDelegate>(owner)); + modifiers: null + ) ?? throw new InvalidOperationException($"Editor extension '{type.FullName}' has no state restore hook."); + return new StateRegistration(id, kind, owner, capture.CreateDelegate>(owner), restore.CreateDelegate>(owner)); } internal sealed record Snapshot( @@ -1081,21 +961,14 @@ internal sealed record Snapshot( HistoryHandlerRegistration[] historyHandlers, StateRegistration[] state, object[] instances - ) { - internal HashSet quarantinedModules { get; } = - new(ReferenceEqualityComparer.Instance); - - internal HashSet quarantinedPanels { get; } = - new(ReferenceEqualityComparer.Instance); - - internal HashSet quarantinedModals { get; } = - new(ReferenceEqualityComparer.Instance); - - internal HashSet startedModules { get; } = - new(ReferenceEqualityComparer.Instance); - - internal HashSet attachedPanels { get; } = - new(ReferenceEqualityComparer.Instance); + ) + { + internal IReadOnlyList panelDeclarations { get; } = Array.AsReadOnly(panels.Select(static registration => registration.attribute).ToArray()); + internal HashSet quarantinedModules { get; } = new(ReferenceEqualityComparer.Instance); + internal HashSet quarantinedPanels { get; } = new(ReferenceEqualityComparer.Instance); + internal HashSet quarantinedModals { get; } = new(ReferenceEqualityComparer.Instance); + internal HashSet startedModules { get; } = new(ReferenceEqualityComparer.Instance); + internal HashSet attachedPanels { get; } = new(ReferenceEqualityComparer.Instance); } internal sealed record ModuleRegistration( @@ -1103,7 +976,6 @@ internal sealed record ModuleRegistration( Type type, EditorModule module ); - internal sealed record ActionRegistration( EditorActionAttribute attribute, Type type, @@ -1113,12 +985,10 @@ internal sealed record ActionRegistration( EditorMenuAttribute[] menus, EditorToolbarItemAttribute[] toolbars, EditorShortcutAttribute[] shortcuts - ) { + ) + { internal string id { get; } = attribute.action; - - internal string? area { get; } = string.IsNullOrEmpty(attribute.area) - ? null - : attribute.area; + internal string? area { get; } = string.IsNullOrEmpty(attribute.area) ? null : attribute.area; } internal sealed record MenuSourceRegistration( @@ -1127,7 +997,6 @@ internal sealed record MenuSourceRegistration( Type type, EditorMenuSource source ); - internal sealed record DropRegistration( Type sourceType, Type targetType, @@ -1136,25 +1005,21 @@ internal sealed record DropRegistration( Type type, EditorDrop drop ); - internal sealed record PanelRegistration( EditorPanelAttribute attribute, Type type, EditorPanel panel ); - internal sealed record ModalRegistration( EditorModalAttribute attribute, Type type, EditorModal modal ); - internal sealed record HistoryHandlerRegistration( EditorHistoryHandlerAttribute attribute, Type type, EditorHistoryHandler handler ); - internal sealed record StateRegistration( string id, StateOwnerKind kind, @@ -1162,7 +1027,6 @@ internal sealed record StateRegistration( Action capture, Action restore ); - internal enum StateOwnerKind { Module, @@ -1173,19 +1037,18 @@ private readonly record struct PanelState( bool isOpen, ReadOnlyMemory payload ); - private readonly record struct ShortcutValidationEntry( ActionRegistration registration, string area, HotKeyGesture gesture ); - private sealed class ActivationState( Snapshot? previous, Snapshot candidate, HashSet existing, EditorHistory.HandlerUpdate handlers - ) { + ) + { internal Snapshot? previous { get; } = previous; internal Snapshot candidate { get; } = candidate; internal HashSet existing { get; } = existing; diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentService.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentService.cs index 7bf862880..c42f94cb9 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentService.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Documents/EditorDocumentService.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using Inno.Core.Events; namespace Inno.Editor.Interactions; - internal sealed class EditorDocumentService : IEditorDocumentService { private readonly List m_documents = []; private readonly Dictionary m_providers = new(StringComparer.Ordinal); - - internal EditorDocumentService() { } - + private readonly EventDispatcher m_events; + internal EditorDocumentService(EventDispatcher events) => m_events = events; /// /// Gets an immutable snapshot of currently owned source documents. /// @@ -46,6 +45,7 @@ public IDisposable RegisterProvider(EditorDocumentProvider provider) Unregister(provider); throw; } + return new ProviderLease(this, provider); } @@ -66,28 +66,27 @@ public EditorDocumentContext Open( Guid assetId = default ) { string normalizedPath = NormalizePath(assetPath); - EditorDocumentContext? identityOwner = assetId == Guid.Empty - ? null - : m_documents.FirstOrDefault(document => document.assetId == assetId); + EditorDocumentContext? identityOwner = assetId == Guid.Empty ? null : m_documents.FirstOrDefault(document => document.assetId == assetId); EditorDocumentContext? pathOwner = FindPathOwner(normalizedPath); if (identityOwner is not null && pathOwner is not null && !ReferenceEquals(identityOwner, pathOwner)) RetireStalePathOwner(pathOwner, normalizedPath); - EditorDocumentContext? existing = identityOwner ?? pathOwner; if (existing is not null && assetId != Guid.Empty && existing.assetId != assetId) { RetireStalePathOwner(existing, normalizedPath); existing = null; } + if (existing is not null) { _ = UpdateAssetPath(existing.documentId, normalizedPath); return existing; } - EditorDocumentProvider provider = m_providers.Values - .OrderBy(static candidate => candidate.id, StringComparer.Ordinal) - .FirstOrDefault(candidate => candidate.CanOpen(normalizedPath)) - ?? throw new InvalidOperationException($"No editor document provider can open '{normalizedPath}'."); + + EditorDocumentProvider provider = m_providers.Values.OrderBy( + static candidate => candidate.id, + StringComparer.Ordinal + ).FirstOrDefault(candidate => candidate.CanOpen(normalizedPath)) ?? throw new InvalidOperationException($"No editor document provider can open '{normalizedPath}'."); var context = new EditorDocumentContext(Guid.NewGuid(), assetId, normalizedPath, provider.id) { isProviderAvailable = true @@ -102,6 +101,7 @@ public EditorDocumentContext Open( m_documents.Remove(context); throw; } + return context; } @@ -150,7 +150,6 @@ public void SetDirty( Guid documentId, bool isDirty = true ) => Get(documentId).isDirty = isDirty; - /// /// Persists the supplied value through the configured storage contract. /// @@ -164,7 +163,6 @@ public bool Save(Guid documentId) => Invoke(documentId, static ( provider, context ) => provider.Save(context), clearDirty: true); - /// /// Saves every dirty document through its owning provider. /// @@ -179,6 +177,7 @@ public bool SaveAll() if (document.isDirty && !Save(document.documentId)) succeeded = false; } + return succeeded; } @@ -195,7 +194,6 @@ public bool Apply(Guid documentId) => Invoke(documentId, static ( provider, context ) => provider.Apply(context), clearDirty: true); - /// /// Reverts one open document through its owning provider. /// @@ -209,7 +207,6 @@ public bool Revert(Guid documentId) => Invoke(documentId, static ( provider, context ) => provider.Revert(context), clearDirty: true); - /// /// Closes the requested resource and releases its operation-scoped state. /// @@ -226,6 +223,7 @@ public bool Close( Guid documentId, EditorDocumentCloseMode mode ) { + m_events.Emit(new EditorOperationPreparingEvent()); EditorDocumentContext? document = Find(documentId); if (document is null || mode == EditorDocumentCloseMode.Cancel) return false; @@ -246,6 +244,7 @@ internal void Shutdown() if (m_providers.TryGetValue(document.providerId, out EditorDocumentProvider? provider)) provider.Close(document); } + m_documents.Clear(); m_providers.Clear(); } @@ -255,6 +254,7 @@ private bool Invoke( Func operation, bool clearDirty ) { + m_events.Emit(new EditorOperationPreparingEvent()); EditorDocumentContext document = Get(documentId); if (!m_providers.TryGetValue(document.providerId, out EditorDocumentProvider? provider)) return false; @@ -264,29 +264,21 @@ bool clearDirty return succeeded; } - private EditorDocumentContext Get(Guid documentId) - => Find(documentId) - ?? throw new ArgumentException($"Editor document '{documentId}' is not open.", nameof(documentId)); - + private EditorDocumentContext Get(Guid documentId) => Find(documentId) ?? throw new ArgumentException($"Editor document '{documentId}' is not open.", nameof(documentId)); private EditorDocumentContext? Find(Guid documentId) => m_documents.FirstOrDefault(document => document.documentId == documentId); - private EditorDocumentContext? FindPathOwner( string assetPath, Guid excludedDocumentId = default - ) - => m_documents.FirstOrDefault(document => document.documentId != excludedDocumentId - && string.Equals(document.assetPath, assetPath, StringComparison.Ordinal)); - + ) => m_documents.FirstOrDefault(document => document.documentId != excludedDocumentId && string.Equals(document.assetPath, assetPath, StringComparison.Ordinal)); private void RetireStalePathOwner( EditorDocumentContext document, string destinationPath ) { if (document.isDirty) { - throw new InvalidOperationException( - $"Open document '{document.title}' has unsaved changes for '{destinationPath}'. " + - "Save, revert, or close it before assigning that source path to another asset identity."); + throw new InvalidOperationException($"Open document '{document.title}' has unsaved changes for '{destinationPath}'. " + "Save, revert, or close it before assigning that source path to another asset identity."); } + if (m_providers.TryGetValue(document.providerId, out EditorDocumentProvider? provider)) provider.Close(document); if (!m_documents.Remove(document)) @@ -304,11 +296,11 @@ private static string NormalizePath(string assetPath) private void Unregister(EditorDocumentProvider provider) { - if (!m_providers.TryGetValue(provider.id, out EditorDocumentProvider? registered) - || !ReferenceEquals(provider, registered)) + if (!m_providers.TryGetValue(provider.id, out EditorDocumentProvider? registered) || !ReferenceEquals(provider, registered)) { return; } + m_providers.Remove(provider.id); foreach (EditorDocumentContext document in m_documents.Where(document => document.providerId == provider.id)) document.isProviderAvailable = false; @@ -320,11 +312,10 @@ EditorDocumentProvider provider ) : IDisposable { private EditorDocumentService? m_owner = owner; - /// /// Releases the resources owned by this instance. /// -public void Dispose() + public void Dispose() { EditorDocumentService? current = m_owner; m_owner = null; diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs new file mode 100644 index 000000000..5268bad6c --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorEditGesture.cs @@ -0,0 +1,96 @@ +using System; + +namespace Inno.Editor.Interactions; +/// +/// Owns one continuous edit transaction without retaining edited objects, setters or extension instances. +/// +public sealed class EditorEditGesture : IDisposable +{ + private EditorHistoryTransaction? m_transaction; + /// + /// Begins an edit whose updates use the existing domain mutation and History protocols. + /// + /// + /// The authoritative History for the current editor session. + /// + /// + /// The operation name displayed by Undo and Redo after a successful commit. + /// + /// + /// The History owner is null. + /// + /// + /// History cannot begin a transaction in its current state. + /// + public EditorEditGesture( + IEditorHistory history, + string name + ) { + ArgumentNullException.ThrowIfNull(history); + m_transaction = history.BeginTransaction(name); + mergeKey = "gesture:" + m_transaction.id.ToString("N"); + } + + /// + /// Gets the transient merge boundary shared only by updates within this gesture. + /// + public string mergeKey { get; } + /// + /// Gets whether this gesture still owns an active transaction, including a rejected completion. + /// + public bool isActive => m_transaction is not null; + + /// + /// Commits the edit exactly once; rejected completion leaves the transaction available for retry. + /// + /// + /// The gesture is finished or a nested transaction remains active. + /// + public void Commit() + { + EditorHistoryTransaction transaction = RequireTransaction(); + transaction.Commit(); + m_transaction = null; + transaction.Dispose(); + } + + /// + /// Attempts to restore the state before the gesture without discarding a failed rollback. + /// + /// + /// The rollback outcome; a state-preserving failure keeps this gesture active for retry. + /// + /// + /// The gesture is finished or a nested transaction remains active. + /// + public EditorHistoryResult Cancel() + { + EditorHistoryTransaction transaction = RequireTransaction(); + EditorHistoryResult result = transaction.Rollback(); + if (result.succeeded) + { + m_transaction = null; + transaction.Dispose(); + } + + return result; + } + + /// + /// Cancels an unfinished gesture and releases its ownership only after rollback succeeds. + /// + /// + /// Rollback is blocked or a nested transaction remains active. + /// + public void Dispose() + { + if (m_transaction is null) + return; + EditorHistoryResult result = Cancel(); + if (!result.succeeded) + throw new InvalidOperationException($"The edit gesture could not roll back: {result.message}"); + GC.SuppressFinalize(this); + } + + private EditorHistoryTransaction RequireTransaction() => m_transaction ?? throw new InvalidOperationException("The edit gesture has already finished."); +} diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorOperationPreparingEvent.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorOperationPreparingEvent.cs new file mode 100644 index 000000000..a6472e6f0 --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Editing/EditorOperationPreparingEvent.cs @@ -0,0 +1,10 @@ +using Inno.Core.Events; + +namespace Inno.Editor.Interactions; +/// +/// Synchronously closes transient edits before command execution or an extension-handler generation switch. +/// +/// +/// A handler failure aborts the boundary. Subscribers must use owned Core Event hubs and release them during retirement. +/// +public sealed class EditorOperationPreparingEvent : Event; diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistory.cs b/src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistory.cs index c2fdc38c3..f46671a4f 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistory.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/History/EditorHistory.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; - using Inno.Core.Logging; using Inno.Editor.Core; namespace Inno.Editor.Interactions; - /// /// Owns the bounded, transactional Undo and Redo history for one editor runtime. /// @@ -18,16 +16,13 @@ internal sealed class EditorHistory : IEditorHistory, IDisposable private readonly EditorHistoryOptions m_options; private readonly EditorHistoryBlobStore m_blobStore; private readonly Logger m_log; - - private IReadOnlyDictionary m_handlers = - new Dictionary(StringComparer.Ordinal); + private IReadOnlyDictionary m_handlers = new Dictionary(StringComparer.Ordinal); private EditorHistoryContext? m_context; private IsolationState? m_isolation; private bool m_isTransitioning; private bool m_isFaulted; private string? m_faultReason; private bool m_isDisposed; - /// /// Creates an empty history with explicit entry, resident-memory, and temporary-disk budgets. /// @@ -59,68 +54,70 @@ Logger log /// Gets the maximum number of committed top-level operations retained by this history. /// public int capacity { get; } - /// /// Gets whether a previous operation is currently available and permits Undo. /// - public bool canUndo => !m_isFaulted && !m_isTransitioning && m_transactions.Count == 0 && - m_undo.Count > 0 && m_undo[^1].canUndo; - + public bool canUndo => !m_isFaulted && !m_isTransitioning && m_transactions.Count == 0 && m_undo.Count > 0 && m_undo[^1].canUndo; /// /// Gets whether a reverted operation is currently available and permits Redo. /// - public bool canRedo => !m_isFaulted && !m_isTransitioning && m_transactions.Count == 0 && - m_redo.Count > 0 && m_redo[^1].canRedo; - + public bool canRedo => !m_isFaulted && !m_isTransitioning && m_transactions.Count == 0 && m_redo.Count > 0 && m_redo[^1].canRedo; /// /// Gets whether a failed compensation left the domain state indeterminate. /// public bool isFaulted => m_isFaulted; - /// /// Gets the diagnostic that faulted this history, or while the history is healthy. /// public string? faultReason => m_faultReason; - /// /// Gets the next operation name displayed by the Undo command, or when unavailable. /// public string? undoName => m_undo.Count == 0 ? null : m_undo[^1].name; - /// /// Gets the next operation name displayed by the Redo command, or when unavailable. /// public string? redoName => m_redo.Count == 0 ? null : m_redo[^1].name; - /// /// Gets the diagnostic explaining why the newest Undo entry is a barrier, or when available. /// public string? undoUnavailableReason => GetUnavailableReason(m_undo, EditorHistoryDirection.Undo); - /// /// Gets the diagnostic explaining why the newest Redo entry is a barrier, or when available. /// public string? redoUnavailableReason => GetUnavailableReason(m_redo, EditorHistoryDirection.Redo); - /// /// Gets the estimated resident payload bytes retained by committed Undo and Redo entries. /// - public long residentBytes => Sum(m_undo, static operation => operation.estimatedMemorySize) + - Sum(m_redo, static operation => operation.estimatedMemorySize) + - (m_isolation is IsolationState isolation - ? Sum(isolation.undo, static operation => operation.estimatedMemorySize) + - Sum(isolation.redo, static operation => operation.estimatedMemorySize) - : 0L); - + public long residentBytes => Sum( + m_undo, + static operation => operation.estimatedMemorySize + ) + Sum( + m_redo, + static operation => operation.estimatedMemorySize + ) + (m_isolation is IsolationState isolation ? Sum( + isolation.undo, + static operation => operation.estimatedMemorySize + ) + Sum( + isolation.redo, + static operation => operation.estimatedMemorySize + ) : 0L); /// /// Gets the estimated temporary disk payload bytes retained by committed Undo and Redo entries. /// - public long diskBytes => Sum(m_undo, static operation => operation.estimatedDiskSize) + - Sum(m_redo, static operation => operation.estimatedDiskSize) + - (m_isolation is IsolationState isolation - ? Sum(isolation.undo, static operation => operation.estimatedDiskSize) + - Sum(isolation.redo, static operation => operation.estimatedDiskSize) - : 0L); + public long diskBytes => Sum( + m_undo, + static operation => operation.estimatedDiskSize + ) + Sum( + m_redo, + static operation => operation.estimatedDiskSize + ) + (m_isolation is IsolationState isolation ? Sum( + isolation.undo, + static operation => operation.estimatedDiskSize + ) + Sum( + isolation.redo, + static operation => operation.estimatedDiskSize + ) : 0L); /// /// Begins an atomic group whose child operations appear as one Undo entry. @@ -184,6 +181,7 @@ internal EditorHistoryResult Execute( ObserveFailure(result); operation.Dispose(); } + return result; } @@ -221,6 +219,7 @@ EditorHistoryChange change { change.Dispose(); } + EditorHistoryResult result = Invoke(operation.RedoInternal, name, "execute"); if (result.succeeded) Record(operation); @@ -229,6 +228,7 @@ EditorHistoryChange change ObserveFailure(result); operation.Dispose(); } + return result; } @@ -322,7 +322,6 @@ object mergeKey /// The transition result. A failed operation remains available for retry. /// public EditorHistoryResult Undo() => Transition(m_undo, m_redo, undo: true); - /// /// Attempts to reapply the newest reverted operation. /// @@ -330,7 +329,6 @@ object mergeKey /// The transition result. A failed operation remains available for retry. /// public EditorHistoryResult Redo() => Transition(m_redo, m_undo, undo: false); - /// /// Starts a temporary empty history branch while retaining the current branch for restoration. /// @@ -347,12 +345,7 @@ internal IDisposable BeginIsolation() throw new InvalidOperationException("Editor history cannot be isolated during a transaction."); if (m_isolation is not null) throw new InvalidOperationException("Editor history already owns an isolated branch."); - - var isolation = new IsolationState( - m_undo, - m_redo, - m_isFaulted, - m_faultReason); + var isolation = new IsolationState(m_undo, m_redo, m_isFaulted, m_faultReason); m_isolation = isolation; m_undo = []; m_redo = []; @@ -378,6 +371,7 @@ internal void Clear() DisposeAll(isolation.redo); m_isolation = null; } + while (m_transactions.TryPop(out TransactionOperation? transaction)) transaction.Dispose(); m_isFaulted = false; @@ -407,6 +401,7 @@ internal void CommitTransaction(EditorHistoryTransaction transaction) operation.Dispose(); return; } + Record(operation); } @@ -425,6 +420,7 @@ internal EditorHistoryResult RollbackTransaction(EditorHistoryTransaction transa { ObserveFailure(result); } + return result; } @@ -442,6 +438,7 @@ private void Record(EditorHistoryOperation operation) operation.Dispose(); return; } + m_undo.Add(operation); int retainedUndoCount = m_isolation?.undo.Count ?? 0; while (m_undo.Count > 0 && m_undo.Count + retainedUndoCount > capacity) @@ -449,6 +446,7 @@ private void Record(EditorHistoryOperation operation) m_undo[0].Dispose(); m_undo.RemoveAt(0); } + EnforceBudgets(); } @@ -462,20 +460,17 @@ bool undo return EditorHistoryResult.Failure("Undo and Redo are unavailable while a transaction is active."); if (source.Count == 0) return EditorHistoryResult.Failure(undo ? "Nothing to undo." : "Nothing to redo."); - EditorHistoryOperation operation = source[^1]; m_isTransitioning = true; try { - EditorHistoryResult result = Invoke( - undo ? operation.UndoInternal : operation.RedoInternal, - operation.name, - undo ? "undo" : "redo"); + EditorHistoryResult result = Invoke(undo ? operation.UndoInternal : operation.RedoInternal, operation.name, undo ? "undo" : "redo"); if (!result.succeeded) { ObserveFailure(result); return result; } + source.RemoveAt(source.Count - 1); destination.Add(operation); return result; @@ -488,11 +483,8 @@ bool undo private TransactionOperation PopTransaction(Guid id) { - if (!m_transactions.TryPop(out TransactionOperation? operation) || - operation.id != id) - { - throw new InvalidOperationException("Editor history transactions must complete in stack order."); - } + TransactionOperation operation = PeekTransaction(id); + _ = m_transactions.Pop(); return operation; } @@ -508,9 +500,9 @@ private void EnsureMutable() ObjectDisposedException.ThrowIf(m_isDisposed, this); if (m_isFaulted) { - throw new InvalidOperationException( - $"Editor history is faulted and must be cleared before further use. {m_faultReason}"); + throw new InvalidOperationException($"Editor history is faulted and must be cleared before further use. {m_faultReason}"); } + if (m_isTransitioning) throw new InvalidOperationException("History cannot be modified while Undo or Redo is executing."); } @@ -521,10 +513,7 @@ private void ObserveFailure(EditorHistoryResult result) return; m_isFaulted = true; m_faultReason = result.message; - m_log.Write( - LogLevel.Error, - "Editor history was faulted because state integrity was lost: {0}", - [result.message]); + m_log.Write(LogLevel.Error, "Editor history was faulted because state integrity was lost: {0}", [result.message]); } internal void Attach( @@ -542,17 +531,15 @@ internal HandlerUpdate PrepareHandlerUpdate(IReadOnlyDictionary change.Retain( - m_blobStore, - m_options.inlinePayloadThreshold, - !string.IsNullOrWhiteSpace(m_options.cacheDirectory)); - + private EditorHistoryChange RetainChange(EditorHistoryChange change) => change.Retain( + m_blobStore, + m_options.inlinePayloadThreshold, + !string.IsNullOrWhiteSpace(m_options.cacheDirectory) + ); private bool TryGetHandler( string kind, out EditorHistoryHandler? handler ) => m_handlers.TryGetValue(kind, out handler); - private EditorHistoryAvailability Query( EditorHistoryChange change, EditorHistoryDirection direction @@ -561,9 +548,9 @@ EditorHistoryDirection direction return EditorHistoryAvailability.Unavailable("The editor history is not attached to an interaction runtime."); if (!TryGetHandler(change.kind, out EditorHistoryHandler? handler) || handler is null) { - return EditorHistoryAvailability.Unavailable( - $"Required history handler '{change.kind}' is not loaded."); + return EditorHistoryAvailability.Unavailable($"Required history handler '{change.kind}' is not loaded."); } + try { return handler.QueryInternal(m_context, change, direction); @@ -591,21 +578,32 @@ private bool TryMerge( out EditorHistoryChange? merged ) { merged = null; - if (!string.Equals(older.kind, newer.kind, StringComparison.Ordinal) || - older.mergeKey is null || - !string.Equals(older.mergeKey, newer.mergeKey, StringComparison.Ordinal) || - !TryGetHandler(older.kind, out EditorHistoryHandler? handler) || - handler is null) + if (!string.Equals( + older.kind, + newer.kind, + StringComparison.Ordinal + ) + || older.mergeKey is null + || !string.Equals( + older.mergeKey, + newer.mergeKey, + StringComparison.Ordinal + ) + || !TryGetHandler( + older.kind, + out EditorHistoryHandler? handler + ) + || handler is null) { return false; } + return handler.TryMergeInternal(older, newer, out merged); } private void EnforceBudgets() { - while (m_undo.Count > 0 && - (residentBytes > m_options.maxResidentBytes || diskBytes > m_options.maxDiskBytes)) + while (m_undo.Count > 0 && (residentBytes > m_options.maxResidentBytes || diskBytes > m_options.maxDiskBytes)) { m_undo[0].Dispose(); m_undo.RemoveAt(0); @@ -636,6 +634,7 @@ List redo undo[i].Dispose(); undo.RemoveRange(0, newestUnsafe + 1); } + if (redo.Exists(static operation => !operation.isReloadSafe)) DisposeAll(redo); } @@ -667,9 +666,8 @@ EditorHistoryDirection direction EditorHistoryAvailability availability = Query(data.change, direction); return availability.isAvailable ? null : availability.message; } - bool available = direction == EditorHistoryDirection.Undo - ? operation.canUndo - : operation.canRedo; + + bool available = direction == EditorHistoryDirection.Undo ? operation.canUndo : operation.canRedo; return available ? null : $"'{operation.name}' cannot currently be {direction.ToString().ToLowerInvariant()}."; } @@ -692,20 +690,13 @@ string transition { EditorHistoryResult result = callback(); if (!result.succeeded) - m_log.Write( - LogLevel.Warn, - "Editor history operation '{0}' could not {1}: {2}", - [name, transition, result.message]); + m_log.Write(LogLevel.Warn, "Editor history operation '{0}' could not {1}: {2}", [name, transition, result.message]); return result; } catch (Exception exception) { - m_log.Write( - LogLevel.Error, - "Editor history operation '{0}' failed to {1}: {2}", - [name, transition, exception]); - return EditorHistoryResult.StateIntegrityLost( - $"The {transition} callback for '{name}' threw before it could prove rollback: {exception.Message}"); + m_log.Write(LogLevel.Error, "Editor history operation '{0}' failed to {1}: {2}", [name, transition, exception]); + return EditorHistoryResult.StateIntegrityLost($"The {transition} callback for '{name}' threw before it could prove rollback: {exception.Message}"); } } @@ -720,10 +711,10 @@ internal sealed class HandlerUpdate( EditorHistory owner, IReadOnlyDictionary previous, IReadOnlyDictionary candidate - ) { + ) + { private bool m_activated; private bool m_finished; - internal void Activate() { if (m_finished) @@ -756,10 +747,7 @@ internal void Complete() } catch (Exception exception) { - owner.m_log.Write( - LogLevel.Error, - "Editor history could not release reload-unsafe entries: {0}", - [exception]); + owner.m_log.Write(LogLevel.Error, "Editor history could not release reload-unsafe entries: {0}", [exception]); } } } @@ -770,14 +758,12 @@ private sealed record IsolationState( bool isFaulted, string? faultReason ); - private sealed class Isolation( EditorHistory owner, IsolationState state ) : IDisposable { private EditorHistory? m_owner = owner; - /// /// Releases the resources owned by this instance. /// @@ -796,7 +782,6 @@ private sealed class DelegateOperation : EditorHistoryOperation private readonly Func m_undo; private readonly object? m_mergeKey; private Func m_redo; - internal DelegateOperation( string operationName, Func undo, @@ -821,7 +806,6 @@ internal DelegateOperation( /// The validated editor history result that represents the completed operation. /// protected override EditorHistoryResult Undo() => m_undo(); - /// /// Reapplies the state represented by this history operation. /// @@ -829,7 +813,6 @@ internal DelegateOperation( /// The validated editor history result that represents the completed operation. /// protected override EditorHistoryResult Redo() => m_redo(); - /// /// Attempts to merge without changing state when the operation cannot complete. /// @@ -841,11 +824,11 @@ internal DelegateOperation( /// protected override bool TryMerge(EditorHistoryOperation newer) { - if (m_mergeKey is null || newer is not DelegateOperation candidate || - !Equals(m_mergeKey, candidate.m_mergeKey)) + if (m_mergeKey is null || newer is not DelegateOperation candidate || !Equals(m_mergeKey, candidate.m_mergeKey)) { return false; } + m_redo = candidate.m_redo; return true; } @@ -856,7 +839,6 @@ private sealed class DataOperation : EditorHistoryOperation private readonly EditorHistory m_owner; private readonly string m_name; private EditorHistoryChange m_change; - internal DataOperation( EditorHistory owner, string operationName, @@ -871,32 +853,26 @@ EditorHistoryChange change /// Gets the human-readable name used for presentation and diagnostics. /// public override string name => m_name; - /// /// Gets whether this value can undo. /// public override bool canUndo => m_owner.Query(m_change, EditorHistoryDirection.Undo).isAvailable; - /// /// Gets whether this value can redo. /// public override bool canRedo => m_owner.Query(m_change, EditorHistoryDirection.Redo).isAvailable; - /// /// Gets whether this value is reload safe. /// public override bool isReloadSafe => true; - /// /// Gets the estimated resident-memory cost in bytes. /// public override long estimatedMemorySize => m_change.residentSize; - /// /// Gets the estimated spill-file cost in bytes. /// public override long estimatedDiskSize => m_change.diskSize; - internal EditorHistoryChange change => m_change; /// @@ -906,7 +882,6 @@ EditorHistoryChange change /// The validated editor history result that represents the completed operation. /// protected override EditorHistoryResult Undo() => m_owner.Apply(m_change, EditorHistoryDirection.Undo); - /// /// Reapplies the state represented by this history operation. /// @@ -914,7 +889,6 @@ EditorHistoryChange change /// The validated editor history result that represents the completed operation. /// protected override EditorHistoryResult Redo() => m_owner.Apply(m_change, EditorHistoryDirection.Redo); - /// /// Attempts to merge without changing state when the operation cannot complete. /// @@ -926,12 +900,11 @@ EditorHistoryChange change /// protected override bool TryMerge(EditorHistoryOperation newer) { - if (newer is not DataOperation candidate || - !m_owner.TryMerge(m_change, candidate.m_change, out EditorHistoryChange? merged) || - merged is null) + if (newer is not DataOperation candidate || !m_owner.TryMerge(m_change, candidate.m_change, out EditorHistoryChange? merged) || merged is null) { return false; } + EditorHistoryChange retained; try { @@ -941,6 +914,7 @@ protected override bool TryMerge(EditorHistoryOperation newer) { merged.Dispose(); } + m_change.Dispose(); m_change = retained; return true; @@ -962,13 +936,11 @@ protected override void Dispose(bool disposing) private sealed class ValueOperation : EditorHistoryOperation { private const double C_MERGE_WINDOW_SECONDS = 1.0; - private readonly T m_before; private readonly Action m_apply; private readonly object m_mergeKey; private T m_after; private long m_lastEditTimestamp; - internal ValueOperation( string operationName, T before, @@ -1024,10 +996,15 @@ protected override EditorHistoryResult Redo() /// protected override bool TryMerge(EditorHistoryOperation newer) { - if (newer is not ValueOperation candidate || - !Equals(m_mergeKey, candidate.m_mergeKey) || - Stopwatch.GetElapsedTime(m_lastEditTimestamp, candidate.m_lastEditTimestamp).TotalSeconds > - C_MERGE_WINDOW_SECONDS) + if (newer is not ValueOperation candidate + || !Equals( + m_mergeKey, + candidate.m_mergeKey + ) + || Stopwatch.GetElapsedTime( + m_lastEditTimestamp, + candidate.m_lastEditTimestamp + ).TotalSeconds > C_MERGE_WINDOW_SECONDS) return false; m_after = candidate.m_after; m_lastEditTimestamp = candidate.m_lastEditTimestamp; @@ -1041,42 +1018,43 @@ Guid transactionId ) : EditorHistoryOperation { private readonly List m_children = []; - /// /// Gets the human-readable name used for presentation and diagnostics. /// public override string name => operationName; - internal Guid id => transactionId; - internal int count => m_children.Count; - /// /// Gets whether this value is reload safe. /// public override bool isReloadSafe => m_children.TrueForAll(static operation => operation.isReloadSafe); - /// /// Gets whether this value can undo. /// public override bool canUndo => m_children.TrueForAll(static operation => operation.canUndo); - /// /// Gets whether this value can redo. /// public override bool canRedo => m_children.TrueForAll(static operation => operation.canRedo); - /// /// Gets the estimated resident-memory cost in bytes. /// public override long estimatedMemorySize => Sum(m_children, static operation => operation.estimatedMemorySize); - /// /// Gets the estimated spill-file cost in bytes. /// public override long estimatedDiskSize => Sum(m_children, static operation => operation.estimatedDiskSize); - internal void Add(EditorHistoryOperation operation) => m_children.Add(operation); + internal void Add(EditorHistoryOperation operation) + { + if (m_children.Count > 0 && m_children[^1].TryMergeInternal(operation)) + { + operation.Dispose(); + return; + } + + m_children.Add(operation); + } /// /// Restores the state that preceded this history operation. @@ -1094,15 +1072,14 @@ protected override EditorHistoryResult Undo() continue; for (int rollback = i + 1; rollback < m_children.Count; rollback++) { - EditorHistoryResult compensation = InvokeChild( - m_children[rollback].RedoInternal, - m_children[rollback].name, - "redo compensation"); + EditorHistoryResult compensation = InvokeChild(m_children[rollback].RedoInternal, m_children[rollback].name, "redo compensation"); if (!compensation.succeeded) compensationFailures.Add($"'{m_children[rollback].name}': {compensation.message}"); } + return CombineFailure(result, compensationFailures, "undo"); } + return EditorHistoryResult.Success(); } @@ -1122,15 +1099,14 @@ protected override EditorHistoryResult Redo() continue; for (int rollback = i - 1; rollback >= 0; rollback--) { - EditorHistoryResult compensation = InvokeChild( - m_children[rollback].UndoInternal, - m_children[rollback].name, - "undo compensation"); + EditorHistoryResult compensation = InvokeChild(m_children[rollback].UndoInternal, m_children[rollback].name, "undo compensation"); if (!compensation.succeeded) compensationFailures.Add($"'{m_children[rollback].name}': {compensation.message}"); } + return CombineFailure(result, compensationFailures, "redo"); } + return EditorHistoryResult.Success(); } @@ -1145,8 +1121,7 @@ string transition } catch (Exception exception) { - return EditorHistoryResult.StateIntegrityLost( - $"Child '{childName}' threw during {transition}: {exception.Message}"); + return EditorHistoryResult.StateIntegrityLost($"Child '{childName}' threw during {transition}: {exception.Message}"); } } @@ -1157,11 +1132,11 @@ string transition ) { if (original.statePreserved && compensationFailures.Count == 0) return original; - string compensation = compensationFailures.Count == 0 - ? "The failing child could not prove that it preserved its input state." - : $"Compensation failures: {string.Join("; ", compensationFailures)}"; - return EditorHistoryResult.StateIntegrityLost( - $"Transaction {transition} failed: {original.message} {compensation}"); + string compensation = compensationFailures.Count == 0 ? "The failing child could not prove that it preserved its input state." : $"Compensation failures: {string.Join( + "; ", + compensationFailures + )}"; + return EditorHistoryResult.StateIntegrityLost($"Transaction {transition} failed: {original.message} {compensation}"); } /// diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuCatalog.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuCatalog.cs index 21e19be08..3f3ba1b03 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuCatalog.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Menus/EditorMenuCatalog.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; - using Inno.Core.Logging; using Inno.Editor.Core; @@ -11,30 +10,16 @@ internal sealed class EditorMenuCatalog( EditorExtensionCatalog catalog, EditorActionRouter actions, Logger log -) { +) +{ private readonly HashSet m_sourceFailures = new(StringComparer.Ordinal); + private Dictionary? m_templates; internal EditorMenuModel Build(EditorMenuContext context) { ArgumentNullException.ThrowIfNull(context); - var placements = new List(); - foreach (EditorExtensionCatalog.ActionRegistration registration in catalog.extensions.actions) - { - foreach (EditorMenuAttribute menu in registration.menus) - { - if (!string.Equals(menu.area, context.area, StringComparison.Ordinal)) - continue; - placements.Add(new Placement( - NormalizePath(menu.path), - registration.id, - menu.order, - menu.separatorBefore, - argument: null, - status: null, - isGroup: false)); - } - } - + TemplateNode[] template = GetTemplate(context.area); + var dynamicPlacements = new List(); foreach (EditorExtensionCatalog.MenuSourceRegistration registration in catalog.extensions.menuSources) { if (!string.Equals(registration.area, context.area, StringComparison.Ordinal)) @@ -45,160 +30,218 @@ internal EditorMenuModel Build(EditorMenuContext context) registration.source.Build(context, builder); string sourceName = registration.type.FullName ?? registration.type.Name; m_sourceFailures.Remove(sourceName); - placements.AddRange(builder.groups.Select(static group => new Placement( - NormalizePath(group.path), - actionId: null, - group.order, - group.separatorBefore, - argument: null, - status: null, - isGroup: true))); - placements.AddRange(builder.items.Select(static item => new Placement( - NormalizePath(item.path), - item.actionId, - item.order, - item.separatorBefore, - item.argument, - status: null, - isGroup: false))); + if (builder.groups.Count == 0 && builder.items.Count == 0) + continue; + var placements = new List(builder.groups.Count + builder.items.Count); + foreach (var group in builder.groups) + placements.Add(CreatePlacement(group.path, null, group.order, group.separatorBefore, null)); + foreach (var item in builder.items) + placements.Add(CreatePlacement(item.path, item.actionId, item.order, item.separatorBefore, item.argument)); + dynamicPlacements.AddRange(placements); } catch (Exception exception) { string sourceName = registration.type.FullName ?? registration.type.Name; if (m_sourceFailures.Add(sourceName)) + log.Write(LogLevel.Error, "Editor menu source '{0}' failed: {1}", [sourceName, exception]); + } + } + if (dynamicPlacements.Count > 0) + { + foreach (Placement placement in dynamicPlacements.OrderBy(static value => value.order).ThenBy(static value => value.path, StringComparer.Ordinal)) + template = Merge(template, placement, 0); + } + return new EditorMenuModel(Resolve(template, context)); + } + + internal void InvalidateDiscovery() + { + m_templates = null; + m_sourceFailures.Clear(); + } + + private TemplateNode[] GetTemplate(string area) + { + if (m_templates is null) + { + var placementsByArea = new Dictionary>(StringComparer.Ordinal); + foreach (EditorExtensionCatalog.ActionRegistration registration in catalog.extensions.actions) + { + foreach (EditorMenuAttribute menu in registration.menus) { - log.Write( - LogLevel.Error, - "Editor menu source '{0}' failed: {1}", - [sourceName, exception]); + if (!placementsByArea.TryGetValue(menu.area, out List? placements)) + placementsByArea.Add(menu.area, placements = []); + placements.Add(CreatePlacement(menu.path, registration.id, menu.order, menu.separatorBefore, null)); } } + var templates = new Dictionary(StringComparer.Ordinal); + foreach ((string key, List placements) in placementsByArea) + { + var root = new MutableNode(string.Empty, 0, false); + foreach (Placement placement in placements.OrderBy(static value => value.order).ThenBy(static value => value.path, StringComparer.Ordinal)) + AddPlacement(root, placement); + templates.Add(key, FreezeTemplate(root.children.Values)); + } + m_templates = templates; } + return m_templates.TryGetValue(area, out TemplateNode[]? template) ? template : []; + } - if (string.Equals( - context.area, - EditorBuiltInInteractionIds.C_MAIN_MENU_AREA, - StringComparison.Ordinal)) + private IReadOnlyList Resolve( + TemplateNode[] nodes, + EditorMenuContext context + ) { + if (nodes.Length == 0) + return Array.Empty(); + var result = new List(nodes.Length); + foreach (TemplateNode node in nodes) { - foreach (EditorExtensionCatalog.PanelRegistration panel in catalog.extensions.panels) + IReadOnlyList children = Resolve(node.children, context); + Placement? actionPlacement = null; + EditorActionState state = EditorActionState.hidden; + int order = children.Count > 0 ? children[0].order : node.order; + bool separatorBefore = false; + foreach (Placement placement in node.placements) { - string menuPath = string.IsNullOrEmpty(panel.attribute.menuPath) - ? panel.attribute.title - : $"{panel.attribute.menuPath}/{panel.attribute.title}"; - placements.Add(new Placement( - $"Panel/{menuPath}", - EditorBuiltInInteractionIds.C_TOGGLE_PANEL, - panel.attribute.order, - panel.attribute.separatorBefore, - panel.attribute.id, - new EditorActionState(isVisible: true, isEnabled: true, isChecked: panel.panel.isOpen), - isGroup: false)); + if (placement.actionId is string action) + { + EditorActionState candidate = actions.Query(action, context.CreateActionContext(placement.argument)); + if (!candidate.isVisible) + continue; + actionPlacement = placement; + state = candidate; + } + order = placement.order; + separatorBefore = placement.separatorBefore; } + if (actionPlacement is null) + { + if (children.Count == 0) + continue; + state = EditorActionState.enabled; + } + string label = string.IsNullOrWhiteSpace(state.displayName) ? node.label : state.displayName!; + result.Add(new EditorMenuItem(label, actionPlacement?.actionId ?? string.Empty, order, + separatorBefore, state, children, actionPlacement?.argument)); } - - var root = new MutableNode(string.Empty, 0, false); - foreach (Placement placement in placements - .OrderBy(static value => value.order) - .ThenBy(static value => value.path, StringComparer.Ordinal)) + for (int index = 1; index < result.Count; index++) { - AddPlacement(root, placement, context); + if (result[index - 1].order <= result[index].order) + continue; + result.Sort(static ( + left, + right + ) => left.order != right.order ? left.order.CompareTo(right.order) : StringComparer.Ordinal.Compare(left.label, right.label)); + break; } - return new EditorMenuModel(Freeze(root.children.Values)); + return result.AsReadOnly(); } - private void AddPlacement( - MutableNode root, - Placement placement, - EditorMenuContext context + private static Placement CreatePlacement( + string path, + string? actionId, + int order, + bool separatorBefore, + object? argument ) { - EditorActionContext actionContext = context.CreateActionContext(placement.argument); - EditorActionState state = placement.isGroup - ? EditorActionState.enabled - : placement.status ?? actions.Query(placement.actionId!, actionContext); - if (!state.isVisible) - return; + string[] segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (segments.Length == 0) + throw new InvalidOperationException("An editor menu path must contain at least one segment."); + return new Placement(string.Join('/', segments), segments, actionId, order, separatorBefore, argument); + } - string[] segments = placement.path.Split('/'); + private static void AddPlacement( + MutableNode root, + Placement placement + ) { MutableNode current = root; - for (int i = 0; i < segments.Length; i++) + for (int index = 0; index < placement.segments.Length; index++) { - string segment = segments[i]; + string segment = placement.segments[index]; if (!current.children.TryGetValue(segment, out MutableNode? child)) { child = new MutableNode(segment, placement.order, - i == segments.Length - 1 && placement.separatorBefore); + index == placement.segments.Length - 1 && placement.separatorBefore); current.children.Add(segment, child); } current = child; } - current.order = placement.order; current.separatorBefore = placement.separatorBefore; - if (placement.isGroup) - return; - current.actionId = placement.actionId; - current.argument = placement.argument; - current.state = state; - if (!string.IsNullOrWhiteSpace(state.displayName)) - current.label = state.displayName!; + current.placements.Add(placement); } - private static IReadOnlyList Freeze(IEnumerable nodes) - { - var result = new List(); - foreach (MutableNode node in nodes - .OrderBy(static value => value.order) - .ThenBy(static value => value.label, StringComparer.Ordinal)) + private static TemplateNode[] FreezeTemplate(IEnumerable nodes) + => nodes.OrderBy(static value => value.order).ThenBy(static value => value.label, StringComparer.Ordinal) + .Select(static node => new TemplateNode(node.label, node.order, node.separatorBefore, + node.placements.OrderBy(static placement => placement.order).ToArray(), FreezeTemplate(node.children.Values))).ToArray(); + + private static TemplateNode[] Merge( + TemplateNode[] nodes, + Placement placement, + int depth + ) { + string label = placement.segments[depth]; + int index = -1; + for (int candidate = 0; candidate < nodes.Length; candidate++) { - IReadOnlyList children = Freeze(node.children.Values); - EditorActionState state = node.actionId is null - ? new EditorActionState(children.Count > 0, children.Count > 0) - : node.state; - if (!state.isVisible && children.Count == 0) + if (!string.Equals(nodes[candidate].label, label, StringComparison.Ordinal)) continue; - result.Add(new EditorMenuItem( - node.label, - node.actionId ?? string.Empty, - node.order, - node.separatorBefore, - state, - children, - node.argument)); + index = candidate; + break; + } + TemplateNode current = index >= 0 ? nodes[index] : new TemplateNode(label, placement.order, false, [], []); + bool leaf = depth == placement.segments.Length - 1; + Placement[] placements = current.placements; + if (leaf) + { + int insertion = 0; + while (insertion < placements.Length && placements[insertion].order <= placement.order) + insertion++; + placements = new Placement[current.placements.Length + 1]; + Array.Copy(current.placements, placements, insertion); + placements[insertion] = placement; + Array.Copy(current.placements, insertion, placements, insertion + 1, current.placements.Length - insertion); } + TemplateNode replacement = new(label, current.order, current.separatorBefore, placements, + leaf ? current.children : Merge(current.children, placement, depth + 1)); + var result = new TemplateNode[nodes.Length + (index < 0 ? 1 : 0)]; + Array.Copy(nodes, result, nodes.Length); + result[index >= 0 ? index : nodes.Length] = replacement; + Array.Sort(result, static ( + left, + right + ) => left.order != right.order ? left.order.CompareTo(right.order) : StringComparer.Ordinal.Compare(left.label, right.label)); return result; } - private static string NormalizePath(string path) - { - string normalized = string.Join( - '/', - path.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); - if (string.IsNullOrEmpty(normalized)) - throw new InvalidOperationException("An editor menu path must contain at least one segment."); - return normalized; - } - private sealed record Placement( string path, + string[] segments, string? actionId, int order, bool separatorBefore, - object? argument, - EditorActionState? status, - bool isGroup + object? argument + ); + + private sealed record TemplateNode( + string label, + int order, + bool separatorBefore, + Placement[] placements, + TemplateNode[] children ); private sealed class MutableNode( string label, int order, bool separatorBefore - ) { + ) + { internal string label = label; internal int order = order; internal bool separatorBefore = separatorBefore; + internal readonly List placements = []; internal readonly Dictionary children = new(StringComparer.Ordinal); - internal string? actionId; - internal object? argument; - internal EditorActionState state = EditorActionState.hidden; } } diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Properties/ScriptingApi.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Properties/ScriptingApi.cs index 1cc55080b..d5b8f332f 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Properties/ScriptingApi.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Properties/ScriptingApi.cs @@ -1,25 +1,19 @@ using Inno.Scripting.Api; using Inno.Editor.Interactions; -[assembly: ScriptingApiNamespace( - "InnoEditor.Interactions", - "Inno.Editor.Interactions", - ScriptingApiScope.Editor)] - +[assembly: ScriptingApiNamespace("InnoEditor.Interactions", "Inno.Editor.Interactions", ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorInteractions), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorInteraction), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorSelectionState), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(IEditorSelectionCoordinator), ScriptingApiScope.Editor)] - [assembly: ScriptingApiExport(typeof(EditorAction), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorAction<>), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(EditorAction<,>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorAction<, >), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorArgumentAction<>), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(EditorPresentationAction<,>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorPresentationAction<, >), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorActionAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorActionContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorActionContext<>), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(EditorActionContext<,>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorActionContext<, >), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorActionArgumentContext<>), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorActionState), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorShortcutAttribute), ScriptingApiScope.Editor)] @@ -35,7 +29,6 @@ [assembly: ScriptingApiExport(typeof(EditorHistoryPayload), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorHistoryResult), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorHistoryTransaction), ScriptingApiScope.Editor)] - [assembly: ScriptingApiExport(typeof(IEditorDocumentService), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDocumentProvider), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDocumentContext), ScriptingApiScope.Editor)] @@ -49,7 +42,6 @@ [assembly: ScriptingApiExport(typeof(EditorViewportToolContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorViewportToolSession), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorPlanarNavigation), ScriptingApiScope.Editor)] - [assembly: ScriptingApiExport(typeof(EditorMenuAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorMenuBuilder), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorMenuContext), ScriptingApiScope.Editor)] @@ -57,20 +49,20 @@ [assembly: ScriptingApiExport(typeof(EditorMenuModel), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorMenuSource), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorMenuSourceAttribute), ScriptingApiScope.Editor)] - [assembly: ScriptingApiExport(typeof(EditorToolbarIcon), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorToolbarItem), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorToolbarItemAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorToolbarModel), ScriptingApiScope.Editor)] - [assembly: ScriptingApiExport(typeof(EditorDragData), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDragContext), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDrop), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(EditorDrop<,>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorDrop<, >), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropAttribute), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropContext), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(EditorDropContext<,>), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorDropContext<, >), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropPlacement), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropResult), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropStatus), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorDropVisual), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorEditGesture), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorSelectionSnapshot), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs index 6e5431741..ce91feaeb 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteraction.cs @@ -1,16 +1,19 @@ using System; - using Inno.Core.Identity; namespace Inno.Editor.Interactions; - /// -/// Provides a lightweight fluent handle for one interaction area and optional target. +/// Provides a fluent handle that borrows an optional target for a synchronous interaction request. /// +/// +/// A target-bearing handle retains that target while it is in use. Do not cache it across frames, +/// asynchronous work, or extension generations. Resolve live identity objects through Core Identity +/// before creating a fresh handle. Focus and deferred routing capture their own identity or weak reference. +/// Target-free handles can be reused within their owning interaction runtime. +/// public readonly struct EditorInteraction { private readonly EditorInteractions m_interactions; - internal EditorInteraction( EditorInteractions interactions, string area, @@ -26,34 +29,26 @@ internal EditorInteraction( /// Gets the stable interaction area. /// public string area { get; } - /// - /// Gets the optional target represented by this handle. + /// Gets the borrowed target without extending its validity or resolving a replacement generation. /// public object? target { get; } - /// /// Gets whether this handle's target is the current editor selection. /// - public bool isSelected => target is not null && - Equals(m_interactions.selection.selectedTarget, target); + public bool isSelected => m_interactions.selection.Contains(target); /// /// Marks this area and target as the active keyboard context. /// public void Focus() => m_interactions.Focus(area, target); - /// /// Selects this target, or clears selection when the target is . /// /// /// when the built-in selection action executed; otherwise, . /// - public bool Select() - => Execute(target is null - ? EditorBuiltInInteractionIds.C_CLEAR_SELECTION - : EditorBuiltInInteractionIds.C_SELECT); - + public bool Select() => Execute(target is null ? EditorBuiltInInteractionIds.C_CLEAR_SELECTION : EditorBuiltInInteractionIds.C_SELECT); /// /// Queries an action for this area and target. /// @@ -73,7 +68,6 @@ public EditorActionState Query( string action, object? argument = null ) => m_interactions.Query(action, area, target, argument); - /// /// Executes an action for this area and target. /// @@ -93,7 +87,6 @@ public bool Execute( string action, object? argument = null ) => m_interactions.Execute(action, area, target, argument); - /// /// Queues an action until the current UI traversal completes. /// @@ -110,7 +103,6 @@ public void Enqueue( string action, object? argument = null ) => m_interactions.Enqueue(action, area, target, argument); - /// /// Presents an active action in place of this target's normal content. /// @@ -130,7 +122,6 @@ public bool Present( string action, object? argument = null ) => m_interactions.Present(action, area, target, argument); - /// /// Gets whether an action owns an active multi-frame operation for this target. /// @@ -144,7 +135,6 @@ public bool Present( /// Thrown when is empty. /// public bool IsActive(string action) => m_interactions.IsActive(action, area, target); - /// /// Builds the complete contextual menu for this area and target. /// @@ -152,7 +142,13 @@ public bool Present( /// The immutable resolved menu model. /// public EditorMenuModel BuildMenu() => m_interactions.BuildMenu(area, target); - + /// + /// Builds searchable commands from this area's menus and applicable unplaced Actions. + /// + /// + /// Current command states and neutral placement arguments; execution revalidates their targets. + /// + public EditorMenuModel BuildCommands() => m_interactions.BuildCommands(area, target); /// /// Builds the complete compact toolbar for this area and target. /// @@ -160,7 +156,6 @@ public bool Present( /// The immutable resolved toolbar model. /// public EditorToolbarModel BuildToolbar() => m_interactions.BuildToolbar(area, target); - /// /// Resolves the shortcut displayed for an action in this area. /// @@ -179,9 +174,7 @@ public bool Present( public bool TryGetShortcut( string action, out HotKeyGesture gesture - ) - => m_interactions.TryGetShortcut(action, area, target, out gesture); - + ) => m_interactions.TryGetShortcut(action, area, target, out gesture); /// /// Gets the currently active drag identity, if its source remains registered. /// @@ -192,7 +185,6 @@ out HotKeyGesture gesture /// when a live drag source exists; otherwise, . /// public bool TryGetActiveDragIdentity(out RuntimeIdentity identity) => m_interactions.TryGetActiveDragIdentity(out identity); - /// /// Begins a managed drag originating from this area. /// @@ -203,7 +195,6 @@ out HotKeyGesture gesture /// The source object's domain-qualified transient identity. /// public RuntimeIdentity BeginDrag(EditorDragData data) => m_interactions.BeginDrag(area, data); - /// /// Queries this handle as a drop target. /// @@ -219,9 +210,7 @@ out HotKeyGesture gesture public EditorDropStatus QueryDrop( RuntimeIdentity identity, EditorDropPlacement placement = EditorDropPlacement.None - ) - => m_interactions.QueryDrop(identity, area, target, placement); - + ) => m_interactions.QueryDrop(identity, area, target, placement); /// /// Delivers a managed drop to this target. /// @@ -237,6 +226,5 @@ public EditorDropStatus QueryDrop( public EditorDropResult Drop( RuntimeIdentity identity, EditorDropPlacement placement = EditorDropPlacement.None - ) - => m_interactions.Drop(identity, area, target, placement); + ) => m_interactions.Drop(identity, area, target, placement); } diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs index 8f01095d7..5cd3b36d6 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorInteractions.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.Loader; - using Inno.Core.Events; using Inno.Core.Identity; using Inno.Core.Logging; @@ -11,14 +9,15 @@ using Inno.Editor.Core; namespace Inno.Editor.Interactions; - /// /// Provides the single presentation-independent entry point for editor actions, menus, selection, focus, and drag-and-drop. /// public sealed class EditorInteractions : IEditorSelectionCoordinator, IEditorHistoryIsolation { private readonly EditorContext m_editor; - private readonly IReadOnlyDictionary m_identityDomains; + private readonly Dictionary m_identityDomains; + private readonly int m_identityOwnerThread = Environment.CurrentManagedThreadId; + private bool m_identityDomainsRetired; private readonly EditorHistory m_history; private readonly EditorDocumentService m_documents; private readonly Logger m_log; @@ -28,12 +27,11 @@ public sealed class EditorInteractions : IEditorSelectionCoordinator, IEditorHis private EditorToolbarCatalog? m_toolbars; private EditorDropRouter? m_drops; private string m_focusedArea = EditorBuiltInInteractionIds.C_GLOBAL_AREA; - private object? m_focusedTarget; - private object? m_previousGenerationSelection; - private object? m_previousGenerationFocus; - private Guid? m_pendingSelectionId; - private Guid? m_pendingFocusId; - + private EditorTargetReference? m_focus; + private EditorTargetReference[]? m_previousGenerationSelection; + private EditorTargetReference? m_previousGenerationFocus; + private EditorTargetReference? m_previousGenerationAnchor; + private bool m_generationTransitionPrepared; internal EditorInteractions( EditorContext editor, IEnumerable identityDomains, @@ -43,26 +41,37 @@ Logger log ArgumentNullException.ThrowIfNull(identityDomains); m_identityDomains = identityDomains.ToDictionary(static allocator => allocator.domainId); m_log = log ?? throw new ArgumentNullException(nameof(log)); - m_documents = new EditorDocumentService(); - m_history = new EditorHistory(new EditorHistoryOptions - { - cacheDirectory = Path.Combine(editor.projectDirectory, "Library", "Editor", "History") - }, log); + selection = new EditorSelectionState(this); + m_documents = new EditorDocumentService(events); + m_history = new EditorHistory(new EditorHistoryOptions { cacheDirectory = Path.Combine(editor.projectDirectory, "Library", "Editor", "History") }, log); m_history.Attach(editor, this); } + /// + /// Gets synchronous Editor lifecycle notifications implemented by the Core event dispatcher. + /// + /// + /// Own and dispose a hub rather than retaining callbacks beyond their extension generation. + /// + [ScriptingApiIgnore] + public EventDispatcher events { get; } = new(); + + internal void PrepareOperation() => events.Emit(new EditorOperationPreparingEvent()); /// /// Gets the shared read-only editor selection state. /// - public EditorSelectionState selection { get; } = new(); + public EditorSelectionState selection { get; } - object? IEditorSelectionCoordinator.selectedTarget => selection.selectedTarget; + /// + /// Gets the immutable product keyboard policy used by commands and selection controls. + /// + public EditorKeyboardPolicy keyboard => m_editor.keyboard; + object? IEditorSelectionCoordinator.selectedTarget => selection.selectedTarget; /// /// Gets the transactional Undo and Redo history owned by this editor runtime. /// public IEditorHistory history => m_history; - /// /// Gets the headless reload-safe document lifetime used by dedicated asset editors and Inspectors. /// @@ -83,7 +92,8 @@ Logger log /// /// Do not retain the result across callbacks or generation changes. Candidate modules are never exposed. /// - public bool TryGetModule(out TModule? module) where TModule : EditorModule + public bool TryGetModule(out TModule? module) + where TModule : EditorModule { if (m_catalog is not null) return m_catalog.TryGetModule(out module); @@ -106,18 +116,44 @@ public bool TryGetModule(out TModule? module) where TModule : EditorMod /// [ScriptingApiIgnore] public IDisposable BeginHistoryIsolation() => m_history.BeginIsolation(); - internal EditorHistory historyHost => m_history; - /// /// Gets the area that most recently received keyboard focus. /// public string focusedArea => m_focusedArea; + /// + /// Gets immutable discovery metadata for the active, available panels. + /// + /// + /// Current panel declarations; the snapshot contains no panel or extension instances. + /// + public IReadOnlyList GetPanelDeclarations() => m_catalog?.GetPanelDeclarations() ?? Array.Empty(); + /// + /// Reads whether one available panel is currently open. + /// + /// + /// The stable panel identity. + /// + /// + /// Receives the open state, or false when the panel is unavailable. + /// + /// + /// True when the current generation contains the requested available panel. + /// + public bool TryGetPanelState( + string panelId, + out bool isOpen + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(panelId); + isOpen = false; + return m_catalog?.TryGetPanelState(panelId, out isOpen) == true; + } + /// /// Gets the target associated with the focused area. /// - public object? focusedTarget => m_focusedTarget; + public object? focusedTarget => m_focus?.Resolve(this); /// /// Creates a lightweight interaction handle for one area and optional target. @@ -138,7 +174,6 @@ public EditorInteraction For( string area, object? target = null ) => new(this, area, target); - /// /// Replaces the editor selection after closing presentations owned by other targets. /// @@ -147,11 +182,120 @@ public EditorInteraction For( /// public void SetSelection(object? target) { - PrepareSelectionChange(target); + PrepareOperation(); if (target is null) selection.Clear(); else selection.Select(target); + PrepareSelectionChange(target); + } + + /// + /// Replaces the ordered selection; duplicates are removed and the final target becomes primary. + /// + /// + /// Registered objects resolved for the current callback. + /// + /// + /// The range anchor, or null to use the first selected target. + /// + /// + /// The target collection or one member is null. + /// + /// + /// A target has no live identity registration. + /// + public void SetSelection( + IEnumerable targets, + IdentityObject? anchor + ) { + PrepareOperation(); + selection.Replace(targets, anchor); + PrepareSelectionChange(selection.selectedTarget); + } + + /// + /// Adds or removes a member while preserving the existing range anchor. + /// + /// + /// A currently registered selection target. + /// + /// + /// The target is null. + /// + public void ToggleSelection(IdentityObject target) + { + ArgumentNullException.ThrowIfNull(target); + var values = new List(); + _ = selection.CopyTargets(values); + bool removed = values.Remove(target); + if (!removed) + values.Add(target); + SetSelection(values, selection.anchor as IdentityObject ?? target); + } + + /// + /// Selects the inclusive visible range between the current anchor and the clicked target. + /// + /// + /// Runtime identities in the current visible sort and filter order, resolved through Core Identity only when selected. + /// + /// + /// The clicked range endpoint, which becomes primary. + /// + /// + /// Whether to preserve existing members outside the selected range. + /// + /// + /// The visible targets or endpoint is null. + /// + /// + /// The endpoint is not in the visible target order. + /// + /// + /// A range identity is unavailable; the current selection remains unchanged. + /// + public void SelectRange( + IReadOnlyList orderedTargets, + IdentityObject target, + bool additive = false + ) { + ArgumentNullException.ThrowIfNull(orderedTargets); + ArgumentNullException.ThrowIfNull(target); + IdentityObject anchor = selection.anchor as IdentityObject ?? target; + int first = -1; + int last = -1; + for (int i = 0; i < orderedTargets.Count; i++) + { + if (orderedTargets[i] == anchor.identity.runtimeIdentity) + first = i; + if (orderedTargets[i] == target.identity.runtimeIdentity) + last = i; + } + + if (last < 0) + throw new ArgumentException("The selection endpoint is not in the visible order.", nameof(target)); + if (first < 0) + { + first = last; + anchor = target; + } + + var values = new List(); + if (additive) + _ = selection.CopyTargets(values); + var included = new HashSet(values, ReferenceEqualityComparer.Instance); + for (int i = Math.Min(first, last); i <= Math.Max(first, last); i++) + { + if (!TryResolveIdentity(orderedTargets[i], out IdentityObject? value) || value is null) + throw new InvalidOperationException("The visible selection range contains an unavailable identity."); + if (!ReferenceEquals(value, target) && included.Add(value)) + values.Add(value); + } + + _ = values.Remove(target); + values.Add(target); + SetSelection(values, anchor); } /// @@ -170,7 +314,6 @@ public bool TryGetDragSource( RuntimeIdentity identity, out IdentityObject? source ) => Drops.TryGetSource(identity, out source); - /// /// Gets the active drag identity used to select the matching native payload domain. /// @@ -181,7 +324,6 @@ out IdentityObject? source /// when a live drag source exists; otherwise, . /// public bool TryGetActiveDragIdentity(out RuntimeIdentity identity) => Drops.TryGetActiveIdentity(out identity); - /// /// Toggles one panel in the currently active extension generation. /// @@ -200,6 +342,35 @@ public bool TogglePanel(string panelId) return m_catalog?.TryTogglePanel(panelId) == true; } + /// + /// Makes a temporary Core identity domain available to selection, focus and drag resolution. + /// + /// + /// The borrowed allocator owned by the temporary runtime session. + /// + /// + /// A routing lease; retire it after restoring Edit selection and before releasing the session. + /// + /// + /// The allocator is null. + /// + /// + /// The domain is already registered or the caller is not the owner thread. + /// + /// + /// The interaction runtime has retired its identity routing. + /// + [ScriptingApiIgnore] + public IDisposable RegisterIdentityDomain(IdentityAllocator allocator) + { + ArgumentNullException.ThrowIfNull(allocator); + EnsureIdentityDomainOwner(); + ObjectDisposedException.ThrowIf(m_identityDomainsRetired, this); + if (!m_identityDomains.TryAdd(allocator.domainId, allocator)) + throw new InvalidOperationException("The Editor identity domain is already registered."); + return new IdentityDomainLease(this, allocator.domainId); + } + /// /// Resolves a domain-qualified runtime identity through the Editor's complete identity-domain set. /// @@ -217,9 +388,7 @@ public bool TryResolveIdentity( RuntimeIdentity identity, out IdentityObject? target ) { - target = m_identityDomains.TryGetValue(identity.domainId, out IdentityAllocator? allocator) - ? allocator.Get(identity) - : null; + target = m_identityDomains.TryGetValue(identity.domainId, out IdentityAllocator? allocator) ? allocator.Get(identity) : null; return target is not null; } @@ -244,10 +413,7 @@ public bool TryResolveIdentity( Guid persistentId, out IdentityObject? target ) { - target = persistentId != Guid.Empty - && m_identityDomains.TryGetValue(domainId, out IdentityAllocator? allocator) - ? allocator.Get(persistentId) - : null; + target = persistentId != Guid.Empty && m_identityDomains.TryGetValue(domainId, out IdentityAllocator? allocator) ? allocator.Get(persistentId) : null; return target is not null; } @@ -305,131 +471,128 @@ internal void Update() internal void PrepareGenerationTransition() { - if (m_previousGenerationSelection is not null || m_previousGenerationFocus is not null) + if (m_generationTransitionPrepared) throw new InvalidOperationException("An interaction generation transition is already active."); - - m_previousGenerationSelection = selection.selectedTarget; - m_previousGenerationFocus = m_focusedTarget; - PrepareRetiringTarget( - selection.selectedTarget, - persistentId => m_pendingSelectionId = persistentId, - selection.Clear); - PrepareRetiringTarget( - m_focusedTarget, - persistentId => m_pendingFocusId = persistentId, - () => m_focusedTarget = null); + m_previousGenerationSelection = selection.CaptureReferences(); + m_previousGenerationAnchor = selection.anchorReference; + m_previousGenerationFocus = m_focus; + m_generationTransitionPrepared = true; Actions.ResetTransientState(); Drops.Cancel(); } internal void RollbackGenerationTransition() { - m_pendingSelectionId = null; - m_pendingFocusId = null; - if (m_previousGenerationSelection is object previousSelection) - selection.Select(previousSelection); - else - selection.Clear(); - m_focusedTarget = m_previousGenerationFocus; - m_previousGenerationSelection = null; - m_previousGenerationFocus = null; + Actions.ResetTransientState(); + if (m_previousGenerationSelection is not null) + selection.Restore(m_previousGenerationSelection, m_previousGenerationAnchor); + m_focus = m_previousGenerationFocus; + CompleteGenerationTransition(); } internal void CompleteGenerationTransition() { + Actions.InvalidateDiscovery(); + m_menus?.InvalidateDiscovery(); + selection.Rebind(); + m_focus = m_focus?.Rebind(this); m_previousGenerationSelection = null; + m_previousGenerationAnchor = null; m_previousGenerationFocus = null; + m_generationTransitionPrepared = false; } internal void Shutdown(IReadOnlyList actions) { Actions.Clear(actions); + m_menus?.InvalidateDiscovery(); Drops.Cancel(); m_catalog = null; selection.Clear(); m_focusedArea = EditorBuiltInInteractionIds.C_GLOBAL_AREA; - m_focusedTarget = null; - m_pendingSelectionId = null; - m_pendingFocusId = null; + m_focus = null; m_previousGenerationSelection = null; + m_previousGenerationAnchor = null; m_previousGenerationFocus = null; m_documents.Shutdown(); m_history.Dispose(); + m_identityDomains.Clear(); + m_identityDomainsRetired = true; } - internal bool DispatchShortcut(KeyPressedEvent keyEvent) - => Actions.DispatchShortcut( - keyEvent, - m_focusedArea, - m_focusedTarget ?? selection.selectedTarget); - + internal bool DispatchShortcut(KeyPressedEvent keyEvent) => Actions.DispatchShortcut(keyEvent, m_focusedArea, focusedTarget ?? selection.selectedTarget); internal void Focus( string area, object? target ) { m_focusedArea = area; - m_focusedTarget = target; + m_focus = target is null ? null : EditorTargetReference.Capture(target); } internal void PrepareSelectionChange(object? target) => Actions.LosePresentationExcept(target); - internal EditorActionState Query( string action, string area, object? target, object? argument - ) - => Actions.Query(action, CreateActionContext(area, target, argument)); - + ) => Actions.Query(action, CreateActionContext(area, target, argument)); internal bool Execute( string action, string area, object? target, object? argument - ) - => Actions.Execute(action, CreateActionContext(area, target, argument)); - + ) => Actions.Execute(action, CreateActionContext(area, target, argument)); internal void Enqueue( string action, string area, object? target, object? argument - ) - => Actions.Enqueue(action, CreateActionContext(area, target, argument)); - + ) => Actions.Enqueue(action, CreateActionContext(area, target, argument)); internal bool Present( string action, string area, object? target, object? argument - ) - => Actions.Present(action, CreateActionContext(area, target, argument)); - + ) => Actions.Present(action, CreateActionContext(area, target, argument)); internal bool IsActive( string action, string area, object? target ) => Actions.IsActive(action, CreateActionContext(area, target, null)); - internal EditorMenuModel BuildMenu( string area, object? target ) => Menus.Build(new EditorMenuContext(m_editor, this, area, target)); + internal EditorMenuModel BuildCommands( + string area, + object? target + ) { + EditorMenuModel menu = BuildMenu(area, target); + var placed = new HashSet(StringComparer.Ordinal); + Collect(menu.items); + IReadOnlyList additional = Actions.BuildUnplacedCommands(CreateActionContext(area, target, null), placed); + return new EditorMenuModel([..menu.items, ..additional]); + void Collect(IReadOnlyList items) + { + foreach (EditorMenuItem item in items) + { + if (item.actionId.Length != 0) + _ = placed.Add(item.actionId); + Collect(item.children); + } + } + } internal EditorToolbarModel BuildToolbar( string area, object? target - ) - => Toolbars.Build(CreateActionContext(area, target, argument: null)); - + ) => Toolbars.Build(CreateActionContext(area, target, argument: null)); internal bool TryGetShortcut( string action, string area, object? target, out HotKeyGesture gesture - ) - => Actions.TryGetShortcut(action, area, target, out gesture); - + ) => Actions.TryGetShortcut(action, area, target, out gesture); internal RuntimeIdentity BeginDrag( string area, EditorDragData data @@ -446,18 +609,9 @@ internal EditorDropStatus QueryDrop( object? target, EditorDropPlacement placement ) { - if (target is null || - !Drops.TryGetSource(identity, out IdentityObject? source) || - source is null) + if (target is null || !Drops.TryGetSource(identity, out IdentityObject? source) || source is null) return EditorDropStatus.rejected; - return Drops.Query(identity, new EditorDropContext( - m_editor, - this, - area, - new EditorDragData(source, string.Empty), - source, - target, - placement)); + return Drops.Query(identity, new EditorDropContext(m_editor, this, area, new EditorDragData(source, string.Empty), source, target, placement)); } internal EditorDropResult Drop( @@ -466,74 +620,48 @@ internal EditorDropResult Drop( object? target, EditorDropPlacement placement ) { - if (target is null || - !Drops.TryGetSource(identity, out IdentityObject? source) || - source is null) + if (target is null || !Drops.TryGetSource(identity, out IdentityObject? source) || source is null) return EditorDropResult.rejected; - return Drops.Drop(identity, new EditorDropContext( - m_editor, - this, - area, - new EditorDragData(source, string.Empty), - source, - target, - placement)); + return Drops.Drop(identity, new EditorDropContext(m_editor, this, area, new EditorDragData(source, string.Empty), source, target, placement)); } - private EditorActionRouter Actions => m_actions - ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); - - private EditorMenuCatalog Menus => m_menus - ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); - - private EditorDropRouter Drops => m_drops - ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); - - private EditorToolbarCatalog Toolbars => m_toolbars - ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); + private EditorActionRouter Actions => m_actions ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); + private EditorMenuCatalog Menus => m_menus ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); + private EditorDropRouter Drops => m_drops ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); + private EditorToolbarCatalog Toolbars => m_toolbars ?? throw new InvalidOperationException("Editor interactions are not attached to a runtime."); private EditorActionContext CreateActionContext( string area, object? target, object? argument - ) - => new(m_editor, this, area, target, argument); - + ) => new(m_editor, this, area, target, argument); private void ResolveGenerationTargets() { - if (m_pendingSelectionId is Guid selectionId) - { - m_pendingSelectionId = null; - IdentityObject? replacement = IdentityAllocator.hasCurrent - ? IdentityAllocator.current.Get(selectionId) - : null; - if (replacement is not null) - selection.Select(replacement); - else - selection.Clear(); - } - if (m_pendingFocusId is Guid focusId) - { - m_pendingFocusId = null; - m_focusedTarget = IdentityAllocator.hasCurrent - ? IdentityAllocator.current.Get(focusId) - : null; - } + selection.Rebind(); + m_focus = m_focus?.Rebind(this); + } + private void EnsureIdentityDomainOwner() + { + if (Environment.CurrentManagedThreadId != m_identityOwnerThread) + throw new InvalidOperationException("Editor identity domains must be changed on the owning thread."); } - private static void PrepareRetiringTarget( - object? target, - Action retainIdentity, - Action clear - ) { - if (target is null || - !target.GetType().Assembly.IsCollectible && - AssemblyLoadContext.GetLoadContext(target.GetType().Assembly)?.IsCollectible != true) + private sealed class IdentityDomainLease( + EditorInteractions owner, + IdentityDomainId domain + ) : IDisposable + { + private EditorInteractions? m_owner = owner; + /// + public void Dispose() { - return; + EditorInteractions? active = m_owner; + if (active is null) + return; + active.EnsureIdentityDomainOwner(); + active.m_identityDomains.Remove(domain); + m_owner = null; } - if (target is IdentityObject identityObject) - retainIdentity(identityObject.identity.persistentId); - clear(); } + } diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs deleted file mode 100644 index f8c9230cc..000000000 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorSelectionState.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Inno.Editor.Interactions; - -/// -/// Stores editor-wide object selection state. -/// -public sealed class EditorSelectionState -{ - private object? m_selectedTarget; - - /// - /// Gets the selected target, or when nothing is selected. - /// - public object? selectedTarget => m_selectedTarget; - - /// - /// Gets a monotonically increasing selection change version. - /// - public ulong version { get; private set; } - - /// - /// Selects a target object. - /// - /// - /// Target to select. - /// - internal void Select(object target) - { - ArgumentNullException.ThrowIfNull(target); - if (Equals(m_selectedTarget, target)) - { - return; - } - - m_selectedTarget = target; - version++; - } - - /// - /// Clears the current selection. - /// - internal void Clear() - { - if (m_selectedTarget is null) - { - return; - } - - m_selectedTarget = null; - version++; - } - - /// - /// Tries to read the current target as a requested type. - /// - /// - /// Requested target type. - /// - /// - /// Typed target when successful. - /// - /// - /// when the current target is compatible. - /// - public bool TryGet([NotNullWhen(true)] out TTarget? target) - { - if (m_selectedTarget is TTarget typedTarget) - { - target = typedTarget; - return true; - } - - target = default; - return false; - } - -} diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorTargetReference.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorTargetReference.cs new file mode 100644 index 000000000..790e2a766 --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/EditorTargetReference.cs @@ -0,0 +1,69 @@ +using System; +using Inno.Core.Identity; + +namespace Inno.Editor.Interactions; +internal sealed class EditorTargetReference +{ + private readonly object? m_value; + private readonly WeakReference? m_target; + private EditorTargetReference( + RuntimeIdentity? runtimeIdentity, + Guid persistentId, + object? value, + WeakReference? target + ) { + this.runtimeIdentity = runtimeIdentity; + this.persistentId = persistentId; + m_value = value; + m_target = target; + } + + internal RuntimeIdentity? runtimeIdentity { get; } + internal Guid persistentId { get; } + + internal static EditorTargetReference Capture(object? value) + { + if (value is IdentityObject target) + { + RuntimeIdentity runtime = target.identity.runtimeIdentity ?? throw new InvalidOperationException("An editor interaction target must belong to a live identity domain."); + return new EditorTargetReference(runtime, target.identity.persistentId, null, null); + } + + if (value is null or string or decimal or Guid or DateTime or DateTimeOffset + or DateOnly or TimeOnly or TimeSpan or Half or Int128 or UInt128) + return new EditorTargetReference(null, Guid.Empty, value, null); + Type type = value.GetType(); + if (type.IsPrimitive || type.IsEnum && !type.IsCollectible) + return new EditorTargetReference(null, Guid.Empty, value, null); + // A host-defined struct can still contain extension objects; boxing does not make it neutral. + return new EditorTargetReference(null, Guid.Empty, null, new WeakReference(value)); + } + + internal object? Resolve(EditorInteractions interactions) + { + if (runtimeIdentity is RuntimeIdentity runtime) + return interactions.TryResolveIdentity(runtime, out IdentityObject? target) ? target : null; + return m_target is not null ? m_target.TryGetTarget(out object? value) ? value : null : m_value; + } + + internal EditorTargetReference Rebind(EditorInteractions interactions) + { + if (runtimeIdentity is not RuntimeIdentity runtime || interactions.TryResolveIdentity(runtime, out _)) + return this; + if (interactions.TryResolveIdentity(runtime.domainId, persistentId, out IdentityObject? replacement) && replacement is not null) + { + return Capture(replacement); + } + + return this; + } + + internal bool Matches( + EditorInteractions interactions, + object? target + ) { + if (target is IdentityObject identity && runtimeIdentity is RuntimeIdentity runtime) + return identity.identity.runtimeIdentity == runtime; + return Equals(Resolve(interactions), target); + } +} diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs index dbf8b4f01..09ba9d282 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Runtime/IEditorSelectionCoordinator.cs @@ -1,10 +1,35 @@ +using System; +using Inno.Core.Identity; +using Inno.Scripting.Api; + namespace Inno.Editor.Interactions; /// -/// Provides the narrow selection boundary used by editor features that replace object instances. +/// Coordinates host-owned selection and the Core identity lifetime of temporary presentations. /// public interface IEditorSelectionCoordinator { + /// + /// Registers the Core identity domain used by a temporary scene presentation. + /// + /// + /// The borrowed allocator owned by that presentation's runtime session. + /// + /// + /// A routing lease released after restoring the preceding selection. + /// + /// + /// The allocator is null. + /// + /// + /// The domain is already active or the caller is not the owner thread. + /// + /// + /// The coordinator has retired. + /// + [ScriptingApiIgnore] + IDisposable RegisterIdentityDomain(IdentityAllocator allocator); + /// /// Gets the currently selected target, or when selection is empty. /// diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs new file mode 100644 index 000000000..2c07a9d0e --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionSnapshot.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Core.Identity; + +namespace Inno.Editor.Interactions; +/// +/// Captures ordered selection identities without retaining any domain object or extension instance. +/// +public sealed class EditorSelectionSnapshot +{ + private readonly EditorTargetReference[] m_targets; + internal EditorSelectionSnapshot( + IEnumerable targets, + RuntimeIdentity? anchor + ) { + m_targets = targets.ToArray(); + this.identities = Array.AsReadOnly(m_targets.Where(value => value.runtimeIdentity is not null).Select(value => value.runtimeIdentity!.Value).ToArray()); + this.anchor = anchor; + } + + /// + /// Gets unique selection identities in order; the final identity is the primary selection. + /// + public IReadOnlyList identities { get; } + /// + /// Gets the range anchor identity, or null when there is no registered anchor. + /// + public RuntimeIdentity? anchor { get; } + + /// + /// Resolves this immutable selection in its original domains, including same-identity recovery after a generation change. + /// + /// + /// The target type accepted by the current presenter. + /// + /// + /// The active session supplying the original identity domains. + /// + /// + /// A temporary caller-owned collection to clear and fill. + /// + /// + /// The number of currently available compatible targets; missing members remain represented by this snapshot. + /// + public int CopyTargets( + EditorInteractions interactions, + ICollection destination + ) + where TTarget : class + { + ArgumentNullException.ThrowIfNull(interactions); + ArgumentNullException.ThrowIfNull(destination); + destination.Clear(); + foreach (EditorTargetReference reference in m_targets) + if (reference.Rebind(interactions).Resolve(interactions) is TTarget target) + destination.Add(target); + return destination.Count; + } + + /// + /// Gets the primary identity, or null when this snapshot contains no registered targets. + /// + public RuntimeIdentity? primary => identities.Count == 0 ? null : identities[^1]; +} diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs new file mode 100644 index 000000000..93647e5cb --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Selection/EditorSelectionState.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Inno.Core.Identity; + +namespace Inno.Editor.Interactions; +/// +/// Owns ordered transient selection through domain-qualified identities instead of retaining live objects. +/// +public sealed class EditorSelectionState +{ + private readonly EditorInteractions m_interactions; + private EditorTargetReference[] m_targets = []; + private HashSet m_identityMembership = []; + private EditorTargetReference? m_anchor; + private EditorSelectionSnapshot? m_snapshot; + internal EditorSelectionState(EditorInteractions interactions) + { + m_interactions = interactions; + } + + /// + /// Gets the current primary target for this callback, or null when unavailable or unselected. + /// + public object? selectedTarget => m_targets.Length == 0 ? null : m_targets[^1].Resolve(m_interactions); + /// + /// Gets the number of selected identities, including temporarily unavailable members. + /// + public int count => m_targets.Length; + /// + /// Gets the current range anchor, or null when the anchor is unavailable. + /// + public object? anchor => m_anchor?.Resolve(m_interactions); + /// + /// Gets the selection revision, incremented only when membership, order or resolved generation changes. + /// + public ulong version { get; private set; } + + /// + /// Checks membership by the target's owning identity domain and generation. + /// + /// + /// The immediate target to inspect. + /// + /// + /// True when the target is a member of this selection. + /// + public bool Contains(object? target) + { + if (target is null) + return false; + if (target is IdentityObject identity) + return identity.identity.runtimeIdentity is RuntimeIdentity runtime && m_identityMembership.Contains(runtime); + foreach (EditorTargetReference entry in m_targets) + { + if (entry.Matches(m_interactions, target)) + return true; + } + + return false; + } + + /// + /// Copies currently available selected targets into a caller-owned temporary collection. + /// + /// + /// The target type accepted by the caller. + /// + /// + /// The collection to clear and fill; do not retain live entries across callbacks. + /// + /// + /// The number of targets resolved in this callback. + /// + /// + /// The destination is null. + /// + public int CopyTargets(ICollection destination) + where TTarget : class + { + ArgumentNullException.ThrowIfNull(destination); + destination.Clear(); + foreach (EditorTargetReference entry in m_targets) + { + if (entry.Resolve(m_interactions) is TTarget target) + destination.Add(target); + } + + return destination.Count; + } + + /// + /// Captures the ordered runtime identities without retaining their resolved objects. + /// + /// + /// An immutable callback-independent snapshot; resolve identities afresh before using targets. + /// + public EditorSelectionSnapshot Capture() + { + if (m_snapshot is not null) + return m_snapshot; + return m_snapshot = new EditorSelectionSnapshot(m_targets, m_anchor?.runtimeIdentity); + } + + /// + /// Resolves the primary selected target as the requested type for immediate use. + /// + /// + /// The expected target type. + /// + /// + /// Receives the available primary target, or null. + /// + /// + /// True when the current primary target has the requested type. + /// + public bool TryGet([NotNullWhen(true)] out TTarget? target) + { + if (selectedTarget is TTarget typed) + { + target = typed; + return true; + } + + target = default; + return false; + } + + internal void Select(object target) + { + ArgumentNullException.ThrowIfNull(target); + if (m_targets.Length == 1 && m_targets[0].Matches(m_interactions, target)) + return; + EditorTargetReference entry = EditorTargetReference.Capture(target); + m_targets = [entry]; + m_anchor = entry; + Changed(); + } + + internal void Replace( + IEnumerable targets, + IdentityObject? anchor + ) { + ArgumentNullException.ThrowIfNull(targets); + var entries = new List(); + var seen = new HashSet(); + foreach (IdentityObject target in targets) + { + ArgumentNullException.ThrowIfNull(target); + EditorTargetReference entry = EditorTargetReference.Capture(target); + if (seen.Add(entry.runtimeIdentity!.Value)) + entries.Add(entry); + } + + EditorTargetReference? nextAnchor = anchor is null ? entries.Count == 0 ? null : entries[0] : EditorTargetReference.Capture(anchor); + bool equal = entries.Count == m_targets.Length && nextAnchor?.runtimeIdentity == m_anchor?.runtimeIdentity; + for (int i = 0; equal && i < entries.Count; i++) + equal = entries[i].runtimeIdentity == m_targets[i].runtimeIdentity; + if (equal) + return; + m_targets = entries.ToArray(); + m_anchor = nextAnchor; + Changed(seen); + } + + internal void Clear() + { + if (m_targets.Length == 0) + return; + m_targets = []; + m_anchor = null; + Changed(); + } + + internal EditorTargetReference? anchorReference => m_anchor; + + internal EditorTargetReference[] CaptureReferences() => (EditorTargetReference[])m_targets.Clone(); + internal void Restore( + EditorTargetReference[] targets, + EditorTargetReference? anchor + ) { + m_targets = targets; + m_anchor = anchor; + Changed(); + } + + internal void Rebind() + { + bool changed = false; + for (int i = 0; i < m_targets.Length; i++) + { + EditorTargetReference rebound = m_targets[i].Rebind(m_interactions); + if (rebound.runtimeIdentity == m_targets[i].runtimeIdentity) + continue; + m_targets[i] = rebound; + changed = true; + } + + if (m_anchor is not null) + m_anchor = m_anchor.Rebind(m_interactions); + if (changed) + Changed(); + } + + private void Changed(HashSet? identities = null) + { + if (identities is null) + { + identities = new HashSet(m_targets.Length); + foreach (EditorTargetReference target in m_targets) + if (target.runtimeIdentity is RuntimeIdentity identity) + identities.Add(identity); + } + m_identityMembership = identities; + m_snapshot = null; + version++; + } +} diff --git a/src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs b/src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs index 76e65fdcd..03308f701 100644 --- a/src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs +++ b/src/composition/editor/framework/Inno.Editor.Interactions/Viewport/EditorViewportToolContext.cs @@ -2,7 +2,6 @@ using Inno.Core.Mathematics; namespace Inno.Editor.Interactions; - /// /// Coordinates pointer capture, transforms, and one history transaction for a viewport gesture. /// @@ -10,9 +9,8 @@ public sealed class EditorViewportToolContext { private readonly IEditorHistory m_history; private readonly IEditorViewportCoordinateConverter m_coordinates; - private EditorHistoryTransaction? m_transaction; + private EditorEditGesture? m_transaction; private int? m_capturedPointerId; - internal EditorViewportToolContext( IEditorHistory history, IEditorViewportCoordinateConverter coordinates @@ -25,12 +23,10 @@ IEditorViewportCoordinateConverter coordinates /// Gets whether this tool owns a pointer. /// public bool hasPointerCapture => m_capturedPointerId.HasValue; - /// /// Gets the captured pointer identity, or . /// public int? capturedPointerId => m_capturedPointerId; - /// /// Gets whether a history transaction is active for the current gesture. /// @@ -72,7 +68,7 @@ public void BeginHistoryGesture(string name) throw new InvalidOperationException("A viewport history gesture requires pointer capture."); if (m_transaction is not null) throw new InvalidOperationException("The current viewport gesture already owns a history transaction."); - m_transaction = m_history.BeginTransaction(name); + m_transaction = new EditorEditGesture(m_history, name); } /// @@ -83,14 +79,19 @@ public void BeginHistoryGesture(string name) /// public void CompleteHistoryGesture(bool commit) { - EditorHistoryTransaction? transaction = m_transaction; + EditorEditGesture? transaction = m_transaction; if (transaction is null) return; - m_transaction = null; if (commit) transaction.Commit(); else - _ = transaction.Rollback(); + { + EditorHistoryResult result = transaction.Cancel(); + if (!result.succeeded) + throw new InvalidOperationException($"The viewport gesture could not roll back: {result.message}"); + } + + m_transaction = null; transaction.Dispose(); } @@ -104,7 +105,6 @@ public void CompleteHistoryGesture(bool commit) /// World coordinates. /// public Vector2 ScreenToWorld(Vector2 screenPosition) => m_coordinates.ScreenToWorld(screenPosition); - /// /// Converts world coordinates to viewport-local pixel coordinates. /// @@ -115,9 +115,7 @@ public void CompleteHistoryGesture(bool commit) /// Viewport-local pixel coordinates. /// public Vector2 WorldToScreen(Vector2 worldPosition) => m_coordinates.WorldToScreen(worldPosition); - internal bool Accepts(int pointerId) => !m_capturedPointerId.HasValue || m_capturedPointerId.Value == pointerId; - internal void Cancel() { CompleteHistoryGesture(commit: false); diff --git a/src/composition/editor/framework/Inno.Editor.Settings/Inno.Editor.Settings.csproj b/src/composition/editor/framework/Inno.Editor.Settings/Inno.Editor.Settings.csproj index 970e97138..18eecd14a 100644 --- a/src/composition/editor/framework/Inno.Editor.Settings/Inno.Editor.Settings.csproj +++ b/src/composition/editor/framework/Inno.Editor.Settings/Inno.Editor.Settings.csproj @@ -10,21 +10,12 @@ - - - + + + + - + diff --git a/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs b/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs index b21593c24..87b97364f 100644 --- a/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs +++ b/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettings.cs @@ -1,7 +1,7 @@ +using Inno.Core.Events; using System; using System.Collections.Generic; using System.IO; - using Inno.Core.Logging; using Inno.Core.Serialization; using Inno.Extensibility.Types; @@ -9,19 +9,18 @@ using Inno.Editor.Interactions; namespace Inno.Editor.Settings; - /// /// Owns the discovered Settings catalog and the project-root Settings document. /// [EditorModule("editor-settings", order: int.MinValue)] public sealed class EditorSettings : EditorModule { + private readonly EventDispatcher m_events; private readonly EditorSettingsCatalog m_catalog; private readonly IEditorHistory m_history; private readonly Logger m_log; private readonly object m_sync = new(); private readonly EditorSettingsStore m_store; - /// /// Creates the Settings service for one project and its shared editor history. /// @@ -32,7 +31,7 @@ public sealed class EditorSettings : EditorModule /// The runtime that owns the shared Undo and Redo history. /// /// - /// Thrown when or is + /// Thrown when or is /// . /// /// @@ -62,20 +61,46 @@ SerializationRegistry serialization m_catalog = new EditorSettingsCatalog(types); m_store = new EditorSettingsStore(context.projectDirectory, serialization); m_history = interactions.history; + m_events = interactions.events; m_log = logs.CreateLogger(); } /// - /// Occurs after a complete Settings Apply, Undo, or Redo changes the effective document. - /// The committed service is the event's only argument. + /// Observes committed Apply, Undo and Redo changes through an owned Core Event hub. /// - public event Action? changed; + /// + /// Synchronous callback reading the current committed settings; observer failures are logged and isolated. + /// + /// + /// A subscription owner that must be disposed before its feature or extension generation retires. + /// + /// + /// The observer is null. + /// + public IDisposable ListenChanges(Action observer) + { + ArgumentNullException.ThrowIfNull(observer); + EventHub hub = m_events.CreateHub(); + hub.Listen(change => + { + if (!ReferenceEquals(change.settings, this)) + return; + try + { + observer(this); + } + catch (Exception exception) + { + m_log.Write(LogLevel.Error, "Editor Settings changed subscriber failed: {0}", [exception]); + } + }); + return hub; + } /// /// Gets the current discovered type-catalog revision. /// public long catalogRevision => m_catalog.snapshot.revision; - /// /// Gets all discovered path definitions in deterministic path order. /// @@ -98,9 +123,7 @@ public EditorSettingObject Get(string path) EditorSetting definition = ResolveField(path); lock (m_sync) { - return m_store.TryGet(definition.path, out EditorSettingObject? value) && value is not null - ? value - : definition.CreateDefault(); + return m_store.TryGet(definition.path, out EditorSettingObject? value) && value is not null ? value : definition.CreateDefault(); } } @@ -133,10 +156,8 @@ public bool Apply( IReadOnlySet? resets = null ) { ArgumentNullException.ThrowIfNull(values); - var normalizedValues = new Dictionary( - values.Count, - StringComparer.Ordinal); - foreach ((string path, EditorSettingObject value) in values) + var normalizedValues = new Dictionary(values.Count, StringComparer.Ordinal); + foreach ((string path, EditorSettingObject value)in values) { ArgumentNullException.ThrowIfNull(value); EditorSetting definition = ResolveField(path); @@ -162,26 +183,27 @@ public bool Apply( foreach (string path in normalizedResets) storageChanged |= replacement.Remove(path); } - foreach ((string path, EditorSettingObject candidate) in normalizedValues) + + foreach ((string path, EditorSettingObject candidate)in normalizedValues) { if (normalizedResets?.Contains(path) == true) continue; - if (replacement.TryGetValue(path, out EditorSettingObject? current) && - current.ValueEquals(candidate)) + if (replacement.TryGetValue(path, out EditorSettingObject? current) && current.ValueEquals(candidate)) { continue; } - if (!replacement.ContainsKey(path) && - ResolveField(path).CreateDefault().ValueEquals(candidate)) + + if (!replacement.ContainsKey(path) && ResolveField(path).CreateDefault().ValueEquals(candidate)) { continue; } + replacement[path] = candidate.Copy(); storageChanged = true; } + if (!storageChanged) return false; - before = m_store.GetDocument(); m_store.Replace(replacement); after = m_store.GetDocument(); @@ -207,7 +229,6 @@ public bool Apply( /// Releases resources retained by this feature after it has stopped. /// protected override void OnDispose() => m_catalog.Dispose(); - internal void RestoreFromHistory(ReadOnlySpan document) { lock (m_sync) @@ -226,27 +247,7 @@ internal void ValidateDocument(ReadOnlySpan document) m_store.ValidateDocument(document); } - internal void NotifyChanged() - { - Action? handlers = changed; - if (handlers is null) - return; - foreach (Delegate subscription in handlers.GetInvocationList()) - { - try - { - ((Action)subscription)(this); - } - catch (Exception exception) - { - m_log.Write( - LogLevel.Error, - "Editor Settings changed subscriber failed: {0}", - [exception]); - } - } - } - + internal void NotifyChanged() => m_events.Emit(new EditorSettingsChangedEvent(this)); private EditorSetting ResolveField(string path) { string normalized = EditorSettingsPathUtility.Normalize(path); diff --git a/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettingsChangedEvent.cs b/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettingsChangedEvent.cs new file mode 100644 index 000000000..74d805b2d --- /dev/null +++ b/src/composition/editor/framework/Inno.Editor.Settings/Runtime/EditorSettingsChangedEvent.cs @@ -0,0 +1,7 @@ +using Inno.Core.Events; + +namespace Inno.Editor.Settings; +internal sealed class EditorSettingsChangedEvent(EditorSettings settings) : Event +{ + internal EditorSettings settings { get; } = settings; +} diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorAuthoringServices.cs b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorAuthoringServices.cs index 3c0fafae3..bfad6ab29 100644 --- a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorAuthoringServices.cs +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorAuthoringServices.cs @@ -1,4 +1,5 @@ using Inno.Core.IO; +using Inno.Build; using System; using System.IO; using System.Linq; @@ -47,7 +48,8 @@ ScriptCompiler compiler internal static EditorAuthoringServices Start( string projectDirectory, - EngineHost engineHost + EngineHost engineHost, + string dotnetHost ) { ArgumentException.ThrowIfNullOrWhiteSpace(projectDirectory); ArgumentNullException.ThrowIfNull(engineHost); @@ -114,7 +116,8 @@ .. PluginSourceService.GetActivatableMounts(pluginScan) var compiler = new ScriptCompiler( new ScriptCompilerOptions { - projectRootDirectory = projectRoot + projectRootDirectory = projectRoot, + compilationEnvironment = new SdkScriptCompilationEnvironment(dotnetHost, projectRoot) }, assets, plugins); diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorHost.cs b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorHost.cs index 37fdbb303..b0cb47e6b 100644 --- a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorHost.cs +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorHost.cs @@ -297,7 +297,7 @@ private void Initialize() m_resources.Register(() => engineHost.logs.UnregisterSink(hostLog)); } EditorAuthoringServices activeAuthoring = m_resources.Acquire( - () => EditorAuthoringServices.Start(projectDirectory, engineHost), + () => EditorAuthoringServices.Start(projectDirectory, engineHost, m_launch.buildContext.dotnetHost), static services => services.Dispose()); m_authoring = activeAuthoring; _ = m_resources.Acquire( @@ -435,7 +435,7 @@ private void Initialize() presentation, reloadCoordinator), static service => service.Dispose()); - var editorContext = new EditorContext(projectDirectory, m_launch.keyboard); + var editorContext = new EditorContext(projectDirectory, m_launch.keyboard, EditorDefaultLayout.document); presentation.SetLayoutFile(null); presentation.LoadLayout(editorContext.imguiLayout); var playOptions = new RuntimeSessionOptions diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorLayer.cs b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorLayer.cs index 52d9d8c4e..e850923d7 100644 --- a/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorLayer.cs +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Hosting/EditorLayer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; - using Inno.Core.Events; using Inno.Core.Execution; using Inno.Core.Layers; @@ -11,10 +10,8 @@ using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.PlayMode; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; namespace Inno.Editor.Hosting; - /// /// Bridges the engine layer lifecycle to the attribute-discovered editor runtime. /// @@ -25,12 +22,13 @@ internal sealed class EditorLayer : Layer private readonly EditorProjectDiagnosticPublisher m_diagnostics = new(); private readonly EditorPlayModeLoop m_playModeLoop = new(); private readonly ImGuiEditorRuntime m_runtime; + private readonly Action m_drawFrame; + private readonly Action m_prepareFrame; private readonly Logger m_log; private readonly LifetimeScope m_resources = new(); private bool m_isShutdownPrepared; private float m_totalTime; private double m_nextPersistenceRetryTime; - internal EditorLayer( IPresentationContext presentation, EditorContext context, @@ -41,23 +39,15 @@ internal EditorLayer( m_presentation = presentation ?? throw new ArgumentNullException(nameof(presentation)); m_context = context; m_log = logs.CreateLogger(); - EditorWidget.SetupStyle(); m_resources.Own(m_diagnostics); - m_runtime = m_resources.Own(new ImGuiEditorRuntime( - context, - types, - logs, - CreateHostServices(hostServices, m_playModeLoop))); + m_runtime = m_resources.Own(new ImGuiEditorRuntime(context, types, logs, CreateHostServices(hostServices, m_playModeLoop))); + m_drawFrame = m_runtime.Draw; + m_prepareFrame = m_runtime.PrepareFrame; } internal int panelCount => m_runtime.panelCount; - internal bool isFocused { get; set; } - - internal float totalTime - { - set => m_totalTime = value; - } + internal float totalTime { set => m_totalTime = value; } internal void DisposeUnattached() { @@ -65,7 +55,6 @@ internal void DisposeUnattached() } internal void QuiescePlayMode() => m_playModeLoop.Quiesce(); - /// /// Starts the editor extension runtime and subscribes its host-level input handling. /// @@ -91,7 +80,6 @@ public override void OnDetach() /// The elapsed frame time in seconds. /// public override void OnUpdate(float deltaTime) => m_playModeLoop.Tick(deltaTime); - /// /// Updates editor presentation after the active Play Mode session has advanced. /// @@ -101,7 +89,7 @@ public override void OnDetach() public override void OnLateUpdate(float deltaTime) { m_runtime.Update(new EditorFrame(deltaTime, m_totalTime, isFocused)); - m_presentation.RenderFrame(m_runtime.Draw); + m_presentation.RenderFrame(m_drawFrame, m_prepareFrame); SaveLayoutIfChanged(); } @@ -129,11 +117,9 @@ internal bool PrepareShutdown() { if (m_diagnostics.PublishPersistenceFailure(exception)) { - m_log.Write( - LogLevel.Error, - "Project editor state could not be saved to '{0}': {1}", - [m_context.layoutPath, exception]); + m_log.Write(LogLevel.Error, "Project editor state could not be saved to '{0}': {1}", [m_context.layoutPath, exception]); } + return false; } } @@ -141,12 +127,11 @@ internal bool PrepareShutdown() private void SaveLayoutIfChanged() { bool layoutChanged = CaptureLayout(force: false); - if (!layoutChanged && - (!m_diagnostics.hasPersistenceFailure || - m_context.frame.totalTime < m_nextPersistenceRetryTime)) + if (!layoutChanged && (!m_diagnostics.hasPersistenceFailure || m_context.frame.totalTime < m_nextPersistenceRetryTime)) { return; } + try { _ = m_context.SaveLayoutIfChanged(); @@ -158,10 +143,7 @@ private void SaveLayoutIfChanged() m_nextPersistenceRetryTime = m_context.frame.totalTime + 1.0; if (m_diagnostics.PublishPersistenceFailure(exception)) { - m_log.Write( - LogLevel.Error, - "Project editor state could not be saved to '{0}': {1}", - [m_context.layoutPath, exception]); + m_log.Write(LogLevel.Error, "Project editor state could not be saved to '{0}': {1}", [m_context.layoutPath, exception]); } } } diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj b/src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj index f0289ea69..2edc0305c 100644 --- a/src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Inno.Editor.Hosting.csproj @@ -61,4 +61,7 @@ + + + diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/DefaultEditorLayout.ini b/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/DefaultEditorLayout.ini new file mode 100644 index 000000000..6dd4d9f52 --- /dev/null +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/DefaultEditorLayout.ini @@ -0,0 +1,75 @@ +[Window][WindowOverViewport_11111111] +Pos=0,31 +Size=1600,868 +Collapsed=0 + +[Window][Hierarchy] +Pos=731,31 +Size=430,868 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][Inspector] +Pos=1164,31 +Size=435,527 +Collapsed=0 +DockId=0x00000005,0 + +[Window][Scene] +Pos=0,31 +Size=401,371 +Collapsed=0 +DockId=0x00000003,0 + +[Window][Game] +Pos=403,31 +Size=326,371 +Collapsed=0 +DockId=0x00000004,0 + +[Window][Shader Editor] +Pos=1164,31 +Size=435,527 +Collapsed=0 +DockId=0x00000005,1 + +[Window][File] +Pos=0,404 +Size=729,495 +Collapsed=0 +DockId=0x00000008,0 + +[Window][Console] +Pos=1164,560 +Size=435,338 +Collapsed=0 +DockId=0x00000006,1 + +[Window][Stats] +Pos=1164,560 +Size=435,338 +Collapsed=0 +DockId=0x00000006,0 + +[Docking][Data] +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,31 Size=1600,868 Split=X + DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=729,868 Split=Y Selected=0x39CACFEF + DockNode ID=0x00000007 Parent=0x00000001 SizeRef=661,371 Split=X Selected=0xE601B12F + DockNode ID=0x00000003 Parent=0x00000007 SizeRef=401,371 Selected=0xE601B12F + DockNode ID=0x00000004 Parent=0x00000007 SizeRef=326,371 Selected=0xD1EB2482 + DockNode ID=0x00000008 Parent=0x00000001 SizeRef=661,495 Selected=0x39CACFEF + DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=868,868 Split=X Selected=0x36DC96AB + DockNode ID=0x0000000B Parent=0x00000002 SizeRef=430,868 Selected=0xBABDAE5E + DockNode ID=0x0000000C Parent=0x00000002 SizeRef=435,868 Split=Y Selected=0x36DC96AB + DockNode ID=0x00000005 Parent=0x0000000C SizeRef=562,527 CentralNode=1 Selected=0x36DC96AB + DockNode ID=0x00000006 Parent=0x0000000C SizeRef=562,338 Selected=0x590DF097 + +[InnoEditor][Panels] +asset.file-browser=true +diagnostics.console=true +diagnostics.stats=true +rendering.game-view=true +rendering.scene-view=true +rendering.shader-editor=true +scene.hierarchy=true +scene.inspector=true diff --git a/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/EditorDefaultLayout.cs b/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/EditorDefaultLayout.cs new file mode 100644 index 000000000..dfd2e0adf --- /dev/null +++ b/src/composition/editor/hosting/Inno.Editor.Hosting/Layout/EditorDefaultLayout.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Inno.Editor.Hosting; + +internal static class EditorDefaultLayout +{ + private const string C_RESOURCE_NAME = "Inno.Editor.Hosting.DefaultEditorLayout.ini"; + + internal static string document { get; } = ReadDocument(); + + private static string ReadDocument() + { + using Stream stream = typeof(EditorDefaultLayout).Assembly.GetManifestResourceStream(C_RESOURCE_NAME) + ?? throw new InvalidOperationException("The standard Editor layout resource is missing."); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs deleted file mode 100644 index 471924f4f..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetEditorModule.cs +++ /dev/null @@ -1,734 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.ExceptionServices; - -using Inno.Assets; -using Inno.Assets.Pipeline; -using Inno.Core.Logging; -using Inno.Extensibility.Types; -using Inno.Editor.Core; -using Inno.Editor.Assets; -using Inno.Editor.Inspection; -using Inno.Editor.Interactions; -using Inno.Editor.Settings; -using Inno.Plugins.Authoring; -using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; - -namespace Inno.Editor.Panel.FileBrowser; - -/// -/// Owns shared Asset Browser state and asset-type extension dispatch. -/// -[EditorModule("asset-browser", order: 100)] -public sealed partial class AssetEditorModule : EditorModule, IInspectionIconProvider -{ - private readonly AssetPipeline m_pipeline; - private readonly AssetEditorRegistry m_editors; - private readonly AssetCreationRegistry m_creations; - private readonly AssetIconRegistry m_icons; - private readonly EditorSettings m_settings; - private readonly EditorInteractions m_interactions; - private readonly Logger m_log; - private readonly PluginEnvironment m_plugins; - private EditorContext? m_context; - - /// - /// Creates the Asset Browser feature module. - /// - /// - /// The active editor interaction entry point. - /// - /// - /// The project Settings service that owns semantic icon values. - /// - /// - /// The isolated authoring asset pipeline used by browser operations. - /// - /// - /// The active Plugin catalog that authoritatively identifies Plugin-owned source mounts. - /// - /// - /// The active type catalog used to build extension registry snapshots. - /// - /// - /// The application log router used for Asset Browser operation diagnostics. - /// - /// - /// Thrown when any required service is . - /// - public AssetEditorModule( - EditorInteractions interactions, - EditorSettings settings, - AssetPipeline pipeline, - PluginEnvironment plugins, - TypeCatalog types, - LogRouter logs - ) { - m_interactions = interactions ?? throw new ArgumentNullException(nameof(interactions)); - m_settings = settings ?? throw new ArgumentNullException(nameof(settings)); - m_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); - m_plugins = plugins ?? throw new ArgumentNullException(nameof(plugins)); - ArgumentNullException.ThrowIfNull(types); - ArgumentNullException.ThrowIfNull(logs); - m_log = logs.CreateLogger(); - m_editors = new AssetEditorRegistry(types); - m_creations = new AssetCreationRegistry(types); - m_icons = new AssetIconRegistry(m_settings, types); - browser = new AssetBrowserState(interactions, pipeline); - } - - /// - /// Gets shared Asset Browser navigation and selection state. - /// - public AssetBrowserState browser { get; } - - /// - /// Captures an immutable snapshot of the current observable state. - /// - /// - /// The lifecycle or domain state applied by this operation. - /// - protected override void Capture(EditorState state) - { - state.Set("root", browser.root.ToString()); - state.Set("assetsDirectory", browser.GetDirectory(AssetBrowserRoot.Assets)); - state.Set("pluginsDirectory", browser.GetDirectory(AssetBrowserRoot.Plugins)); - } - - /// - /// Restores the supplied snapshot while preserving current invariants. - /// - /// - /// The lifecycle or domain state applied by this operation. - /// - protected override void Restore(EditorState state) - { - AssetBrowserRoot restoredRoot = Enum.TryParse( - state.Get("root", string.Empty), - out AssetBrowserRoot parsedRoot) - ? parsedRoot - : AssetBrowserRoot.Assets; - string assetsDirectory = RestoreDirectory( - AssetBrowserRoot.Assets, - state.Get("assetsDirectory", string.Empty)); - string pluginsDirectory = RestoreDirectory( - AssetBrowserRoot.Plugins, - state.Get("pluginsDirectory", string.Empty)); - browser.Restore(restoredRoot, assetsDirectory, pluginsDirectory); - } - - private string RestoreDirectory( - AssetBrowserRoot root, - string path - ) { - string directory = NormalizePath(path); - if (string.IsNullOrEmpty(directory)) - return string.Empty; - AssetPath isolated = AssetPath.Parse(directory); - if ((root == AssetBrowserRoot.Assets) != (isolated.source == AssetSourceId.project)) - return string.Empty; - while (!IsAvailableDirectory(directory)) - { - string parent = GetParentDirectory(directory); - if (string.Equals(parent, directory, StringComparison.Ordinal)) - return string.Empty; - directory = parent; - } - return directory; - } - - private bool IsAvailableDirectory(string path) - => m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(path), out AssetFileEntry entry) - && entry.isDirectory; - - internal EditorInteractions interactions => m_interactions; - - internal AssetPipeline pipeline => m_pipeline; - - internal IReadOnlyList creationTemplates => m_creations.templates; - - internal bool CanCreateAsset( - string directory, - string templateId - ) { - if (!m_pipeline.isInitialized || !m_creations.TryGet(templateId, out _)) - return false; - AssetPath path = AssetPath.Parse(NormalizePath(directory)); - AssetSourceMount? mount = m_pipeline.sourceMounts.FirstOrDefault(source => source.id == path.source); - return mount is { isReadOnly: false } - && (string.IsNullOrEmpty(path.localPath) - || m_pipeline.TryGetFileSystemEntry(path, out AssetFileEntry entry) && entry.isDirectory); - } - - internal AssetFileEntry CreateAsset( - string directory, - string templateId - ) { - if (!m_creations.TryGet(templateId, out AssetCreationRegistry.Registration? registration) - || registration is null) - { - throw new InvalidOperationException($"Asset creation template '{templateId}' is unavailable."); - } - if (!CanCreateAsset(directory, templateId)) - throw new InvalidOperationException("The selected Asset directory is not writable."); - - AssetCreationMenuAttribute declaration = registration.declaration; - AssetPath parent = AssetPath.Parse(NormalizePath(directory)); - AssetSourceMount mount = m_pipeline.sourceMounts.Single(source => source.id == parent.source); - string prefix = parent.localPath.TrimEnd('/'); - if (prefix.Length != 0) - prefix += "/"; - int suffix = 1; - AssetPath path; - do - { - string number = suffix == 1 ? string.Empty : " " + suffix; - path = new AssetPath( - parent.source, - prefix + declaration.defaultName + number + declaration.extension); - suffix++; - } - while (File.Exists(mount.Resolve(path.localPath)) - || File.Exists(mount.Resolve(path.localPath) + ".imeta")); - - AssetObject candidate = registration.template.Create() - ?? throw new InvalidOperationException( - $"Asset creation template '{templateId}' returned no asset."); - if (candidate.GetType() != registration.template.assetType) - { - throw new InvalidOperationException( - $"Asset creation template '{templateId}' returned '{candidate.GetType().FullName}' " + - $"instead of '{registration.template.assetType.FullName}'."); - } - AssetSourceStore sources = m_pipeline.CreateSourceStore(); - byte[] source = registration.template.Encode(new AssetCreationContext(sources), candidate) - ?? throw new InvalidOperationException( - $"Asset creation template '{templateId}' returned no source data."); - return CreateSource(path, source); - } - - internal bool IsPluginSource(AssetSourceId source) => m_plugins.TryGet(source, out _); - - internal bool IsPluginRoot(AssetFileEntry entry) - { - ArgumentNullException.ThrowIfNull(entry); - return string.IsNullOrEmpty(entry.assetPath.localPath) && IsPluginSource(entry.source); - } - - internal string folderIcon => m_settings - .Get("Editor/Appearance/Icons/Folder") - .GetAsString("value", Inno.Adapter.Presentation.ImGui.ImGuiIcon.Folder)!; - - internal bool TryCreateContext( - EditorContext editor, - string relativePath, - out AssetEditorContext? context - ) { - if (!m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry)) - { - context = null; - return false; - } - _ = m_pipeline.TryGetInfo(AssetPath.Parse(relativePath), out AssetInfo? info); - _ = m_pipeline.TryGetAssetType(AssetPath.Parse(relativePath), out Type? assetType); - context = new AssetEditorContext( - editor, - m_interactions, - entry.assetPath.ToString(), - entry.name, - entry.isDirectory, - info, - assetType, - () => CreateDefaultDragData(entry)); - return true; - } - - internal bool Open(AssetEditorContext context) - { - AssetEditor editor = m_editors.Resolve(context.assetType); - if (!editor.CanOpen(context)) - return false; - try - { - editor.Open(context); - return true; - } - catch (Exception exception) - { - m_log.Write( - LogLevel.Error, - "Failed to open asset '{0}': {1}", - [context.relativePath, exception]); - return false; - } - } - - internal EditorValidationResult ValidateRename( - AssetEditorContext context, - string newName - ) { - AssetEditor editor = m_editors.Resolve(context.assetType); - return ToEditorValidation(ValidateRename( - editor, - context, - Normalize(context.relativePath), - newName, - validateEditor: true, - out _)); - } - - internal void Rename( - AssetEditorContext context, - string newName - ) { - AssetEditor editor = m_editors.Resolve(context.assetType); - AssetOperationValidation validation = ValidateRename( - editor, - context, - Normalize(context.relativePath), - newName, - validateEditor: false, - out string targetPath); - if (!validation.isValid) - throw new InvalidOperationException(validation.message); - string sourcePath = Normalize(context.relativePath); - EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); - if (!result.succeeded) - throw new InvalidOperationException(result.message); - var data = new AssetHistoryData( - AssetHistoryOperationKind.Move, - sourcePath, - targetPath, - isDirectory: context.isDirectory, - archive: []); - try - { - m_interactions.history.RecordApplied( - "Rename Asset", - new EditorHistoryChange( - AssetHistoryKinds.SourceOperation, - EditorHistoryPayload.FromBytes(data.Encode()))); - } - catch (Exception exception) - { - RollbackAndRethrow( - exception, - () => RequireMove(editor, context, targetPath, sourcePath)); - } - } - - /// - /// Determines whether an asset source can move into a target directory without conflicts. - /// - /// - /// The current source-relative asset path. - /// - /// - /// The destination source-relative directory, or an empty string for the root. - /// - /// - /// when the requested move is valid. - /// - internal bool CanMoveToDirectory( - string sourcePath, - string targetDirectory - ) - => TryPrepareMove( - sourcePath, - targetDirectory, - validateEditor: true, - out _, - out _, - out _); - - /// - /// Moves an asset into a directory and records the committed path transaction in Editor history. - /// - /// - /// The current source-relative asset path. - /// - /// - /// The destination source-relative directory, or an empty string for the root. - /// - /// - /// The active history that receives the committed move. - /// - /// - /// The source entry at its committed destination path. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when validation or the Asset transaction fails. - /// - internal AssetFileEntry MoveToDirectoryWithHistory( - string sourcePath, - string targetDirectory, - IEditorHistory history - ) { - ArgumentNullException.ThrowIfNull(history); - if (!TryPrepareMove( - sourcePath, - targetDirectory, - validateEditor: true, - out AssetEditorContext? context, - out AssetEditor? editor, - out string targetPath) || - context is null || - editor is null) - { - throw new InvalidOperationException( - $"Asset '{sourcePath}' cannot be moved to '{targetDirectory}'."); - } - - string normalizedSource = Normalize(sourcePath); - EditorHistoryResult result = MoveAsset(editor, context, normalizedSource, targetPath); - if (!result.succeeded) - throw new InvalidOperationException(result.message); - - var data = new AssetHistoryData( - AssetHistoryOperationKind.Move, - normalizedSource, - targetPath, - context.isDirectory, - archive: []); - try - { - history.RecordApplied( - "Move Asset", - new EditorHistoryChange( - AssetHistoryKinds.SourceOperation, - EditorHistoryPayload.FromBytes(data.Encode()))); - } - catch (Exception exception) - { - RollbackAndRethrow( - exception, - () => RequireMove(editor, context, targetPath, normalizedSource)); - } - - return m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out AssetFileEntry moved) - ? moved - : throw new InvalidOperationException( - $"Moved asset '{targetPath}' is unavailable after the transaction."); - } - - internal bool Delete(AssetEditorContext context) - { - AssetEditor editor = m_editors.Resolve(context.assetType); - AssetOperationValidation validation = editor.ValidateDelete(context); - if (!validation.isValid) - { - m_log.Write( - LogLevel.Warn, - "Asset delete was rejected for '{0}': {1}", - [context.relativePath, validation.message]); - return false; - } - string path = context.relativePath; - m_pipeline.Delete(AssetPath.Parse(path)); - try - { - editor.OnDeleted(context); - } - catch (Exception exception) - { - m_log.Write( - LogLevel.Error, - "Asset editor delete hook failed for '{0}': {1}", - [path, exception]); - } - return true; - } - - internal bool DeleteWithHistory( - EditorActionContext context, - AssetEditorContext asset - ) { - ArgumentNullException.ThrowIfNull(context); - ArgumentNullException.ThrowIfNull(asset); - byte[] archive = AssetSourceArchive.Capture(m_pipeline, asset.relativePath, out bool isDirectory); - if (!Delete(asset)) - return false; - var data = new AssetHistoryData( - AssetHistoryOperationKind.Delete, - Normalize(asset.relativePath), - string.Empty, - isDirectory, - archive); - try - { - context.history.RecordApplied( - "Delete Asset", - new EditorHistoryChange( - AssetHistoryKinds.SourceOperation, - EditorHistoryPayload.FromBytes(data.Encode()))); - } - catch (Exception exception) - { - RollbackAndRethrow( - exception, - () => AssetSourceArchive.Restore( - m_pipeline, - Normalize(asset.relativePath), - isDirectory, - archive)); - } - return true; - } - - internal void MoveFromHistory( - string sourcePath, - string targetPath - ) { - EditorContext editorContext = m_context - ?? throw new InvalidOperationException("Asset editor module is not attached."); - if (!TryCreateContext(editorContext, sourcePath, out AssetEditorContext? context) || context is null) - throw new InvalidOperationException($"Asset '{sourcePath}' is no longer available."); - AssetEditor editor = m_editors.Resolve(context.assetType); - EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); - if (!result.succeeded) - throw new InvalidOperationException(result.message); - } - - internal void DeleteFromHistory(string relativePath) - { - EditorContext editorContext = m_context - ?? throw new InvalidOperationException("Asset editor module is not attached."); - if (!TryCreateContext(editorContext, relativePath, out AssetEditorContext? context) || context is null) - throw new InvalidOperationException($"Asset '{relativePath}' is no longer available."); - if (!Delete(context)) - throw new InvalidOperationException($"Asset '{relativePath}' could not be deleted."); - } - - internal void SelectPath(string relativePath) - { - object? target = m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry) - ? entry - : null; - _ = m_interactions.For(FileBrowserInteractionIds.C_AREA, target).Select(); - } - - internal bool TryCreateDragData( - AssetEditorContext context, - out EditorDragData? data - ) { - AssetEditor editor = m_editors.Resolve(context.assetType); - if (!editor.CanStartDrag(context)) - { - data = null; - return false; - } - data = editor.CreateDragData(context); - return data is not null; - } - - /// - /// Resolves the presentation icon registered for an asset type or source extension. - /// - /// - /// The source entry whose presentation icon should be resolved. - /// - /// - /// The most specific registered icon, or the built-in directory or file icon when no - /// registration matches the entry. - /// - /// - /// Thrown when is . - /// - public string GetIcon(AssetFileEntry entry) - { - ArgumentNullException.ThrowIfNull(entry); - if (entry.isDirectory) - return folderIcon; - _ = m_pipeline.TryGetAssetType(entry.assetPath, out Type? assetType); - if (m_icons.TryResolve(assetType, entry.assetPath.ToString(), out string icon)) - { - return icon; - } - return m_settings - .Get("Editor/Appearance/Icons/File") - .GetAsString("value", Inno.Adapter.Presentation.ImGui.ImGuiIcon.File)!; - } - - /// - /// Initializes this feature when its owning runtime becomes active. - /// - /// - /// The context that supplies state and services for this operation. - /// - protected override void OnStart(EditorContext context) - { - m_context = context ?? throw new ArgumentNullException(nameof(context)); - } - - /// - /// Stops this feature before its owning runtime releases the active generation. - /// - /// - /// The context that supplies state and services for this operation. - /// - protected override void OnStop(EditorContext context) - { - m_context = null; - } - - /// - /// Releases resources retained by this feature after it has stopped. - /// - protected override void OnDispose() - { - m_creations.Dispose(); - m_editors.Dispose(); - m_icons.Dispose(); - } - - private AssetOperationValidation ValidateRename( - AssetEditor editor, - AssetEditorContext context, - string sourcePath, - string newName, - bool validateEditor, - out string targetPath - ) { - newName = newName.Trim(); - targetPath = string.Empty; - if (string.IsNullOrEmpty(newName)) - return AssetOperationValidation.Invalid("An asset name is required."); - if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || - newName.Contains('/') || newName.Contains('\\')) - { - return AssetOperationValidation.Invalid( - "Asset names cannot contain path separators or invalid file-name characters."); - } - - string renamedEntry = ComposeRenamedEntryName(sourcePath, newName, context.isDirectory); - string parent = Normalize(Path.GetDirectoryName(sourcePath)); - targetPath = string.IsNullOrEmpty(parent) ? renamedEntry : $"{parent}/{renamedEntry}"; - if (!string.Equals(sourcePath, targetPath, StringComparison.OrdinalIgnoreCase) && - m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) - { - return AssetOperationValidation.Invalid($"Asset '{targetPath}' already exists."); - } - return validateEditor - ? editor.ValidateRename(context, targetPath) - : AssetOperationValidation.valid; - } - - private bool TryPrepareMove( - string sourcePath, - string targetDirectory, - bool validateEditor, - out AssetEditorContext? context, - out AssetEditor? editor, - out string targetPath - ) { - context = null; - editor = null; - targetPath = string.Empty; - string source = Normalize(sourcePath); - string directory = Normalize(targetDirectory); - AssetPath isolatedSource = AssetPath.Parse(source); - AssetPath isolatedDirectory = AssetPath.Parse(directory); - if (isolatedSource.source != AssetSourceId.project || - isolatedDirectory.source != AssetSourceId.project) - { - return false; - } - EditorContext? editorContext = m_context; - if (editorContext is null || - string.IsNullOrEmpty(source) || - !TryCreateContext(editorContext, source, out context) || - context is null) - { - return false; - } - if (!string.IsNullOrEmpty(directory) && - (!m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(directory), out AssetFileEntry destination) || - !destination.isDirectory)) - { - return false; - } - - string parent = Normalize(Path.GetDirectoryName(source)); - if (string.Equals(parent, directory, StringComparison.OrdinalIgnoreCase)) - return false; - if (context.isDirectory && - (string.Equals(source, directory, StringComparison.OrdinalIgnoreCase) || - directory.StartsWith(source + "/", StringComparison.OrdinalIgnoreCase))) - { - return false; - } - - string name = Path.GetFileName(source); - targetPath = string.IsNullOrEmpty(directory) ? name : $"{directory}/{name}"; - if (m_pipeline.TryGetFileSystemEntry(AssetPath.Parse(targetPath), out _)) - return false; - - editor = m_editors.Resolve(context.assetType); - return !validateEditor || editor.ValidateRename(context, targetPath).isValid; - } - - private EditorHistoryResult MoveAsset( - AssetEditor editor, - AssetEditorContext context, - string sourcePath, - string targetPath - ) { - m_pipeline.Move(AssetPath.Parse(sourcePath), AssetPath.Parse(targetPath)); - try - { - editor.OnRenamed(context, sourcePath, targetPath); - } - catch (Exception exception) - { - m_log.Write( - LogLevel.Error, - "Asset editor rename hook failed for '{0}': {1}", - [targetPath, exception]); - } - return EditorHistoryResult.Success(); - } - - private void RequireMove( - AssetEditor editor, - AssetEditorContext context, - string sourcePath, - string targetPath - ) { - EditorHistoryResult result = MoveAsset(editor, context, sourcePath, targetPath); - if (!result.succeeded) - throw new InvalidOperationException(result.message); - } - - private static void RollbackAndRethrow( - Exception failure, - Action rollback - ) { - try - { - rollback(); - } - catch (Exception rollbackException) - { - throw new AggregateException( - "An Asset mutation could not be recorded and its compensation also failed.", - failure, - rollbackException); - } - ExceptionDispatchInfo.Capture(failure).Throw(); - } - - private static EditorValidationResult ToEditorValidation(AssetOperationValidation validation) - => new(validation.isValid, validation.message); - - private static string Normalize(string? path) - => string.IsNullOrWhiteSpace(path) - ? string.Empty - : path.Replace('\\', '/').Trim('/'); - - private static EditorDragData CreateDefaultDragData(AssetFileEntry entry) => new(entry, entry.name); -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetTypeDistance.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetTypeDistance.cs deleted file mode 100644 index 0b8651454..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetTypeDistance.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace Inno.Editor.Panel.FileBrowser; - -internal static class AssetTypeDistance -{ - internal static bool TryGet( - Type concreteType, - Type candidateType, - out int distance - ) { - if (!candidateType.IsAssignableFrom(concreteType)) - { - distance = int.MaxValue; - return false; - } - - if (concreteType == candidateType) - { - distance = 0; - return true; - } - - distance = 1; - Type? current = concreteType.BaseType; - while (current is not null) - { - if (current == candidateType) - return true; - distance++; - current = current.BaseType; - } - - distance = candidateType.IsInterface ? 1 : distance; - return true; - } -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserChangeTracker.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserChangeTracker.cs index 0da02ceee..269c627d4 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserChangeTracker.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserChangeTracker.cs @@ -1,31 +1,29 @@ - +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.IO; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed class FileBrowserChangeTracker(AssetEditorModule assets) +internal sealed class FileBrowserChangeTracker(AssetBrowserModule assets) { private EditorContext? m_context; - internal void Attach(EditorContext context) { ArgumentNullException.ThrowIfNull(context); m_context = context; - assets.pipeline.Changed -= OnAssetsChanged; - assets.pipeline.Changed += OnAssetsChanged; - assets.pipeline.SourceMountsChanged -= OnSourceMountsChanged; - assets.pipeline.SourceMountsChanged += OnSourceMountsChanged; + assets.edits.pipeline.Changed -= OnAssetsChanged; + assets.edits.pipeline.Changed += OnAssetsChanged; + assets.edits.pipeline.SourceMountsChanged -= OnSourceMountsChanged; + assets.edits.pipeline.SourceMountsChanged += OnSourceMountsChanged; } internal void Detach() { - assets.pipeline.Changed -= OnAssetsChanged; - assets.pipeline.SourceMountsChanged -= OnSourceMountsChanged; + assets.edits.pipeline.Changed -= OnAssetsChanged; + assets.edits.pipeline.SourceMountsChanged -= OnSourceMountsChanged; m_context = null; } @@ -35,12 +33,10 @@ private void OnSourceMountsChanged() if (context is null) return; string currentDirectory = FileBrowserUtility.NormalizePath(assets.browser.currentDirectory); - if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(currentDirectory), out AssetFileEntry current) - || !current.isDirectory) + if (!assets.edits.pipeline.TryGetFileSystemEntry(AssetPath.Parse(currentDirectory), out AssetFileEntry current) || !current.isDirectory) assets.browser.SetCurrentDirectory(string.Empty); string selectedPath = FileBrowserUtility.NormalizePath(assets.browser.GetSelectedPath(context)); - if (!string.IsNullOrEmpty(selectedPath) - && !assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(selectedPath), out _)) + if (!string.IsNullOrEmpty(selectedPath) && !assets.edits.pipeline.TryGetFileSystemEntry(AssetPath.Parse(selectedPath), out _)) assets.browser.Select(context, null); } @@ -49,15 +45,11 @@ private void OnAssetsChanged(AssetChangeSet changes) EditorContext? context = m_context; if (context is null) return; - for (int i = 0; i < changes.changes.Count; i++) { AssetChange change = changes.changes[i]; if (change.kind == AssetChangeKind.Moved) - ApplyMove( - context, - change.previousAssetPath?.ToString() ?? string.Empty, - change.assetPath.ToString()); + ApplyMove(context, change.previousAssetPath?.ToString() ?? string.Empty, change.assetPath.ToString()); else if (change.kind is AssetChangeKind.Missing or AssetChangeKind.Removed) ApplyRemoval(context, change.assetPath.ToString()); } @@ -72,22 +64,17 @@ string newPath newPath = Normalize(newPath); if (string.IsNullOrEmpty(oldPath) || string.IsNullOrEmpty(newPath)) return; - string currentDirectory = Normalize(assets.browser.currentDirectory); if (IsSameOrDescendant(currentDirectory, oldPath)) { - string suffix = currentDirectory.Length == oldPath.Length - ? string.Empty - : currentDirectory[oldPath.Length..].TrimStart('/'); + string suffix = currentDirectory.Length == oldPath.Length ? string.Empty : currentDirectory[oldPath.Length..].TrimStart('/'); assets.browser.SetCurrentDirectory(Combine(newPath, suffix)); } string selectedPath = Normalize(assets.browser.GetSelectedPath(context)); if (!IsSameOrDescendant(selectedPath, oldPath)) return; - string selectedSuffix = selectedPath.Length == oldPath.Length - ? string.Empty - : selectedPath[oldPath.Length..].TrimStart('/'); + string selectedSuffix = selectedPath.Length == oldPath.Length ? string.Empty : selectedPath[oldPath.Length..].TrimStart('/'); assets.browser.Select(context, Combine(newPath, selectedSuffix)); } @@ -98,26 +85,28 @@ string removedPath removedPath = Normalize(removedPath); if (string.IsNullOrEmpty(removedPath)) return; - string selectedPath = Normalize(assets.browser.GetSelectedPath(context)); if (IsSameOrDescendant(selectedPath, removedPath)) assets.browser.Select(context, null); - string currentDirectory = Normalize(assets.browser.currentDirectory); if (!IsSameOrDescendant(currentDirectory, removedPath)) return; - string fallback = FileBrowserUtility.GetParentDirectory(removedPath); while (!string.Equals( - fallback, - FileBrowserUtility.GetParentDirectory(fallback), - StringComparison.Ordinal) && - (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(fallback), out var entry) || !entry.isDirectory)) + fallback, + FileBrowserUtility.GetParentDirectory(fallback), + StringComparison.Ordinal + ) + && (!assets.edits.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(fallback), + out var entry + ) + || !entry.isDirectory)) { fallback = FileBrowserUtility.GetParentDirectory(fallback); } - if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(fallback), out AssetFileEntry available) - || !available.isDirectory) + + if (!assets.edits.pipeline.TryGetFileSystemEntry(AssetPath.Parse(fallback), out AssetFileEntry available) || !available.isDirectory) fallback = string.Empty; assets.browser.SetCurrentDirectory(fallback); } @@ -125,17 +114,10 @@ string removedPath private static bool IsSameOrDescendant( string path, string ancestor - ) - => string.Equals(path, ancestor, StringComparison.OrdinalIgnoreCase) || - path.StartsWith(ancestor + "/", StringComparison.OrdinalIgnoreCase); - + ) => string.Equals(path, ancestor, StringComparison.OrdinalIgnoreCase) || path.StartsWith(ancestor + "/", StringComparison.OrdinalIgnoreCase); private static string Combine( string left, string right ) => string.IsNullOrEmpty(right) ? left : $"{left}/{right}"; - - private static string Normalize(string? path) - => string.IsNullOrWhiteSpace(path) - ? string.Empty - : path.Replace('\\', '/').Trim('/'); + private static string Normalize(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/').Trim('/'); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserContextMenu.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserContextMenu.cs index 866a176dd..05b93ec91 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserContextMenu.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserContextMenu.cs @@ -1,34 +1,31 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; -using Inno.Native.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - internal sealed class FileBrowserContextMenu( - AssetEditorModule assets, + AssetBrowserModule assets, FileBrowserRename rename -) { +) +{ internal void DrawEntry( EditorContext context, string id, string relativePath, FileBrowserPresentation presentation ) { - if (NativeImGui.IsItemClicked((int)(ImGuiMouseButton.Right))) - assets.browser.Select(context, relativePath); - if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry)) + if (!assets.edits.pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry)) return; - string creationDirectory = entry.isDirectory - ? entry.assetPath.ToString() - : GetParentDirectory(entry.assetPath.ToString()); - bool isOpen = EditorMenuRenderer.ContextMenu( - id, - CreateDirectoryInteraction(creationDirectory), - assets.interactions.For(FileBrowserInteractionIds.C_AREA, entry)); + if (Ui.IsItemClicked(ImGuiMouseButton.Right) && !assets.edits.interactions.selection.Contains(entry)) + assets.edits.interactions.SetSelection(entry); + string creationDirectory = entry.isDirectory ? entry.assetPath.ToString() : GetParentDirectory(entry.assetPath.ToString()); + bool isOpen = EditorUI.current.menus.ContextMenu(id, CreateDirectoryInteraction(creationDirectory), assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, entry)); if (isOpen) rename.MarkInteraction(presentation); } @@ -39,11 +36,9 @@ internal void DrawDirectory( string relativePath, FileBrowserPresentation presentation ) { - if (IsReadOnlySource(assets.pipeline, relativePath)) + if (IsReadOnlySource(assets.edits.pipeline, relativePath)) return; - bool isOpen = EditorMenuRenderer.ContextMenu( - id, - CreateDirectoryInteraction(relativePath)); + bool isOpen = EditorUI.current.menus.ContextMenu(id, CreateDirectoryInteraction(relativePath)); if (isOpen) rename.MarkInteraction(presentation); } @@ -53,17 +48,15 @@ internal void DrawBackground( string id, FileBrowserPresentation presentation ) { - if (IsReadOnlyLocation(assets.pipeline, assets.browser)) + if (IsReadOnlyLocation(assets.edits.pipeline, assets.browser)) return; - bool isOpen = EditorMenuRenderer.WindowContextMenu( - id, - CreateDirectoryInteraction(assets.browser.currentDirectory)); + bool isOpen = EditorUI.current.menus.WindowContextMenu(id, CreateDirectoryInteraction(assets.browser.currentDirectory)); if (isOpen) rename.MarkInteraction(presentation); } - private Inno.Editor.Interactions.EditorInteraction CreateDirectoryInteraction(string relativePath) - => assets.interactions.For( - FileBrowserInteractionIds.C_AREA, - NormalizePath(relativePath)); + private Inno.Editor.Interactions.EditorInteraction CreateDirectoryInteraction(string relativePath) => assets.edits.interactions.For( + FileBrowserInteractionIds.C_AREA, + NormalizePath(relativePath) + ); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserData.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserData.cs index 1519d6792..af616c7e4 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserData.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserData.cs @@ -1,13 +1,14 @@ - +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.Linq; +using System.Diagnostics.CodeAnalysis; +using Inno.Core.Identity; using Inno.Assets; using Inno.Assets.Pipeline; -using Inno.Editor.Core; namespace Inno.Editor.Panel.FileBrowser; - internal enum FileBrowserEntryTypeFilter { All, @@ -22,22 +23,29 @@ internal enum FileBrowserEntryScopeFilter } internal readonly record struct FileBrowserDisplayEntry( - AssetFileEntry entry, + RuntimeIdentity identity, + AssetPath path, string displayName, + string extension, + bool isDirectory, bool isPluginRoot = false ); - -internal sealed class FileBrowserData(AssetEditorModule assets) +internal sealed class FileBrowserData(AssetBrowserModule assets) { + private ViewInput? m_input; + private IReadOnlyList m_entries = Array.Empty(); + internal IReadOnlyList visibleSelection { get; private set; } = Array.Empty(); + internal IReadOnlyList CollectVisibleEntries( - EditorContext context, FileBrowserEntryTypeFilter typeFilter, FileBrowserEntryScopeFilter scopeFilter, string searchFilter ) { + var input = new ViewInput(assets.edits.pipeline.revision, assets.browser.root, assets.browser.currentDirectory, typeFilter, scopeFilter, searchFilter); + if (m_input == input) + return m_entries; List entries = []; - if (assets.browser.root == AssetBrowserRoot.Plugins && - string.IsNullOrEmpty(assets.browser.currentDirectory)) + if (assets.browser.root == AssetBrowserRoot.Plugins && string.IsNullOrEmpty(assets.browser.currentDirectory)) { CollectPluginRoots(entries, scopeFilter); } @@ -45,11 +53,10 @@ string searchFilter { IReadOnlyList children = GetVisibleChildren(assets.browser.currentDirectory); for (int i = 0; i < children.Count; i++) - entries.Add(new FileBrowserDisplayEntry(children[i], children[i].name)); + entries.Add(Describe(children[i], children[i].name)); } else CollectEntriesRecursive(assets.browser.currentDirectory, entries); - ApplyTypeFilter(entries, typeFilter); ApplySearchFilter(entries, searchFilter); entries.Sort(static ( @@ -57,25 +64,39 @@ string searchFilter right ) => { - int byName = string.Compare( - left.displayName, - right.displayName, - StringComparison.OrdinalIgnoreCase); + int byName = string.Compare(left.displayName, right.displayName, StringComparison.OrdinalIgnoreCase); if (byName != 0) return byName; - int byExtension = string.Compare( - left.entry.extension, - right.entry.extension, - StringComparison.OrdinalIgnoreCase); - return byExtension != 0 - ? byExtension - : string.CompareOrdinal( - left.entry.assetPath.ToString(), - right.entry.assetPath.ToString()); + int byExtension = string.Compare(left.extension, right.extension, StringComparison.OrdinalIgnoreCase); + return byExtension != 0 ? byExtension : string.CompareOrdinal(left.path.ToString(), right.path.ToString()); }); - return entries; + visibleSelection = Array.AsReadOnly(entries.Select(static entry => entry.identity).ToArray()); + m_entries = entries.AsReadOnly(); + m_input = input; + return m_entries; } + internal bool TryResolve( + FileBrowserDisplayEntry item, + [NotNullWhen(true)] out AssetFileEntry? entry + ) { + if (assets.edits.interactions.TryResolveIdentity(item.identity, out IdentityObject? target) + && target is AssetFileEntry resolved && resolved.assetPath == item.path) + { + entry = resolved; + return true; + } + entry = null; + return false; + } + + private static FileBrowserDisplayEntry Describe( + AssetFileEntry entry, + string label, + bool pluginRoot = false + ) => new(entry.identity.runtimeIdentity ?? throw new InvalidOperationException("A visible asset requires a registered identity."), + entry.assetPath, label, entry.extension, entry.isDirectory, pluginRoot); + private static void ApplyTypeFilter( List entries, FileBrowserEntryTypeFilter typeFilter @@ -83,10 +104,10 @@ FileBrowserEntryTypeFilter typeFilter switch (typeFilter) { case FileBrowserEntryTypeFilter.FoldersOnly: - entries.RemoveAll(static item => !item.entry.isDirectory); + entries.RemoveAll(static item => !item.isDirectory); break; case FileBrowserEntryTypeFilter.FilesOnly: - entries.RemoveAll(static item => item.entry.isDirectory); + entries.RemoveAll(static item => item.isDirectory); break; } } @@ -98,8 +119,7 @@ string searchFilter string filter = searchFilter.Trim(); if (!string.IsNullOrEmpty(filter)) { - entries.RemoveAll(item => - !item.displayName.Contains(filter, StringComparison.OrdinalIgnoreCase)); + entries.RemoveAll(item => !item.displayName.Contains(filter, StringComparison.OrdinalIgnoreCase)); } } @@ -107,17 +127,17 @@ private void CollectPluginRoots( List entries, FileBrowserEntryScopeFilter scopeFilter ) { - AssetSourceMount[] mounts = assets.pipeline.sourceMounts - .Where(mount => assets.IsPluginSource(mount.id)) - .OrderBy(static mount => mount.id.value, StringComparer.OrdinalIgnoreCase) - .ToArray(); + AssetSourceMount[] mounts = assets.edits.pipeline.sourceMounts.Where(mount => assets.edits.IsPluginSource(mount.id)).OrderBy( + static mount => mount.id.value, + StringComparer.OrdinalIgnoreCase + ).ToArray(); for (int i = 0; i < mounts.Length; i++) { AssetSourceMount mount = mounts[i]; var rootPath = new AssetPath(mount.id, string.Empty); - if (!assets.pipeline.TryGetFileSystemEntry(rootPath, out AssetFileEntry root)) + if (!assets.edits.pipeline.TryGetFileSystemEntry(rootPath, out AssetFileEntry root)) continue; - entries.Add(new FileBrowserDisplayEntry(root, mount.id.value, isPluginRoot: true)); + entries.Add(Describe(root, mount.id.value, pluginRoot: true)); if (scopeFilter == FileBrowserEntryScopeFilter.Recursive) CollectEntriesRecursive(root.assetPath.ToString(), entries); } @@ -127,13 +147,16 @@ private void CollectEntriesRecursive( string directory, List entries ) { - IReadOnlyList children = GetVisibleChildren(directory); - for (int i = 0; i < children.Count; i++) + var pending = new Stack(); + pending.Push(directory); + while (pending.TryPop(out string? current)) { - AssetFileEntry child = children[i]; - entries.Add(new FileBrowserDisplayEntry(child, child.name)); - if (child.isDirectory) - CollectEntriesRecursive(child.assetPath.ToString(), entries); + foreach (AssetFileEntry child in GetVisibleChildren(current)) + { + entries.Add(Describe(child, child.name)); + if (child.isDirectory) + pending.Push(child.assetPath.ToString()); + } } } @@ -147,10 +170,7 @@ internal List SortTreeEntries(IReadOnlyList entr { if (left.isDirectory != right.isDirectory) return left.isDirectory ? -1 : 1; - return string.Compare( - left.name, - right.name, - StringComparison.OrdinalIgnoreCase); + return string.Compare(left.name, right.name, StringComparison.OrdinalIgnoreCase); }); return sorted; } @@ -158,7 +178,7 @@ internal List SortTreeEntries(IReadOnlyList entr internal IReadOnlyList GetVisibleChildren(string relativePath) { AssetSourceId source = AssetPath.Parse(relativePath).source; - IReadOnlyList children = assets.pipeline.GetFileSystemChildren(AssetPath.Parse(relativePath)); + IReadOnlyList children = assets.edits.pipeline.GetFileSystemChildren(AssetPath.Parse(relativePath)); if (children.Count == 0) return children; List isolated = []; @@ -167,6 +187,16 @@ internal IReadOnlyList GetVisibleChildren(string relativePath) if (children[i].source == source) isolated.Add(children[i]); } + return isolated; } + + private readonly record struct ViewInput( + long revision, + AssetBrowserRoot root, + string directory, + FileBrowserEntryTypeFilter type, + FileBrowserEntryScopeFilter scope, + string search + ); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserDragDrop.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserDragDrop.cs index be502428c..f1236289b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserDragDrop.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserDragDrop.cs @@ -1,21 +1,21 @@ - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - /// /// Binds managed File Browser drag sessions to native entry and directory items. /// /// /// The assets used to initialize this instance. /// -internal sealed class FileBrowserDragDrop(AssetEditorModule assets) +internal sealed class FileBrowserDragDrop(AssetBrowserModule assets) { /// /// Publishes the supplied source entry as a managed File Browser drag payload. @@ -30,18 +30,19 @@ internal void DrawAssetSource( EditorContext context, AssetFileEntry entry ) { - if (!assets.TryCreateContext( - context, - entry.assetPath.ToString(), - out AssetEditorContext? assetContext) || - assetContext is null || - !assets.TryCreateDragData(assetContext, out EditorDragData? data) || - data is null) + if (!assets.edits.TryCreateContext( + context, + entry.assetPath.ToString(), + out AssetEditorContext? assetContext + ) + || assetContext is null + || !assets.edits.TryCreateDragData( + assetContext, + out EditorDragData? data + ) + || data is null) return; - _ = EditorDragDropRenderer.Source( - assets.interactions.For(FileBrowserInteractionIds.C_AREA, entry), - data, - () => NativeImGui.TextUnformatted(data.label)); + _ = EditorDragDropRenderer.Source(assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, entry), data, () => Ui.Text(data.label)); } /// @@ -57,17 +58,13 @@ internal void DrawDirectoryTarget( EditorContext context, string relativePath ) { - if (FileBrowserUtility.IsReadOnlySource(assets.pipeline, relativePath)) + if (FileBrowserUtility.IsReadOnlySource(assets.edits.pipeline, relativePath)) return; - System.Numerics.Vector2 minimum = NativeImGui.GetItemRectMin(); - System.Numerics.Vector2 maximum = NativeImGui.GetItemRectMax(); - EditorDropWidgetResult result = EditorDragDropRenderer.Target( - assets.interactions.For( - FileBrowserInteractionIds.C_AREA, - relativePath)); + System.Numerics.Vector2 minimum = Ui.GetItemMinimum(); + System.Numerics.Vector2 maximum = Ui.GetItemMaximum(); + EditorDropWidgetResult result = EditorDragDropRenderer.Target(assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, relativePath)); if (!result.isPreviewing || !result.status.canDrop) return; - - EditorWidget.DropTargetHighlight(minimum, maximum); + EditorUI.current.widgets.DropTargetHighlight(minimum, maximum); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserNavigation.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserNavigation.cs index c345762b6..fb76db4da 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserNavigation.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserNavigation.cs @@ -1,8 +1,7 @@ - - +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; @@ -10,16 +9,12 @@ using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed class FileBrowserNavigation(AssetEditorModule assets) +internal sealed class FileBrowserNavigation(AssetBrowserModule assets) { private readonly Stack m_backHistory = []; private readonly Stack m_forwardHistory = []; - private BrowserLocation m_historyCurrent = BrowserLocation.AssetsRoot; - internal bool canGoBack => m_backHistory.Count > 0; - internal bool canGoForward => m_forwardHistory.Count > 0; internal void NavigateTo( @@ -28,15 +23,10 @@ internal void NavigateTo( string? selectedPathAfterNavigation = null ) { directory = NormalizePath(directory); - AssetBrowserRoot root = string.IsNullOrEmpty(directory) - ? assets.browser.root - : AssetPath.Parse(directory).source == AssetSourceId.project - ? AssetBrowserRoot.Assets - : AssetBrowserRoot.Plugins; + AssetBrowserRoot root = string.IsNullOrEmpty(directory) ? assets.browser.root : AssetPath.Parse(directory).source == AssetSourceId.project ? AssetBrowserRoot.Assets : AssetBrowserRoot.Plugins; var target = new BrowserLocation(root, directory); if (CurrentLocation == target) return; - m_backHistory.Push(CurrentLocation); m_forwardHistory.Clear(); ApplyLocation(context, target); @@ -103,9 +93,8 @@ FileBrowserTree tree NavigateTo(context, entry.assetPath.ToString(), entry.assetPath.ToString()); return; } - _ = assets.interactions - .For(FileBrowserInteractionIds.C_AREA, entry) - .Execute(FileBrowserInteractionIds.C_OPEN); + + _ = assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, entry).Execute(FileBrowserInteractionIds.C_OPEN); } internal void SyncExternalDirectoryChange( @@ -125,9 +114,8 @@ string directory private readonly record struct BrowserLocation( AssetBrowserRoot root, string directory - ) { - internal static BrowserLocation AssetsRoot { get; } = new( - AssetBrowserRoot.Assets, - string.Empty); + ) + { + internal static BrowserLocation AssetsRoot { get; } = new(AssetBrowserRoot.Assets, string.Empty); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Filters.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Filters.cs index 6afa59665..d766dd373 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Filters.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Filters.cs @@ -1,42 +1,33 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - internal sealed partial class FileBrowserPanel - { - private void DrawEntryFilterCombo() { - if (!EditorWidget.BeginBoundedCombo( - "##AssetEntryFilter", - "Filter", - ImGuiComboFlags.WidthFitPreview)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##AssetEntryFilter", "Filter", ImGuiComboFlags.WidthFitPreview)) return; - DrawEntryTypeFilterOption("All", FileBrowserEntryTypeFilter.All); DrawEntryTypeFilterOption("Folders", FileBrowserEntryTypeFilter.FoldersOnly); DrawEntryTypeFilterOption("Files", FileBrowserEntryTypeFilter.FilesOnly); - NativeImGui.Separator(); + Ui.Separator(); DrawEntryScopeFilterOption("Current", FileBrowserEntryScopeFilter.CurrentOnly); DrawEntryScopeFilterOption("Recursive", FileBrowserEntryScopeFilter.Recursive); - - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } private void DrawEntryTypeFilterOption( @@ -44,7 +35,7 @@ private void DrawEntryTypeFilterOption( FileBrowserEntryTypeFilter filter ) { bool selected = m_entryTypeFilter == filter; - if (NativeImGui.Checkbox(label, ref selected)) + if (Ui.Checkbox(label, ref selected)) { if (selected) m_entryTypeFilter = filter; @@ -58,7 +49,7 @@ private void DrawEntryScopeFilterOption( FileBrowserEntryScopeFilter filter ) { bool selected = m_entryScopeFilter == filter; - if (NativeImGui.Checkbox(label, ref selected)) + if (Ui.Checkbox(label, ref selected)) { if (selected) m_entryScopeFilter = filter; @@ -69,11 +60,6 @@ FileBrowserEntryScopeFilter filter private void DrawSearchInput() { - _ = EditorWidget.SearchInput( - "AssetSearch", - ImGuiIcon.MagnifyingGlass, - ref m_filter, - (nuint)C_SEARCH_BUFFER_SIZE); + _ = EditorUI.current.widgets.SearchInput("AssetSearch", ImGuiIcon.MagnifyingGlass, ref m_filter, (nuint)C_SEARCH_BUFFER_SIZE); } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Grid.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Grid.cs index 1dfc5ddf1..a83e65438 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Grid.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Grid.cs @@ -1,61 +1,55 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed unsafe partial class FileBrowserPanel - +internal sealed partial class FileBrowserPanel { - private void DrawGridRegion( EditorContext context, IReadOnlyList entries ) { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float sliderHeight = NativeImGui.GetFrameHeight() + style->WindowPadding.Y * 2f + style->ItemSpacing.Y; - bool entriesVisible = NativeImGui.BeginChild( - "##EntriesScroll", - new Vector2(0f, -sliderHeight), - (int)ImGuiChildFlags.None); + EditorUiLayout style = Ui.GetLayout(); + float sliderHeight = Ui.GetFrameHeight() + style.windowPadding.Y * 2f + style.itemSpacing.Y; + bool entriesVisible = Ui.BeginChild("##EntriesScroll", new Vector2(0f, -sliderHeight), ImGuiChildFlags.None); try { if (entriesVisible) { + if (Ui.IsWindowFocused()) + m_rename.MarkInteraction(FileBrowserPresentation.Grid); + float cellSize = GetGridCellSize(); + int columns = Math.Max(1, (int)(Ui.GetContentRegionAvailable().X / cellSize)); + int rows = Math.Max(1, (int)(Ui.GetContentRegionAvailable().Y / cellSize)); + HandleEntryKeyboardNavigation(columns * rows); DrawGrid(context, entries); HandleBackgroundSelection(context); - m_contextMenu.DrawBackground( - context, - "##asset_grid_background_context", - FileBrowserPresentation.Grid); + m_contextMenu.DrawBackground(context, "##asset_grid_background_context", FileBrowserPresentation.Grid); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } + DrawGridScaleSlider(); } private void HandleBackgroundSelection(EditorContext context) { - if (!NativeImGui.IsWindowHovered() || - NativeImGui.IsAnyItemHovered() || - !NativeImGui.IsMouseReleased((int)ImGuiMouseButton.Left)) + if (!Ui.IsWindowHovered() || Ui.IsAnyItemHovered() || !Ui.IsMouseReleased(ImGuiMouseButton.Left)) { return; } @@ -65,17 +59,17 @@ private void HandleBackgroundSelection(EditorContext context) private void DrawGridScaleSlider() { - m_gridScale = Math.Clamp(m_gridScale, EditorWidget.style.assetGridMinimumScale, EditorWidget.style.assetGridMaximumScale); + m_gridScale = Math.Clamp(m_gridScale, CurrentBrowserStyle.gridMinimumScale, CurrentBrowserStyle.gridMaximumScale); DrawFooterTopSplitter(); - Vector2 cursor = NativeImGui.GetCursorPos(); - float labelOffsetY = MathF.Max(0f, (NativeImGui.GetFrameHeight() - NativeImGui.GetTextLineHeight()) * 0.5f); - NativeImGui.SetCursorPosY(cursor.Y + labelOffsetY); - NativeImGui.TextUnformatted("Scale"); - NativeImGui.SameLine(0f, -1f); - NativeImGui.SetCursorPosY(cursor.Y); - NativeImGui.SetNextItemWidth(-1f); - _ = NativeImGui.SliderFloat("##GridScale", ref m_gridScale, EditorWidget.style.assetGridMinimumScale, EditorWidget.style.assetGridMaximumScale, "%.1f"); - m_gridScale = Math.Clamp(m_gridScale, EditorWidget.style.assetGridMinimumScale, EditorWidget.style.assetGridMaximumScale); + Vector2 cursor = Ui.GetCursorPosition(); + float labelOffsetY = MathF.Max(0f, (Ui.GetFrameHeight() - Ui.GetTextLineHeight()) * 0.5f); + Ui.SetCursorPosY(cursor.Y + labelOffsetY); + Ui.Text("Scale"); + Ui.SameLine(0f, -1f); + Ui.SetCursorPosY(cursor.Y); + Ui.SetNextItemWidth(-1f); + _ = Ui.SliderFloat("##GridScale", ref m_gridScale, CurrentBrowserStyle.gridMinimumScale, CurrentBrowserStyle.gridMaximumScale); + m_gridScale = Math.Clamp(m_gridScale, CurrentBrowserStyle.gridMinimumScale, CurrentBrowserStyle.gridMaximumScale); } private void DrawGrid( @@ -83,25 +77,45 @@ private void DrawGrid( IReadOnlyList entries ) { float cellSize = GetGridCellSize(); - float available = MathF.Max(cellSize, NativeImGui.GetContentRegionAvail().X); + float available = MathF.Max(cellSize, Ui.GetContentRegionAvailable().X); int columns = Math.Max(1, (int)(available / cellSize)); - - bool tableStarted = NativeImGui.BeginTable("##FileBrowserGrid", columns, (int)(ImGuiTableFlags.SizingFixedFit | - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoSavedSettings), default(System.Numerics.Vector2), 0f); + bool tableStarted = Ui.BeginTable( + "##FileBrowserGrid", + columns, + (ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoSavedSettings), + default(System.Numerics.Vector2), + 0f + ); if (!tableStarted) return; try { - for (int i = 0; i < entries.Count; i++) + int rowCount = (entries.Count + columns - 1) / columns; + Span preserved = stackalloc int[3]; + int preservedCount = CollectPreservedEntryRows(entries, preserved, columns); + m_ui.widgets.ClippedRows(rowCount, -1f, (panel: this, context, entries, columns), static ( + state, + start, + end + ) => { - NativeImGui.TableNextColumn(); - DrawGridItem(context, entries[i]); - } + for (int row = start; row < end; row++) + { + Ui.TableNextRow(); + for (int column = 0; column < state.columns; column++) + { + int index = row * state.columns + column; + if (index >= state.entries.Count) + break; + _ = Ui.TableSetColumnIndex(column); + state.panel.DrawGridItem(state.context, state.entries[index]); + } + } + }, preserved[..preservedCount]); } finally { - NativeImGui.EndTable(); + Ui.EndTable(); } } @@ -109,99 +123,66 @@ private void DrawGridItem( EditorContext context, FileBrowserDisplayEntry item ) { - AssetFileEntry entry = item.entry; float cellSize = GetGridCellSize(); + if (!m_data.TryResolve(item, out AssetFileEntry? entry)) + { + Ui.Dummy(new(cellSize, cellSize)); + return; + } string icon = m_assets.GetIcon(entry); string name = item.isPluginRoot ? item.displayName : entry.name; bool selected = m_assets.browser.IsSelected(context, entry); bool editing = m_rename.IsEditing(context, entry.assetPath.ToString(), FileBrowserPresentation.Grid); - Vector2 itemSize = new(cellSize - EditorWidget.style.assetGridCellPadding, cellSize - EditorWidget.style.assetGridCellPadding); - - NativeImGui.PushID(entry.assetPath.ToString()); - NativeImGui.PushStyleColor((int)ImGuiCol.Header, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.HeaderHovered, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.HeaderActive, EditorPalette.transparent); + Vector2 itemSize = new(cellSize - CurrentBrowserStyle.gridCellPadding, cellSize - CurrentBrowserStyle.gridCellPadding); + Ui.PushId(entry.assetPath.ToString()); + Ui.PushStyleColor(ImGuiCol.Header, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.HeaderHovered, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.HeaderActive, EditorPalette.transparent); try { ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags.AllowDoubleClick; if (editing) selectableFlags |= ImGuiSelectableFlags.AllowOverlap; - bool activated = NativeImGui.Selectable( - "##GridItem", - selected, - (int)selectableFlags, - itemSize); - bool itemHovered = NativeImGui.IsItemHovered(); - bool itemActive = NativeImGui.IsItemActive(); - Vector2 itemMin = NativeImGui.GetItemRectMin(); - Vector2 itemMax = NativeImGui.GetItemRectMax(); - Vector2 layoutCursor = NativeImGui.GetCursorScreenPos(); - bool doubleClicked = itemHovered && - NativeImGui.IsMouseDoubleClicked((int)ImGuiMouseButton.Left); - if (activated) + RequestEntryKeyboardFocus(entry); + bool activated = Ui.Selectable("##GridItem", selected, selectableFlags, itemSize); + bool itemHovered = Ui.IsItemHovered(); + bool itemActive = Ui.IsItemActive(); + Vector2 itemMin = Ui.GetItemMinimum(); + Vector2 itemMax = Ui.GetItemMaximum(); + Vector2 layoutCursor = Ui.GetCursorScreenPosition(); + bool doubleClicked = itemHovered && Ui.IsMouseDoubleClicked(ImGuiMouseButton.Left); + if (activated && !m_keyboardHandled) { - HandleEntryActivation( - context, - entry, - FileBrowserPresentation.Grid, - doubleClicked); + HandleEntryActivation(context, entry, FileBrowserPresentation.Grid, doubleClicked); } if (!editing) { m_dragDrop.DrawAssetSource(context, entry); - m_contextMenu.DrawEntry( - context, - "##asset_grid_context", - entry.assetPath.ToString(), - FileBrowserPresentation.Grid); + m_contextMenu.DrawEntry(context, "##asset_grid_context", entry.assetPath.ToString(), FileBrowserPresentation.Grid); if (entry.isDirectory && !entry.isReadOnly) m_dragDrop.DrawDirectoryTarget(context, entry.assetPath.ToString()); } - DrawGridItemVisual( - icon, - name, - selected, - m_gridScale, - itemHovered, - itemActive, - itemMin, - itemMax, - drawName: !editing); + + DrawGridItemVisual(icon, name, selected, m_gridScale, itemHovered, itemActive, itemMin, itemMax, drawName: !editing); if (editing) { - float width = MathF.Max( - 1f, - itemMax.X - itemMin.X - EditorWidget.style.assetGridLabelHorizontalPadding); - float lineHeight = NativeImGui.GetTextLineHeight(); - float lineAdvance = MathF.Max( - 1f, - lineHeight + EditorWidget.style.assetGridLabelLineSpacing); - float labelAreaHeight = lineHeight + - lineAdvance * (C_GRID_LABEL_LINE_COUNT - 1); - float labelAreaTop = itemMax.Y - - EditorWidget.style.assetGridLabelBottomPadding - - labelAreaHeight; - float y = labelAreaTop + - (labelAreaHeight - lineHeight) * 0.5f; - NativeImGui.SetCursorScreenPos(new Vector2( - itemMin.X + EditorWidget.style.assetGridLabelHorizontalPadding * 0.5f, - y)); - m_rename.Draw( - context, - $"grid_{entry.assetPath.ToString()}", - entry.assetPath.ToString(), - FileBrowserPresentation.Grid, - width, - lineHeight); - NativeImGui.SetCursorScreenPos(layoutCursor); - NativeImGui.Dummy(Vector2.Zero); + float width = MathF.Max(1f, itemMax.X - itemMin.X - CurrentBrowserStyle.gridLabelHorizontalPadding); + float lineHeight = Ui.GetTextLineHeight(); + float lineAdvance = MathF.Max(1f, lineHeight + CurrentBrowserStyle.gridLabelLineSpacing); + float labelAreaHeight = lineHeight + lineAdvance * (C_GRID_LABEL_LINE_COUNT - 1); + float labelAreaTop = itemMax.Y - CurrentBrowserStyle.gridLabelBottomPadding - labelAreaHeight; + float y = labelAreaTop + (labelAreaHeight - lineHeight) * 0.5f; + Ui.SetCursorScreenPosition(new Vector2(itemMin.X + CurrentBrowserStyle.gridLabelHorizontalPadding * 0.5f, y)); + m_rename.Draw(context, $"grid_{entry.assetPath.ToString()}", entry.assetPath.ToString(), FileBrowserPresentation.Grid, width, lineHeight); + Ui.SetCursorScreenPosition(layoutCursor); + Ui.Dummy(Vector2.Zero); } } finally { - NativeImGui.PopStyleColor(3); - NativeImGui.PopID(); + Ui.PopStyleColor(3); + Ui.PopId(); } } @@ -217,100 +198,57 @@ private static void DrawGridItemVisual( bool drawName ) { Vector2 size = max - min; - - Vector4 bg = selected ? EditorPalette.assetAccent : EditorPalette.collectionHeader; + Vector4 bg = selected ? EditorPalette.contentAccent : EditorPalette.collectionHeader; if (active) bg = EditorPalette.GetActive(bg); else if (hovered) bg = EditorPalette.GetHovered(bg); - - uint bgColor = NativeImGui.ColorConvertFloat4ToU32(bg); - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - EditorImGui.DrawFilledRectangle( - min, - max, - bgColor, - EditorWidget.style.assetFrameRounding - ); - - ImFont* font = NativeImGui.Raw.GetFont(); - float fontSize = NativeImGui.GetFontSize(); + uint bgColor = Ui.ToPackedColor(bg); + Ui.DrawFilledRectangle(min, max, bgColor, CurrentBrowserStyle.frameRounding); + float fontSize = Ui.GetFontSize(); float iconFontSize = fontSize * scale; - Vector4 iconBounds = EditorWidget.GetGlyphVisualBounds(font, iconFontSize, icon); - Vector2 iconSize = new( - iconBounds.Z - iconBounds.X, - iconBounds.W - iconBounds.Y); - string[] nameLines = FitTextToLines( - name, - MathF.Max(1f, size.X - EditorWidget.style.assetGridLabelHorizontalPadding), - C_GRID_LABEL_LINE_COUNT); - float lineHeight = NativeImGui.GetTextLineHeight(); - float lineAdvance = MathF.Max( - 1f, - lineHeight + EditorWidget.style.assetGridLabelLineSpacing); - float labelAreaHeight = lineHeight + - lineAdvance * (C_GRID_LABEL_LINE_COUNT - 1); - float labelAreaTop = max.Y - - EditorWidget.style.assetGridLabelBottomPadding - - labelAreaHeight; - float labelHeight = lineHeight + - lineAdvance * Math.Max(0, nameLines.Length - 1); + Vector4 iconBounds = EditorUI.current.widgets.GetGlyphVisualBounds(iconFontSize, icon); + Vector2 iconSize = new(iconBounds.Z - iconBounds.X, iconBounds.W - iconBounds.Y); + string[] nameLines = FitTextToLines(name, MathF.Max(1f, size.X - CurrentBrowserStyle.gridLabelHorizontalPadding), C_GRID_LABEL_LINE_COUNT); + float lineHeight = Ui.GetTextLineHeight(); + float lineAdvance = MathF.Max(1f, lineHeight + CurrentBrowserStyle.gridLabelLineSpacing); + float labelAreaHeight = lineHeight + lineAdvance * (C_GRID_LABEL_LINE_COUNT - 1); + float labelAreaTop = max.Y - CurrentBrowserStyle.gridLabelBottomPadding - labelAreaHeight; + float labelHeight = lineHeight + lineAdvance * Math.Max(0, nameLines.Length - 1); float labelY = labelAreaTop + (labelAreaHeight - labelHeight) * 0.5f; - float iconAreaTop = min.Y + EditorWidget.style.assetGridIconTopPadding; - float iconAreaBottom = MathF.Max( - iconAreaTop + 1f, - labelAreaTop - EditorWidget.style.assetGridIconLabelSpacing); - float maximumIconWidth = MathF.Max( - 1f, - size.X - EditorWidget.style.assetGridIconHorizontalPadding * 2f); + float iconAreaTop = min.Y + CurrentBrowserStyle.gridIconTopPadding; + float iconAreaBottom = MathF.Max(iconAreaTop + 1f, labelAreaTop - CurrentBrowserStyle.gridIconLabelSpacing); + float maximumIconWidth = MathF.Max(1f, size.X - CurrentBrowserStyle.gridIconHorizontalPadding * 2f); float maximumIconHeight = MathF.Max(1f, iconAreaBottom - iconAreaTop); - float fit = MathF.Min( - 1f, - MathF.Min( - maximumIconWidth / MathF.Max(1f, iconSize.X), - maximumIconHeight / MathF.Max(1f, iconSize.Y))); + float fit = MathF.Min(1f, MathF.Min(maximumIconWidth / MathF.Max(1f, iconSize.X), maximumIconHeight / MathF.Max(1f, iconSize.Y))); if (fit < 1f) { iconFontSize *= fit; - iconBounds = EditorWidget.GetGlyphVisualBounds(font, iconFontSize, icon); - iconSize = new( - iconBounds.Z - iconBounds.X, - iconBounds.W - iconBounds.Y); + iconBounds = EditorUI.current.widgets.GetGlyphVisualBounds(iconFontSize, icon); + iconSize = new(iconBounds.Z - iconBounds.X, iconBounds.W - iconBounds.Y); } float horizontalCenter = min.X + size.X * 0.5f; - Vector2 iconAreaCenter = new( - horizontalCenter + - C_GRID_ICON_HORIZONTAL_OPTICAL_OFFSET * EditorWidget.style.zoom, - iconAreaTop + maximumIconHeight * 0.5f); - - uint textColor = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.assetText); - NativeImGui.PushClipRect(min, max, true); - EditorWidget.AddGlyphCentered( - drawList, - font, - iconFontSize, - icon, - iconAreaCenter, - textColor); + Vector2 iconAreaCenter = new(horizontalCenter + C_GRID_ICON_HORIZONTAL_OPTICAL_OFFSET * EditorUI.current.widgets.style.zoom, iconAreaTop + maximumIconHeight * 0.5f); + uint textColor = Ui.ToPackedColor(EditorPalette.contentText); + Ui.PushClipRect(min, max, true); + EditorUI.current.widgets.AddGlyphCentered(iconFontSize, icon, iconAreaCenter, textColor); for (int i = 0; drawName && i < nameLines.Length; i++) { - Vector2 lineSize = NativeImGui.CalcTextSize(nameLines[i]); - Vector2 linePosition = new( - horizontalCenter - lineSize.X * 0.5f, - labelY + lineAdvance * i); - EditorImGui.DrawText(linePosition, textColor, nameLines[i]); + Vector2 lineSize = Ui.MeasureText(nameLines[i]); + Vector2 linePosition = new(horizontalCenter - lineSize.X * 0.5f, labelY + lineAdvance * i); + Ui.DrawText(linePosition, textColor, nameLines[i]); } - NativeImGui.PopClipRect(); + + Ui.PopClipRect(); } private float GetGridCellSize() { - float fontSize = NativeImGui.GetFontSize(); + float fontSize = Ui.GetFontSize(); return MathF.Max( - EditorWidget.style.assetGridMinimumCellSize, - fontSize * (m_gridScale + EditorWidget.style.assetGridScaleBias) + - EditorWidget.style.assetGridFixedCellPadding); + CurrentBrowserStyle.gridMinimumCellSize, + fontSize * (m_gridScale + CurrentBrowserStyle.gridScaleBias) + CurrentBrowserStyle.gridFixedCellPadding + ); } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Layout.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Layout.cs index b06a49b38..56dc7d379 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Layout.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Layout.cs @@ -1,104 +1,81 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Core.Identity; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed unsafe partial class FileBrowserPanel - +internal sealed partial class FileBrowserPanel { - private void DrawBrowser(EditorContext context) { - m_navigation.SyncExternalDirectoryChange( - m_assets.browser.root, - m_assets.browser.currentDirectory); - - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float breadcrumbBarHeight = GetBreadcrumbBarHeight( - m_assets.browser.root, - m_assets.browser.currentDirectory); - Vector2 bodySize = new(0f, -(breadcrumbBarHeight + style->ItemSpacing.Y)); - bool mainVisible = NativeImGui.BeginChild( - "##FileBrowserMain", - bodySize, - (int)ImGuiChildFlags.None, - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); + m_navigation.SyncExternalDirectoryChange(m_assets.browser.root, m_assets.browser.currentDirectory); + EditorUiLayout style = Ui.GetLayout(); + float breadcrumbBarHeight = GetBreadcrumbBarHeight(m_assets.browser.root, m_assets.browser.currentDirectory); + Vector2 bodySize = new(0f, -(breadcrumbBarHeight + style.itemSpacing.Y)); + bool mainVisible = Ui.BeginChild("##FileBrowserMain", bodySize, ImGuiChildFlags.None, (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); try { if (mainVisible) { - ImGuiTableFlags splitFlags = - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoKeepColumnsVisible | - ImGuiTableFlags.SizingFixedFit | - ImGuiTableFlags.NoSavedSettings; - + ImGuiTableFlags splitFlags = ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoKeepColumnsVisible | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings; float splitterWidth = GetTreeSplitterWidth(style); - float availableWidth = MathF.Max(0f, NativeImGui.GetContentRegionAvail().X); - float treeWidth = ResolveTreeWidth( - m_treePaneRatio, - availableWidth, - splitterWidth); - NativeImGui.PushStyleVar((int)ImGuiStyleVar.CellPadding, Vector2.Zero); + float availableWidth = MathF.Max(0f, Ui.GetContentRegionAvailable().X); + float treeWidth = ResolveTreeWidth(m_treePaneRatio, availableWidth, splitterWidth); + Ui.PushStyleVar(ImGuiStyleVar.CellPadding, Vector2.Zero); try { - bool splitStarted = NativeImGui.BeginTable("##FileBrowserSplit", 3, (int)splitFlags, default(System.Numerics.Vector2), 0f); + bool splitStarted = Ui.BeginTable("##FileBrowserSplit", 3, splitFlags, default(System.Numerics.Vector2), 0f); try { if (splitStarted) { - NativeImGui.TableSetupColumn("##Tree", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), treeWidth); - NativeImGui.TableSetupColumn("##Splitter", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), splitterWidth); - NativeImGui.TableSetupColumn("##Content", (int)ImGuiTableColumnFlags.WidthStretch); - - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); + Ui.TableSetupColumn("##Tree", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), treeWidth); + Ui.TableSetupColumn("##Splitter", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), splitterWidth); + Ui.TableSetupColumn("##Content", ImGuiTableColumnFlags.WidthStretch); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); DrawTreePane(context); - - _ = NativeImGui.TableSetColumnIndex(1); + _ = Ui.TableSetColumnIndex(1); DrawTreeSplitter(splitterWidth, availableWidth, treeWidth); - - _ = NativeImGui.TableSetColumnIndex(2); + _ = Ui.TableSetColumnIndex(2); DrawContentPane(context); } } finally { if (splitStarted) - NativeImGui.EndTable(); + Ui.EndTable(); } } finally { - NativeImGui.PopStyleVar(); + Ui.PopStyleVar(); } } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } + DrawBreadcrumbBar(context, breadcrumbBarHeight); } - private static float GetTreeSplitterWidth(ImGuiStyle* style) + private static float GetTreeSplitterWidth(EditorUiLayout style) { - return MathF.Max(EditorWidget.style.assetSplitterMinimumWidth, style->DockingSeparatorSize); + return MathF.Max(EditorUI.current.widgets.style.splitterMinimumWidth, style.dockingSeparatorSize); } private void DrawTreeSplitter( @@ -106,40 +83,27 @@ private void DrawTreeSplitter( float availableWidth, float treeWidth ) { - Vector2 size = new(width, MathF.Max(1f, NativeImGui.GetContentRegionAvail().Y)); - _ = NativeImGui.InvisibleButton("##TreeSplitterGrip", size); - - bool hovered = NativeImGui.IsItemHovered(); - bool active = NativeImGui.IsItemActive(); + Vector2 size = new(width, MathF.Max(1f, Ui.GetContentRegionAvailable().Y)); + _ = Ui.InvisibleButton("##TreeSplitterGrip", size); + bool hovered = Ui.IsItemHovered(); + bool active = Ui.IsItemActive(); if (hovered || active) - NativeImGui.SetMouseCursor((int)ImGuiMouseCursor.ResizeEw); - + Ui.SetMouseCursor(ImGuiMouseCursor.ResizeEw); if (active) { - Vector2 delta = NativeImGui.GetMouseDragDelta((int)ImGuiMouseButton.Left); + Vector2 delta = Ui.GetMouseDragDelta(ImGuiMouseButton.Left); if (MathF.Abs(delta.X) > 0f) { - float requestedWidth = ClampTreeWidth( - treeWidth + delta.X, - availableWidth, - width); - m_treePaneRatio = CalculateTreePaneRatio( - requestedWidth, - availableWidth, - width); - NativeImGui.ResetMouseDragDelta((int)ImGuiMouseButton.Left); + float requestedWidth = ClampTreeWidth(treeWidth + delta.X, availableWidth, width); + m_treePaneRatio = CalculateTreePaneRatio(requestedWidth, availableWidth, width); + Ui.ResetMouseDragDelta(ImGuiMouseButton.Left); } } - Vector2 min = NativeImGui.GetItemRectMin(); - Vector2 max = NativeImGui.GetItemRectMax(); - Vector4 color = active ? EditorPalette.assetAccent : hovered ? EditorPalette.assetBorder : EditorPalette.assetBorderSoft; - EditorImGui.DrawFilledRectangle( - min, - max, - NativeImGui.ColorConvertFloat4ToU32(color), - 0f - ); + Vector2 min = Ui.GetItemMinimum(); + Vector2 max = Ui.GetItemMaximum(); + Vector4 color = active ? EditorPalette.contentAccent : hovered ? EditorPalette.contentBorder : EditorPalette.contentBorderSoft; + Ui.DrawFilledRectangle(min, max, Ui.ToPackedColor(color), 0f); } private static float ClampTreeWidth( @@ -148,9 +112,7 @@ private static float ClampTreeWidth( float splitterWidth ) { float combinedPaneWidth = MathF.Max(0f, availableWidth - splitterWidth); - float minimumPaneWidth = MathF.Min( - EditorWidget.style.assetPaneMinimumVisibleWidth, - combinedPaneWidth * 0.5f); + float minimumPaneWidth = MathF.Min(EditorUI.current.widgets.style.paneMinimumVisibleWidth, combinedPaneWidth * 0.5f); float maximumTreeWidth = MathF.Max(minimumPaneWidth, combinedPaneWidth - minimumPaneWidth); return Math.Clamp(requestedWidth, minimumPaneWidth, maximumTreeWidth); } @@ -161,10 +123,7 @@ private static float ResolveTreeWidth( float splitterWidth ) { float combinedPaneWidth = MathF.Max(0f, availableWidth - splitterWidth); - return ClampTreeWidth( - combinedPaneWidth * Math.Clamp(treePaneRatio, 0f, 1f), - availableWidth, - splitterWidth); + return ClampTreeWidth(combinedPaneWidth * Math.Clamp(treePaneRatio, 0f, 1f), availableWidth, splitterWidth); } private static float CalculateTreePaneRatio( @@ -173,35 +132,23 @@ private static float CalculateTreePaneRatio( float splitterWidth ) { float combinedPaneWidth = MathF.Max(0f, availableWidth - splitterWidth); - return combinedPaneWidth > float.Epsilon - ? Math.Clamp(treeWidth / combinedPaneWidth, 0f, 1f) - : 0.5f; + return combinedPaneWidth > float.Epsilon ? Math.Clamp(treeWidth / combinedPaneWidth, 0f, 1f) : 0.5f; } private void DrawTreePane(EditorContext context) { - NativeImGui.PushStyleColor((int)ImGuiCol.ChildBg, EditorPalette.collectionHeader); + Ui.PushStyleColor(ImGuiCol.ChildBg, EditorPalette.collectionHeader); try { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - Vector2 treePaneSize = new(-style->WindowPadding.X, 0f); - bool treeVisible = NativeImGui.BeginChild( - "##TreePane", - treePaneSize, - (int)ImGuiChildFlags.None, - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); + EditorUiLayout style = Ui.GetLayout(); + Vector2 treePaneSize = new(-style.windowPadding.X, 0f); + bool treeVisible = Ui.BeginChild("##TreePane", treePaneSize, ImGuiChildFlags.None, (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); try { if (treeVisible) { - float footerHeight = NativeImGui.GetFrameHeight() + - style->WindowPadding.Y * 2f + - style->ItemSpacing.Y; - bool treeScrollVisible = NativeImGui.BeginChild( - "##TreeScroll", - new Vector2(0f, -footerHeight), - (int)ImGuiChildFlags.None, - (int)ImGuiWindowFlags.HorizontalScrollbar); + float footerHeight = Ui.GetFrameHeight() + style.windowPadding.Y * 2f + style.itemSpacing.Y; + bool treeScrollVisible = Ui.BeginChild("##TreeScroll", new Vector2(0f, -footerHeight), ImGuiChildFlags.None, ImGuiWindowFlags.HorizontalScrollbar); try { if (treeScrollVisible) @@ -209,52 +156,48 @@ private void DrawTreePane(EditorContext context) m_tree.PrepareOpenRequests(); try { - if (m_assets.browser.root == AssetBrowserRoot.Assets) - m_tree.DrawEntry(context, string.Empty, "Assets", true); - else - m_tree.DrawPluginRoot(context, m_assets.pipeline.sourceMounts); + m_tree.Draw(context); + m_tree.CompleteNavigation(context); } finally { m_tree.ClearOpenRequests(); } + HandleBackgroundSelection(context); - m_contextMenu.DrawBackground( - context, - "##asset_tree_background_context", - FileBrowserPresentation.Tree); + m_contextMenu.DrawBackground(context, "##asset_tree_background_context", FileBrowserPresentation.Tree); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } + DrawRootSwitchFooter(context); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } - if (!IsReadOnlyLocation(m_assets.pipeline, m_assets.browser)) + + if (!IsReadOnlyLocation(m_assets.edits.pipeline, m_assets.browser)) m_dragDrop.DrawDirectoryTarget(context, m_assets.browser.currentDirectory); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } private void DrawRootSwitchFooter(EditorContext context) { - AssetBrowserRoot nextRoot = m_assets.browser.root == AssetBrowserRoot.Assets - ? AssetBrowserRoot.Plugins - : AssetBrowserRoot.Assets; + AssetBrowserRoot nextRoot = m_assets.browser.root == AssetBrowserRoot.Assets ? AssetBrowserRoot.Plugins : AssetBrowserRoot.Assets; DrawFooterTopSplitter(); - PushButtonColors(EditorPalette.assetAccent); + PushButtonColors(EditorPalette.contentAccent); try { - if (NativeImGui.Button($"Switch to {nextRoot}##FileBrowserRoot", new Vector2(-1f, 0f))) + if (Ui.Button($"Switch to {nextRoot}##FileBrowserRoot", new Vector2(-1f, 0f))) { m_tree.RequestOpenRoot(); m_navigation.SwitchRoot(context, nextRoot); @@ -262,40 +205,35 @@ private void DrawRootSwitchFooter(EditorContext context) } finally { - NativeImGui.PopStyleColor(3); + Ui.PopStyleColor(3); } } private void DrawContentPane(EditorContext context) { - NativeImGui.PushStyleColor((int)ImGuiCol.ChildBg, EditorPalette.collectionHeader); + Ui.PushStyleColor(ImGuiCol.ChildBg, EditorPalette.collectionHeader); try { - bool contentVisible = NativeImGui.BeginChild( - "##ContentPane", - Vector2.Zero, - (int)ImGuiChildFlags.None, - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); + bool contentVisible = Ui.BeginChild("##ContentPane", Vector2.Zero, ImGuiChildFlags.None, (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); try { if (contentVisible) { DrawToolbar(context); - DrawEntriesRegion( - context, - m_data.CollectVisibleEntries(context, m_entryTypeFilter, m_entryScopeFilter, m_filter)); + DrawEntriesRegion(context, m_data.CollectVisibleEntries(m_entryTypeFilter, m_entryScopeFilter, m_filter)); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } - if (!IsReadOnlyLocation(m_assets.pipeline, m_assets.browser)) + + if (!IsReadOnlyLocation(m_assets.edits.pipeline, m_assets.browser)) m_dragDrop.DrawDirectoryTarget(context, m_assets.browser.currentDirectory); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } @@ -303,42 +241,46 @@ private void DrawEntriesRegion( EditorContext context, IReadOnlyList entries ) { - if (m_viewMode == ViewMode.List) + m_visibleSelection = m_data.visibleSelection; + m_drawKeyboardFocus = m_requestedKeyboardFocus; + m_requestedKeyboardFocus = null; + m_keyboardHandled = false; + try { - DrawListRegion(context, entries); - return; + if (m_viewMode == ViewMode.List) + DrawListRegion(context, entries); + else + DrawGridRegion(context, entries); } - - DrawGridRegion(context, entries); + finally + { + m_visibleSelection = Array.Empty(); + m_drawKeyboardFocus = null; + } + RuntimeIdentity? activation = m_pendingActivation; + m_pendingActivation = null; + if (activation is RuntimeIdentity identity + && m_assets.edits.interactions.TryResolveIdentity(identity, out Inno.Core.Identity.IdentityObject? target) + && target is AssetFileEntry entry) + m_navigation.OpenEntry(context, entry, m_tree); } private static void DrawFooterTopSplitter() { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - Vector2 cursor = NativeImGui.GetCursorScreenPos(); - float width = NativeImGui.GetContentRegionAvail().X; - float lineY = cursor.Y + style->WindowPadding.Y; - uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.assetBorder); - EditorImGui.DrawLine( - new Vector2(cursor.X, lineY), - new Vector2(cursor.X + width, lineY), - color, - EditorWidget.style.borderSize - ); - NativeImGui.SetCursorPosY(NativeImGui.GetCursorPosY() + style->WindowPadding.Y * 2f); + EditorUiLayout style = Ui.GetLayout(); + Vector2 cursor = Ui.GetCursorScreenPosition(); + float width = Ui.GetContentRegionAvailable().X; + float lineY = cursor.Y + style.windowPadding.Y; + uint color = Ui.ToPackedColor(EditorPalette.contentBorder); + Ui.DrawLine(new Vector2(cursor.X, lineY), new Vector2(cursor.X + width, lineY), color, EditorUI.current.widgets.style.borderSize); + Ui.SetCursorPosY(Ui.GetCursorPosY() + style.windowPadding.Y * 2f); } private static void DrawBreadcrumbTopSeparator() { - Vector2 min = NativeImGui.GetWindowPos(); - Vector2 size = NativeImGui.GetWindowSize(); - uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.assetBorder); - EditorImGui.DrawLine( - min, - new Vector2(min.X + size.X, min.Y), - color, - EditorWidget.style.borderSize - ); + Vector2 min = Ui.GetWindowPos(); + Vector2 size = Ui.GetWindowSize(); + uint color = Ui.ToPackedColor(EditorPalette.contentBorder); + Ui.DrawLine(min, new Vector2(min.X + size.X, min.Y), color, EditorUI.current.widgets.style.borderSize); } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.List.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.List.cs index b7e3af8d7..f89ad1f9e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.List.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.List.cs @@ -1,50 +1,43 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed unsafe partial class FileBrowserPanel - +internal sealed partial class FileBrowserPanel { - private void DrawListRegion( EditorContext context, IReadOnlyList entries ) { - bool entriesVisible = NativeImGui.BeginChild( - "##EntriesScroll", - Vector2.Zero, - (int)ImGuiChildFlags.None); + bool entriesVisible = Ui.BeginChild("##EntriesScroll", Vector2.Zero, ImGuiChildFlags.None); try { if (entriesVisible) { + if (Ui.IsWindowFocused()) + m_rename.MarkInteraction(FileBrowserPresentation.List); + float rowHeight = Ui.GetTextLineHeight() + Ui.GetLayout().cellPadding.Y * 2f + CurrentBrowserStyle.listRowSpacing; + HandleEntryKeyboardNavigation(Math.Max(1, (int)(Ui.GetContentRegionAvailable().Y / rowHeight))); DrawEntriesTable(context, entries, m_assets.browser.currentDirectory); HandleBackgroundSelection(context); - m_contextMenu.DrawBackground( - context, - "##asset_list_background_context", - FileBrowserPresentation.List); + m_contextMenu.DrawBackground(context, "##asset_list_background_context", FileBrowserPresentation.List); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } @@ -53,85 +46,69 @@ private void DrawEntriesTable( IReadOnlyList entries, string currentDirectory ) { - ImGuiTableFlags flags = - ImGuiTableFlags.RowBg | - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoSavedSettings; - - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - Vector2 tableOrigin = NativeImGui.GetCursorScreenPos(); - Vector2 tableSize = NativeImGui.GetContentRegionAvail(); - NativeImGui.PushStyleVar((int)ImGuiStyleVar.CellPadding, new Vector2(style->WindowPadding.X, style->CellPadding.Y)); + ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings; + EditorUiLayout style = Ui.GetLayout(); + Vector2 tableOrigin = Ui.GetCursorScreenPosition(); + Vector2 tableSize = Ui.GetContentRegionAvailable(); + Ui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(style.windowPadding.X, style.cellPadding.Y)); try { - bool tableStarted = NativeImGui.BeginTable( - "##FileBrowserEntries", - 3, - (int)flags, - new Vector2(0f, 0f)); + bool tableStarted = Ui.BeginTable("##FileBrowserEntries", 3, flags, new Vector2(0f, 0f)); if (!tableStarted) { - NativeImGui.Dummy(Vector2.Zero); + Ui.Dummy(Vector2.Zero); return; } + try { - NativeImGui.TableSetupColumn( - "Name", - (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), - m_listNameSeparatorPosition); - NativeImGui.TableSetupColumn( - "Type", - (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), - m_listTypeSeparatorPosition - m_listNameSeparatorPosition); - NativeImGui.TableSetupColumn( - "Source", - (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), - 1f - m_listTypeSeparatorPosition); + Ui.TableSetupColumn("Name", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), m_listNameSeparatorPosition); + Ui.TableSetupColumn("Type", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), m_listTypeSeparatorPosition - m_listNameSeparatorPosition); + Ui.TableSetupColumn("Source", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), 1f - m_listTypeSeparatorPosition); DrawHeaderRow(); - - uint rowBg = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionRow); - uint rowAltBg = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionRowAlternate); - float entryRowHeight = NativeImGui.GetTextLineHeight() + style->CellPadding.Y * 2f; - for (int i = 0; i < entries.Count; i++) + uint rowBg = Ui.ToPackedColor(EditorPalette.collectionRow); + uint rowAltBg = Ui.ToPackedColor(EditorPalette.collectionRowAlternate); + float entryRowHeight = Ui.GetTextLineHeight() + style.cellPadding.Y * 2f + CurrentBrowserStyle.listRowSpacing; + Span preserved = stackalloc int[3]; + int preservedCount = CollectPreservedEntryRows(entries, preserved); + m_ui.widgets.ClippedRows(entries.Count, -1f, + (panel: this, context, entries, currentDirectory, rowBg, rowAltBg, entryRowHeight), static ( + state, + start, + end + ) => { - FileBrowserDisplayEntry item = entries[i]; - AssetFileEntry entry = item.entry; - if (i > 0) - NativeImGui.TableNextRow((int)ImGuiTableRowFlags.None, EditorWidget.style.assetListRowSpacing); - NativeImGui.TableNextRow((int)ImGuiTableRowFlags.None, entryRowHeight); - NativeImGui.TableSetBgColor( - (int)ImGuiTableBgTarget.RowBg0, - i % 2 == 0 ? rowBg : rowAltBg); - - DrawNameCell(context, item); - DrawTextCell(item.isPluginRoot ? "IPLUGIN" : GetTypeText(entry), EditorPalette.assetText); - DrawTextCell( - item.isPluginRoot - ? "~" - : GetSourceText(entry, currentDirectory), - EditorPalette.assetText); - } + for (int index = start; index < end; index++) + { + FileBrowserDisplayEntry item = state.entries[index]; + Ui.TableNextRow(ImGuiTableRowFlags.None, state.entryRowHeight); + if (!state.panel.m_data.TryResolve(item, out AssetFileEntry? entry)) + continue; + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, index % 2 == 0 ? state.rowBg : state.rowAltBg); + state.panel.DrawNameCell(state.context, item, entry); + DrawTextCell(item.isPluginRoot ? "IPLUGIN" : GetTypeText(entry), EditorPalette.contentText); + DrawTextCell(item.isPluginRoot ? "~" : GetSourceText(entry, state.currentDirectory), EditorPalette.contentText); + } + }, preserved[..preservedCount]); } finally { - NativeImGui.EndTable(); + Ui.EndTable(); } - Vector2 tableEnd = NativeImGui.GetCursorScreenPos(); + Vector2 tableEnd = Ui.GetCursorScreenPosition(); float tableHeight = MathF.Max(1f, tableEnd.Y - tableOrigin.Y); var interactionSize = new Vector2(tableSize.X, tableHeight); ListColumnSeparatorState separators = HandleListColumnSeparators(tableOrigin, interactionSize); DrawListColumnSeparators(tableOrigin, tableSize.X, tableHeight, separators); - NativeImGui.SetCursorScreenPos(tableEnd); + Ui.SetCursorScreenPosition(tableEnd); // Commit the restored layout cursor so overlay separator hit targets cannot implicitly // extend the scrolling child when UI scaling changes their physical bounds. - NativeImGui.Dummy(Vector2.Zero); + Ui.Dummy(Vector2.Zero); } finally { - NativeImGui.PopStyleVar(); + Ui.PopStyleVar(); } } @@ -141,39 +118,36 @@ Vector2 size ) { float width = MathF.Max(1f, size.X); float height = MathF.Max(1f, size.Y); - float hitWidth = EditorWidget.style.assetListSeparatorHitWidth; + float hitWidth = CurrentBrowserStyle.listSeparatorHitWidth; bool nameHovered; bool nameActive; bool typeHovered; bool typeActive; - float nameX = origin.X + width * m_listNameSeparatorPosition; - NativeImGui.SetCursorScreenPos(new Vector2(nameX - hitWidth * 0.5f, origin.Y)); - NativeImGui.SetNextItemAllowOverlap(); - _ = NativeImGui.InvisibleButton("##AssetListNameSeparator", new Vector2(hitWidth, height)); - nameHovered = NativeImGui.IsItemHovered(); - nameActive = NativeImGui.IsItemActive(); + Ui.SetCursorScreenPosition(new Vector2(nameX - hitWidth * 0.5f, origin.Y)); + Ui.SetNextItemAllowOverlap(); + _ = Ui.InvisibleButton("##AssetListNameSeparator", new Vector2(hitWidth, height)); + nameHovered = Ui.IsItemHovered(); + nameActive = Ui.IsItemActive(); if (nameHovered || nameActive) - NativeImGui.SetMouseCursor((int)ImGuiMouseCursor.ResizeEw); - + Ui.SetMouseCursor(ImGuiMouseCursor.ResizeEw); if (nameActive) { - float requested = (NativeImGui.GetMousePos().X - origin.X) / width; + float requested = (Ui.GetMousePos().X - origin.X) / width; SetListColumnSeparators(requested, m_listTypeSeparatorPosition); } float typeX = origin.X + width * m_listTypeSeparatorPosition; - NativeImGui.SetCursorScreenPos(new Vector2(typeX - hitWidth * 0.5f, origin.Y)); - NativeImGui.SetNextItemAllowOverlap(); - _ = NativeImGui.InvisibleButton("##AssetListTypeSeparator", new Vector2(hitWidth, height)); - typeHovered = NativeImGui.IsItemHovered(); - typeActive = NativeImGui.IsItemActive(); + Ui.SetCursorScreenPosition(new Vector2(typeX - hitWidth * 0.5f, origin.Y)); + Ui.SetNextItemAllowOverlap(); + _ = Ui.InvisibleButton("##AssetListTypeSeparator", new Vector2(hitWidth, height)); + typeHovered = Ui.IsItemHovered(); + typeActive = Ui.IsItemActive(); if (typeHovered || typeActive) - NativeImGui.SetMouseCursor((int)ImGuiMouseCursor.ResizeEw); - + Ui.SetMouseCursor(ImGuiMouseCursor.ResizeEw); if (typeActive) { - float requested = (NativeImGui.GetMousePos().X - origin.X) / width; + float requested = (Ui.GetMousePos().X - origin.X) / width; SetListColumnSeparators(m_listNameSeparatorPosition, requested); } @@ -187,18 +161,8 @@ private void DrawListColumnSeparators( ListColumnSeparatorState state ) { float bottom = origin.Y + MathF.Max(1f, height); - DrawListColumnSeparator( - origin.X + width * m_listNameSeparatorPosition, - origin.Y, - bottom, - state.nameHovered, - state.nameActive); - DrawListColumnSeparator( - origin.X + width * m_listTypeSeparatorPosition, - origin.Y, - bottom, - state.typeHovered, - state.typeActive); + DrawListColumnSeparator(origin.X + width * m_listNameSeparatorPosition, origin.Y, bottom, state.nameHovered, state.nameActive); + DrawListColumnSeparator(origin.X + width * m_listTypeSeparatorPosition, origin.Y, bottom, state.typeHovered, state.typeActive); } private static void DrawListColumnSeparator( @@ -208,114 +172,77 @@ private static void DrawListColumnSeparator( bool hovered, bool active ) { - Vector4 color = active - ? EditorPalette.assetAccent - : hovered - ? EditorPalette.assetBorder - : EditorPalette.assetBorderSoft; - EditorImGui.DrawLine( - new Vector2(x, top), - new Vector2(x, bottom), - NativeImGui.ColorConvertFloat4ToU32(color), - EditorWidget.style.borderSize - ); + Vector4 color = active ? EditorPalette.contentAccent : hovered ? EditorPalette.contentBorder : EditorPalette.contentBorderSoft; + Ui.DrawLine(new Vector2(x, top), new Vector2(x, bottom), Ui.ToPackedColor(color), EditorUI.current.widgets.style.borderSize); } private void SetListColumnSeparators( float namePosition, float typePosition ) { - float minimum = EditorWidget.style.assetListMinimumColumnRatio; - m_listNameSeparatorPosition = Math.Clamp( - namePosition, - minimum, - 1f - minimum * 2f); - m_listTypeSeparatorPosition = Math.Clamp( - typePosition, - m_listNameSeparatorPosition + minimum, - 1f - minimum); + float minimum = browserStyle.listMinimumColumnRatio; + m_listNameSeparatorPosition = Math.Clamp(namePosition, minimum, 1f - minimum * 2f); + m_listTypeSeparatorPosition = Math.Clamp(typePosition, m_listNameSeparatorPosition + minimum, 1f - minimum); } private static void DrawHeaderRow() { - NativeImGui.TableNextRow(); - NativeImGui.TableSetBgColor( - (int)ImGuiTableBgTarget.RowBg0, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionHeader)); - NativeImGui.TableSetBgColor( - (int)ImGuiTableBgTarget.RowBg1, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionHeader)); - _ = NativeImGui.TableSetColumnIndex(0); + Ui.TableNextRow(); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, Ui.ToPackedColor(EditorPalette.collectionHeader)); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, Ui.ToPackedColor(EditorPalette.collectionHeader)); + _ = Ui.TableSetColumnIndex(0); InsetListCellContent(); - NativeImGui.TextUnformatted("Name"); - _ = NativeImGui.TableSetColumnIndex(1); + Ui.Text("Name"); + _ = Ui.TableSetColumnIndex(1); InsetListCellContent(); - NativeImGui.TextUnformatted("Type"); - _ = NativeImGui.TableSetColumnIndex(2); + Ui.Text("Type"); + _ = Ui.TableSetColumnIndex(2); InsetListCellContent(); - NativeImGui.TextUnformatted("Source"); + Ui.Text("Source"); } private void DrawNameCell( EditorContext context, - FileBrowserDisplayEntry item + FileBrowserDisplayEntry item, + AssetFileEntry entry ) { - AssetFileEntry entry = item.entry; - _ = NativeImGui.TableSetColumnIndex(0); + _ = Ui.TableSetColumnIndex(0); string icon = m_assets.GetIcon(entry); - string name = item.isPluginRoot - ? item.displayName - : entry.nameWithoutExtension; + string name = item.isPluginRoot ? item.displayName : entry.nameWithoutExtension; bool selected = m_assets.browser.IsSelected(context, entry); bool editing = m_rename.IsEditing(context, entry.assetPath.ToString(), FileBrowserPresentation.List); - ImGuiTable* table = NativeImGui.Raw.GetCurrentTable(); - float rowMinimumY = table->RowPosY1; - float rowMaximumY = table->RowPosY2; - + var table = Ui.GetTableBounds(); + float rowMinimumY = table.rowMinimum; + float rowMaximumY = table.rowMaximum; InsetListCellContent(); - NativeImGui.PushStyleColor((int)ImGuiCol.Header, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.HeaderHovered, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.HeaderActive, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.Header, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.HeaderHovered, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.HeaderActive, EditorPalette.transparent); try { - Vector2 iconTextPos = NativeImGui.GetCursorScreenPos(); - ImGuiSelectableFlags selectableFlags = - ImGuiSelectableFlags.SpanAllColumns | - ImGuiSelectableFlags.AllowDoubleClick | - ImGuiSelectableFlags.AllowOverlap; - bool activated = NativeImGui.Selectable( - $"##entry_{entry.assetPath.ToString()}", - selected, - (int)selectableFlags); - bool itemHovered = NativeImGui.IsItemHovered(); - bool doubleClicked = itemHovered && NativeImGui.IsMouseDoubleClicked((int)ImGuiMouseButton.Left); - if (activated) + Vector2 iconTextPos = Ui.GetCursorScreenPosition(); + ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.AllowOverlap; + RequestEntryKeyboardFocus(entry); + bool activated = Ui.Selectable($"##entry_{entry.assetPath.ToString()}", selected, selectableFlags); + bool itemHovered = Ui.IsItemHovered(); + bool doubleClicked = itemHovered && Ui.IsMouseDoubleClicked(ImGuiMouseButton.Left); + if (activated && !m_keyboardHandled) { - HandleEntryActivation( - context, - entry, - FileBrowserPresentation.List, - doubleClicked); + HandleEntryActivation(context, entry, FileBrowserPresentation.List, doubleClicked); } - bool itemActive = NativeImGui.IsItemActive(); + bool itemActive = Ui.IsItemActive(); if (!editing) { - m_contextMenu.DrawEntry( - context, - $"##asset_context_{entry.assetPath.ToString()}", - entry.assetPath.ToString(), - FileBrowserPresentation.List); + m_contextMenu.DrawEntry(context, $"##asset_context_{entry.assetPath.ToString()}", entry.assetPath.ToString(), FileBrowserPresentation.List); } + if (selected || itemHovered) { - Vector4 highlight = itemActive - ? EditorPalette.GetHovered(EditorPalette.assetAccent) - : EditorPalette.assetAccent; - NativeImGui.TableSetBgColor( - (int)ImGuiTableBgTarget.RowBg1, - NativeImGui.ColorConvertFloat4ToU32(highlight)); + Vector4 highlight = itemActive ? EditorPalette.GetHovered(EditorPalette.contentAccent) : EditorPalette.contentAccent; + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, Ui.ToPackedColor(highlight)); } + if (!editing) { m_dragDrop.DrawAssetSource(context, entry); @@ -323,29 +250,30 @@ FileBrowserDisplayEntry item m_dragDrop.DrawDirectoryTarget(context, entry.assetPath.ToString()); } - NativeImGui.SameLine(iconTextPos.X - NativeImGui.GetWindowPos().X, 0f); + Ui.SameLine(iconTextPos.X - Ui.GetWindowPos().X, 0f); if (editing) { - EditorWidget.IconText(icon, string.Empty, false); - NativeImGui.SameLine(0f, 0f); - Vector2 renameCursor = NativeImGui.GetCursorScreenPos(); - NativeImGui.SetCursorScreenPos(new Vector2(renameCursor.X, rowMinimumY)); + EditorUI.current.widgets.IconText(icon, string.Empty, false); + Ui.SameLine(0f, 0f); + Vector2 renameCursor = Ui.GetCursorScreenPosition(); + Ui.SetCursorScreenPosition(new Vector2(renameCursor.X, rowMinimumY)); m_rename.Draw( context, $"list_{entry.assetPath.ToString()}", entry.assetPath.ToString(), FileBrowserPresentation.List, - NativeImGui.GetContentRegionAvail().X, - MathF.Max(1f, rowMaximumY - rowMinimumY)); + Ui.GetContentRegionAvailable().X, + MathF.Max(1f, rowMaximumY - rowMinimumY) + ); } else { - EditorWidget.IconText(icon, name, false); + EditorUI.current.widgets.IconText(icon, name, false); } } finally { - NativeImGui.PopStyleColor(3); + Ui.PopStyleColor(3); } } @@ -353,17 +281,16 @@ private static void DrawTextCell( string text, Vector4 color ) { - NativeImGui.TableNextColumn(); + Ui.TableNextColumn(); InsetListCellContent(); - NativeImGui.PushStyleColor((int)ImGuiCol.Text, color); - NativeImGui.TextUnformatted(text); - NativeImGui.PopStyleColor(); + Ui.PushStyleColor(ImGuiCol.Text, color); + Ui.Text(text); + Ui.PopStyleColor(); } private static void InsetListCellContent() { - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + EditorWidget.style.assetListContentHorizontalPadding); + Ui.SetCursorPosX(Ui.GetCursorPosX() + CurrentBrowserStyle.listContentHorizontalPadding); } private readonly record struct ListColumnSeparatorState( @@ -372,5 +299,4 @@ private readonly record struct ListColumnSeparatorState( bool typeHovered, bool typeActive ); - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Navigation.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Navigation.cs index 9be864440..1282efbca 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Navigation.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.Navigation.cs @@ -1,25 +1,42 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Core.Identity; +using Inno.Editor.Interactions; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed unsafe partial class FileBrowserPanel - +internal sealed partial class FileBrowserPanel { + private int CollectPreservedEntryRows( + IReadOnlyList entries, + Span rows, + int columns = 1 + ) { + RuntimeIdentity? editing = m_rename.activeTarget; + RuntimeIdentity? dragging = m_assets.edits.interactions.TryGetActiveDragIdentity(out RuntimeIdentity source) ? source : null; + if (editing is null && dragging is null && m_drawKeyboardFocus is null) + return 0; + int count = 0; + for (int index = 0; index < entries.Count; index++) + { + RuntimeIdentity identity = entries[index].identity; + if (identity == editing || identity == dragging || identity == m_drawKeyboardFocus) + rows[count++] = index / columns; + } + return count; + } private void DrawToolbar(EditorContext context) { @@ -30,53 +47,42 @@ private void DrawToolbar(EditorContext context) private void DrawNavigationBar(EditorContext context) { string current = m_assets.browser.currentDirectory; - - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.breadcrumbFramePadding); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.breadcrumbFramePadding); bool canGoBack = m_navigation.canGoBack; - PushButtonColors(canGoBack ? EditorPalette.assetAccent : EditorPalette.assetBorderSoft); - - NativeImGui.BeginDisabled(!canGoBack); - if (NativeImGui.SmallButton($"{ImGuiIcon.AngleLeft}##Back")) + PushButtonColors(canGoBack ? EditorPalette.contentAccent : EditorPalette.contentBorderSoft); + Ui.BeginDisabled(!canGoBack); + if (Ui.SmallButton($"{ImGuiIcon.AngleLeft}##Back")) m_navigation.GoBack(context); - - NativeImGui.EndDisabled(); - NativeImGui.PopStyleColor(3); - - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarTightSpacing); + Ui.EndDisabled(); + Ui.PopStyleColor(3); + Ui.SameLine(0f, EditorUI.current.widgets.style.toolbarTightSpacing); bool canGoForward = m_navigation.canGoForward; - PushButtonColors(canGoForward ? EditorPalette.assetAccent : EditorPalette.assetBorderSoft); - NativeImGui.BeginDisabled(!canGoForward); - if (NativeImGui.SmallButton($"{ImGuiIcon.AngleRight}##Forward")) + PushButtonColors(canGoForward ? EditorPalette.contentAccent : EditorPalette.contentBorderSoft); + Ui.BeginDisabled(!canGoForward); + if (Ui.SmallButton($"{ImGuiIcon.AngleRight}##Forward")) m_navigation.GoForward(context); - - NativeImGui.EndDisabled(); - NativeImGui.PopStyleColor(3); - - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarSectionSpacing); - - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.assetText); - NativeImGui.TextUnformatted(GetDirectoryLabel(m_assets.browser.root, current)); - NativeImGui.PopStyleColor(); - - NativeImGui.PopStyleVar(); + Ui.EndDisabled(); + Ui.PopStyleColor(3); + Ui.SameLine(0f, EditorUI.current.widgets.style.toolbarSectionSpacing); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.contentText); + Ui.Text(GetDirectoryLabel(m_assets.browser.root, current)); + Ui.PopStyleColor(); + Ui.PopStyleVar(); } private void DrawViewAndSearchBar() { - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.toolbarFramePadding); - - PushButtonColors(EditorPalette.assetAccent); - if (NativeImGui.SmallButton($"{m_viewMode}##ViewMode")) + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.toolbarFramePadding); + PushButtonColors(EditorPalette.contentAccent); + if (Ui.SmallButton($"{m_viewMode}##ViewMode")) m_viewMode = m_viewMode == ViewMode.List ? ViewMode.Grid : ViewMode.List; - NativeImGui.PopStyleColor(3); - - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarSpacing); + Ui.PopStyleColor(3); + Ui.SameLine(0f, EditorUI.current.widgets.style.toolbarSpacing); DrawEntryFilterCombo(); - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarSpacing); - - NativeImGui.SetNextItemWidth(-1f); + Ui.SameLine(0f, EditorUI.current.widgets.style.toolbarSpacing); + Ui.SetNextItemWidth(-1f); DrawSearchInput(); - NativeImGui.PopStyleVar(); + Ui.PopStyleVar(); } private void HandleEntryActivation( @@ -86,61 +92,102 @@ private void HandleEntryActivation( bool doubleClicked ) { m_rename.MarkInteraction(presentation); + m_keyboardFocus = entry.identity.runtimeIdentity; + m_assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, entry).Focus(); if (doubleClicked) { - m_navigation.OpenEntry(context, entry, m_tree); + m_pendingActivation = entry.identity.runtimeIdentity; return; } - ImGuiIO* io = NativeImGui.Raw.GetIO(); - if (io->KeyCtrl != 0 || io->KeySuper != 0) - m_assets.browser.ToggleSelection(context, entry); - else - m_assets.browser.Select(context, entry.assetPath.ToString()); + EditorUI.current.widgets.SelectTarget(m_assets.edits.interactions, entry, m_visibleSelection); + } + + private void HandleEntryKeyboardNavigation(int pageSize) + { + EditorInteractions interactions = m_assets.edits.interactions; + int focus = -1; + int selectedIndex = -1; + RuntimeIdentity? selected = (interactions.selection.selectedTarget as IdentityObject)?.identity.runtimeIdentity; + for (int i = 0; i < m_visibleSelection.Count; i++) + { + RuntimeIdentity candidate = m_visibleSelection[i]; + if (candidate == m_keyboardFocus) + { + focus = i; + break; + } + if (candidate == selected) + selectedIndex = i; + } + if (focus < 0) + focus = selectedIndex; + ListNavigationResult navigation = m_ui.widgets.NavigateList(interactions, m_visibleSelection.Count, focus, pageSize); + m_keyboardHandled = navigation.focusChanged || navigation.selectionRequested || navigation.activate || navigation.cancel; + if (navigation.focusedIndex >= 0 && m_keyboardHandled) + { + RuntimeIdentity identity = m_visibleSelection[navigation.focusedIndex]; + if (!interactions.TryResolveIdentity(identity, out IdentityObject? target) || target is null) + return; + m_keyboardFocus = identity; + if (navigation.focusChanged) + { + m_requestedKeyboardFocus = m_keyboardFocus; + m_drawKeyboardFocus = m_keyboardFocus; + } + if (navigation.selectionRequested) + { + if (navigation.extendRange) + interactions.SelectRange(m_visibleSelection, target, navigation.additive); + else if (navigation.toggle) + interactions.ToggleSelection(target); + else + interactions.SetSelection(target); + } + interactions.For(FileBrowserInteractionIds.C_AREA, interactions.selection.selectedTarget).Focus(); + if (navigation.activate) + m_pendingActivation = m_keyboardFocus; + } + if (navigation.cancel) + m_keyboardFocus = null; + } + + private void RequestEntryKeyboardFocus(AssetFileEntry entry) + { + if (entry.identity.runtimeIdentity == m_drawKeyboardFocus && Ui.IsWindowFocused(ImGuiFocusedFlags.ChildWindows)) + Ui.FocusNextItem(); } private void DrawBreadcrumbBar( EditorContext context, float height ) { - IReadOnlyList parts = BuildBreadcrumbParts( - m_assets.browser.root, - m_assets.browser.currentDirectory); - Vector2 framePadding = EditorWidget.style.breadcrumbFramePadding; + IReadOnlyList parts = BuildBreadcrumbParts(m_assets.browser.root, m_assets.browser.currentDirectory); + Vector2 framePadding = EditorUI.current.widgets.style.breadcrumbFramePadding; float contentWidth = CalculateBreadcrumbContentWidth(parts, framePadding); - float availableWidth = NativeImGui.GetContentRegionAvail().X; + float availableWidth = Ui.GetContentRegionAvailable().X; bool hasHorizontalOverflow = contentWidth > availableWidth; - NativeImGui.SetNextWindowContentSize(new Vector2( - hasHorizontalOverflow ? contentWidth : 0f, - 0f)); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.WindowPadding), Vector2.Zero); - if (NativeImGui.BeginChild("##BreadcrumbBar", new Vector2(0f, height), (int)(ImGuiChildFlags.None), (int)(ImGuiWindowFlags.HorizontalScrollbar))) + Ui.SetNextWindowContentSize(new Vector2(hasHorizontalOverflow ? contentWidth : 0f, 0f)); + Ui.PushStyleVar((ImGuiStyleVar.WindowPadding), Vector2.Zero); + if (Ui.BeginChild("##BreadcrumbBar", new Vector2(0f, height), (ImGuiChildFlags.None), (ImGuiWindowFlags.HorizontalScrollbar))) { DrawBreadcrumbTopSeparator(); - float contentHeight = hasHorizontalOverflow - ? height - NativeImGui.Raw.GetStyle()->ScrollbarSize - : height; - float itemHeight = EditorWidget.GetClickableTextSize("A", framePadding).Y; - NativeImGui.SetCursorPosY(MathF.Max(0f, (contentHeight - itemHeight) * 0.5f)); - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.assetBreadcrumbText); - + float contentHeight = hasHorizontalOverflow ? height - Ui.GetLayout().scrollbarSize : height; + float itemHeight = EditorUI.current.widgets.GetClickableTextSize("A", framePadding).Y; + Ui.SetCursorPosY(MathF.Max(0f, (contentHeight - itemHeight) * 0.5f)); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.contextText); for (int i = 0; i < parts.Count; i++) { BreadcrumbPart part = parts[i]; if (i > 0) { - NativeImGui.SameLine(0f, EditorWidget.style.assetBreadcrumbSpacing); - EditorWidget.CenteredText(">", new Vector2( - NativeImGui.CalcTextSize(">").X, - itemHeight)); - NativeImGui.SameLine(0f, EditorWidget.style.assetBreadcrumbSpacing); + Ui.SameLine(0f, CurrentBrowserStyle.breadcrumbSpacing); + EditorUI.current.widgets.CenteredText(">", new Vector2(Ui.MeasureText(">").X, itemHeight)); + Ui.SameLine(0f, CurrentBrowserStyle.breadcrumbSpacing); } - Vector2 itemSize = EditorWidget.GetClickableTextSize(part.label, framePadding); - if (EditorWidget.ClickableText( - $"crumb_{part.root}_{part.directory}", - part.label, - itemSize)) + Vector2 itemSize = EditorUI.current.widgets.GetClickableTextSize(part.label, framePadding); + if (EditorUI.current.widgets.ClickableText($"crumb_{part.root}_{part.directory}", part.label, itemSize)) { if (string.IsNullOrEmpty(part.directory)) m_navigation.NavigateToRoot(context, part.root); @@ -149,11 +196,11 @@ float height } } - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } - NativeImGui.EndChild(); - NativeImGui.PopStyleVar(); + Ui.EndChild(); + Ui.PopStyleVar(); } private static float GetBreadcrumbBarHeight( @@ -161,19 +208,14 @@ private static float GetBreadcrumbBarHeight( string currentDirectory ) { IReadOnlyList parts = BuildBreadcrumbParts(root, currentDirectory); - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float itemHeight = EditorWidget.GetClickableTextSize( - "A", - EditorWidget.style.breadcrumbFramePadding).Y; + EditorUiLayout style = Ui.GetLayout(); + float itemHeight = EditorUI.current.widgets.GetClickableTextSize("A", EditorUI.current.widgets.style.breadcrumbFramePadding).Y; float contentHeight = MathF.Max( - EditorWidget.style.assetBreadcrumbHeight, - MathF.Ceiling(itemHeight + style->ItemSpacing.Y * 2f + EditorWidget.style.borderSize)); - float contentWidth = CalculateBreadcrumbContentWidth( - parts, - EditorWidget.style.breadcrumbFramePadding); - return contentWidth > NativeImGui.GetContentRegionAvail().X - ? contentHeight + style->ScrollbarSize - : contentHeight; + CurrentBrowserStyle.breadcrumbHeight, + MathF.Ceiling(itemHeight + style.itemSpacing.Y * 2f + EditorUI.current.widgets.style.borderSize) + ); + float contentWidth = CalculateBreadcrumbContentWidth(parts, EditorUI.current.widgets.style.breadcrumbFramePadding); + return contentWidth > Ui.GetContentRegionAvailable().X ? contentHeight + style.scrollbarSize : contentHeight; } private static float CalculateBreadcrumbContentWidth( @@ -182,18 +224,15 @@ Vector2 framePadding ) { if (parts.Count == 0) return 0f; - float width = 0f; - float separatorWidth = NativeImGui.CalcTextSize(">").X; + float separatorWidth = Ui.MeasureText(">").X; for (int i = 0; i < parts.Count; i++) { if (i > 0) - width += separatorWidth + EditorWidget.style.assetBreadcrumbSpacing * 2f; - - width += NativeImGui.CalcTextSize(parts[i].label).X + framePadding.X * 2f; + width += separatorWidth + CurrentBrowserStyle.breadcrumbSpacing * 2f; + width += Ui.MeasureText(parts[i].label).X + framePadding.X * 2f; } return MathF.Ceiling(width); } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.cs index 056d05a89..7e92fc74d 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserPanel.cs @@ -1,33 +1,36 @@ +using Inno.Core.Identity; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - /// /// Asset browser panel with a tree pane and filtered table view. /// [EditorPanel("asset.file-browser", "File", order: 300, menuPath: "Content")] internal sealed partial class FileBrowserPanel : EditorPanel { + private IReadOnlyList m_visibleSelection = Array.Empty(); + private RuntimeIdentity? m_keyboardFocus; + private RuntimeIdentity? m_requestedKeyboardFocus; + private RuntimeIdentity? m_drawKeyboardFocus; + private RuntimeIdentity? m_pendingActivation; + private bool m_keyboardHandled; private const int C_SEARCH_BUFFER_SIZE = 256; private const int C_GRID_LABEL_LINE_COUNT = 2; private const float C_GRID_ICON_HORIZONTAL_OPTICAL_OFFSET = -1f; - - private readonly AssetEditorModule m_assets; + private readonly AssetBrowserModule m_assets; + private readonly EditorUiSession m_ui; private readonly FileBrowserData m_data; private readonly FileBrowserNavigation m_navigation; private readonly FileBrowserDragDrop m_dragDrop; @@ -35,28 +38,27 @@ internal sealed partial class FileBrowserPanel : EditorPanel private readonly FileBrowserRename m_rename; private readonly FileBrowserContextMenu m_contextMenu; private readonly FileBrowserTree m_tree; - private float m_treePaneRatio = 0.5f; private string m_filter = string.Empty; private ViewMode m_viewMode = ViewMode.List; private FileBrowserEntryTypeFilter m_entryTypeFilter = FileBrowserEntryTypeFilter.All; private FileBrowserEntryScopeFilter m_entryScopeFilter = FileBrowserEntryScopeFilter.CurrentOnly; - private float m_gridScale = EditorWidget.style.assetGridDefaultScale; - private float m_listNameSeparatorPosition = EditorWidget.style.assetListNameSeparatorPosition; - private float m_listTypeSeparatorPosition = EditorWidget.style.assetListTypeSeparatorPosition; - + private float m_gridScale; + private float m_listNameSeparatorPosition; + private float m_listTypeSeparatorPosition; private enum ViewMode { List, Grid } - /// /// Keeps scrolling inside the tree, entry list, and breadcrumb regions. /// public override bool allowScrolling => false; + private AssetBrowserStyle browserStyle => new(m_ui.widgets.style); + /// /// Captures an immutable snapshot of the current observable state. /// @@ -85,51 +87,50 @@ protected override void Restore(EditorState state) { if (Enum.TryParse(state.Get("viewMode", string.Empty), out ViewMode viewMode)) m_viewMode = viewMode; - if (Enum.TryParse( - state.Get("entryTypeFilter", string.Empty), - out FileBrowserEntryTypeFilter typeFilter)) + if (Enum.TryParse(state.Get("entryTypeFilter", string.Empty), out FileBrowserEntryTypeFilter typeFilter)) { m_entryTypeFilter = typeFilter; } - if (Enum.TryParse( - state.Get("entryScopeFilter", string.Empty), - out FileBrowserEntryScopeFilter scopeFilter)) + + if (Enum.TryParse(state.Get("entryScopeFilter", string.Empty), out FileBrowserEntryScopeFilter scopeFilter)) { m_entryScopeFilter = scopeFilter; } + m_filter = state.Get("filter", string.Empty); float restoredTreePaneRatio = state.Get("treePaneRatio", 0.5f); - m_treePaneRatio = float.IsFinite(restoredTreePaneRatio) - ? Math.Clamp(restoredTreePaneRatio, 0f, 1f) - : 0.5f; + m_treePaneRatio = float.IsFinite(restoredTreePaneRatio) ? Math.Clamp(restoredTreePaneRatio, 0f, 1f) : 0.5f; m_gridScale = Math.Clamp( - state.Get("gridScale", EditorWidget.style.assetGridDefaultScale), - EditorWidget.style.assetGridMinimumScale, - EditorWidget.style.assetGridMaximumScale); + state.Get("gridScale", browserStyle.gridDefaultScale), + browserStyle.gridMinimumScale, + browserStyle.gridMaximumScale + ); SetListColumnSeparators( - state.Get("listNameSeparator", EditorWidget.style.assetListNameSeparatorPosition), - state.Get("listTypeSeparator", EditorWidget.style.assetListTypeSeparatorPosition)); + state.Get("listNameSeparator", browserStyle.listNameSeparatorPosition), + state.Get("listTypeSeparator", browserStyle.listTypeSeparatorPosition) + ); } /// /// Creates the panel. /// - internal FileBrowserPanel(AssetEditorModule assets) - { + internal FileBrowserPanel( + AssetBrowserModule assets, + EditorUiSession ui + ) { m_assets = assets; + m_ui = ui ?? throw new ArgumentNullException(nameof(ui)); + AssetBrowserStyle style = new(ui.widgets.style); + m_gridScale = style.gridDefaultScale; + m_listNameSeparatorPosition = style.listNameSeparatorPosition; + m_listTypeSeparatorPosition = style.listTypeSeparatorPosition; m_data = new FileBrowserData(assets); m_navigation = new FileBrowserNavigation(assets); m_dragDrop = new FileBrowserDragDrop(assets); m_changeTracker = new FileBrowserChangeTracker(assets); m_rename = new FileBrowserRename(assets); m_contextMenu = new FileBrowserContextMenu(assets, m_rename); - m_tree = new FileBrowserTree( - m_data, - m_navigation, - m_dragDrop, - m_rename, - m_contextMenu, - assets); + m_tree = new FileBrowserTree(m_data, m_navigation, m_dragDrop, m_rename, m_contextMenu, assets); } /// @@ -173,5 +174,4 @@ protected override void OnDraw(EditorContext context) PopBrowserStyle(); } } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserRename.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserRename.cs index 197559569..adfd71578 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserRename.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserRename.cs @@ -1,12 +1,13 @@ +using Inno.Core.Identity; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; - using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.ImGui.ImGuiWidget; namespace Inno.Editor.Panel.FileBrowser; - internal enum FileBrowserPresentation { Tree, @@ -14,21 +15,19 @@ internal enum FileBrowserPresentation Grid } -internal sealed class FileBrowserRename(AssetEditorModule assets) +internal sealed class FileBrowserRename(AssetBrowserModule assets) { - private AssetFileEntry? m_activeTarget; + private RuntimeIdentity? m_activeTarget; private FileBrowserPresentation m_lastPresentation = FileBrowserPresentation.List; private FileBrowserPresentation m_activePresentation; - + internal RuntimeIdentity? activeTarget => m_activeTarget; internal void Update(EditorContext context) { - AssetFileEntry? target = assets.interactions.selection.selectedTarget as AssetFileEntry; - bool isActive = target is not null && - assets.interactions.For(FileBrowserInteractionIds.C_AREA, target) - .IsActive(FileBrowserInteractionIds.C_RENAME); - if (isActive && Equals(target, m_activeTarget)) + AssetFileEntry? target = assets.edits.interactions.selection.selectedTarget as AssetFileEntry; + bool isActive = target is not null && assets.edits.interactions.For(FileBrowserInteractionIds.C_AREA, target).IsActive(FileBrowserInteractionIds.C_RENAME); + if (isActive && target?.identity.runtimeIdentity == m_activeTarget) return; - m_activeTarget = isActive ? target : null; + m_activeTarget = isActive ? target!.identity.runtimeIdentity : null; m_activePresentation = m_lastPresentation; } @@ -43,9 +42,13 @@ internal bool IsEditing( FileBrowserPresentation presentation ) { Update(context); - return m_activeTarget is AssetFileEntry target && - m_activePresentation == presentation && - string.Equals(target.assetPath.ToString(), relativePath, StringComparison.Ordinal); + return ResolveTarget() is AssetFileEntry target + && m_activePresentation == presentation + && string.Equals( + target.assetPath.ToString(), + relativePath, + StringComparison.Ordinal + ); } internal void Draw( @@ -58,14 +61,18 @@ float rowHeight ) { if (!IsEditing(context, relativePath, presentation) || m_activeTarget is null) return; - _ = assets.interactions - .For(FileBrowserInteractionIds.C_AREA, m_activeTarget) - .Present( - FileBrowserInteractionIds.C_RENAME, - new InlineRenamePresentation( - id, - MathF.Max(1f, width), - MathF.Max(1f, rowHeight))); + _ = assets.edits.interactions.For( + FileBrowserInteractionIds.C_AREA, + ResolveTarget() + ).Present( + FileBrowserInteractionIds.C_RENAME, + new InlineRenamePresentation(id, MathF.Max(1f, width), MathF.Max(1f, rowHeight)) + ); } + private AssetFileEntry? ResolveTarget() => m_activeTarget is RuntimeIdentity identity + && assets.edits.interactions.TryResolveIdentity( + identity, + out IdentityObject? target + ) ? target as AssetFileEntry : null; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Navigation.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Navigation.cs new file mode 100644 index 000000000..3bc7c020b --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Navigation.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Identity; +using Inno.Editor.Assets.ImGui; +using Inno.Editor.Assets; +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using Inno.Editor.ImGui.ImGuiWidget; +using Inno.Editor.Interactions; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Panel.FileBrowser; + +internal sealed partial class FileBrowserTree +{ + private readonly List m_navigationRows = []; + private readonly List m_visibleSelection = []; + private RuntimeIdentity? m_keyboardFocus; + private RuntimeIdentity? m_pendingSelection; + private RuntimeIdentity? m_requestedKeyboardFocus; + private RuntimeIdentity? m_drawKeyboardFocus; + private bool m_rootFocused; + private bool m_requestedRootFocus; + private bool m_drawRootFocus; + private bool m_pendingActivation; + private bool m_pendingRootNavigation; + private int m_keyboardPageSize; + + internal void CompleteNavigation(EditorContext context) + { + EditorInteractions interactions = m_assets.edits.interactions; + if (m_pendingSelection is RuntimeIdentity pending + && interactions.TryResolveIdentity(pending, out IdentityObject? resolved) && resolved is AssetFileEntry entry) + { + if (m_pendingActivation) + { + if (!interactions.selection.Contains(entry)) + interactions.SetSelection(entry); + m_navigation.OpenEntry(context, entry, this); + } + else + EditorUiSession.current.widgets.SelectTarget(interactions, entry, m_visibleSelection); + m_keyboardFocus = pending; + m_rootFocused = false; + interactions.For(FileBrowserInteractionIds.C_AREA, entry).Focus(); + } + if (m_pendingRootNavigation) + { + m_navigation.NavigateToRoot(context, m_assets.browser.root); + m_assets.browser.Select(context, null); + m_keyboardFocus = null; + m_rootFocused = true; + interactions.For(FileBrowserInteractionIds.C_AREA).Focus(); + } + int focus = -1; + int selectedIndex = -1; + RuntimeIdentity? selectedIdentity = (interactions.selection.selectedTarget as IdentityObject)?.identity.runtimeIdentity; + for (int index = 0; index < m_navigationRows.Count; index++) + { + TreeNavigationRow row = m_navigationRows[index]; + if (row.identity is null ? m_rootFocused : row.identity == m_keyboardFocus) + { + focus = index; + break; + } + if (row.identity is not null && row.identity == selectedIdentity) + selectedIndex = index; + } + if (focus < 0) + focus = selectedIndex; + ListNavigationResult navigation = EditorUiSession.current.widgets.NavigateList(interactions, m_navigationRows.Count, focus, m_keyboardPageSize); + if (navigation.focusedIndex >= 0 && (navigation.focusChanged || navigation.selectionRequested || navigation.activate)) + { + TreeNavigationRow row = m_navigationRows[navigation.focusedIndex]; + m_keyboardFocus = row.identity; + m_rootFocused = row.identity is null; + if (navigation.focusChanged) + { + m_requestedKeyboardFocus = row.identity; + m_requestedRootFocus = m_rootFocused; + } + if (row.identity is RuntimeIdentity identity + && interactions.TryResolveIdentity(identity, out IdentityObject? target) && target is AssetFileEntry asset) + { + if (navigation.selectionRequested) + { + if (navigation.extendRange) + interactions.SelectRange(m_visibleSelection, asset, navigation.additive); + else if (navigation.toggle) + interactions.ToggleSelection(asset); + else + interactions.SetSelection(asset); + } + if (navigation.activate) + m_navigation.OpenEntry(context, asset, this); + } + else if (navigation.activate) + { + m_navigation.NavigateToRoot(context, m_assets.browser.root); + m_assets.browser.Select(context, null); + } + interactions.For(FileBrowserInteractionIds.C_AREA, interactions.selection.selectedTarget).Focus(); + } + if (navigation.cancel) + { + m_keyboardFocus = null; + m_rootFocused = false; + } + } + + private void BeginNavigation() + { + if (Ui.IsWindowFocused()) + m_rename.MarkInteraction(FileBrowserPresentation.Tree); + ClearNavigationFrame(); + m_drawKeyboardFocus = m_requestedKeyboardFocus; + m_drawRootFocus = m_requestedRootFocus; + m_requestedKeyboardFocus = null; + m_requestedRootFocus = false; + float rowHeight = Ui.GetFrameHeight() + Ui.GetLayout().itemSpacing.Y; + m_keyboardPageSize = Math.Max(1, (int)(Ui.GetContentRegionAvailable().Y / rowHeight)); + } + + private bool RequestsKeyboardFocus( + AssetFileEntry? entry, + bool isRoot + ) => isRoot ? m_drawRootFocus : entry is not null && entry.identity.runtimeIdentity == m_drawKeyboardFocus; + + private void ClearNavigationFrame() + { + m_pendingSelection = null; + m_pendingActivation = false; + m_pendingRootNavigation = false; + } + + private readonly record struct TreeNavigationRow(RuntimeIdentity? identity); +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Rows.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Rows.cs new file mode 100644 index 000000000..e75b98273 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.Rows.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Identity; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; +using Inno.Editor.Core; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Panel.FileBrowser; + +internal sealed partial class FileBrowserTree +{ + private readonly record struct BrowserTreeRow( + string path, + string label, + string nodeId, + int depth, + bool isRoot, + bool virtualRoot, + bool isLeaf, + RuntimeIdentity? identity + ); + + private readonly List m_rows = []; + private long m_sourceRevision = long.MinValue; + private ulong m_expansionRevision = ulong.MaxValue; + private AssetBrowserRoot? m_rowsRoot; + + internal void Draw(EditorContext context) + { + RefreshRows(); + RuntimeIdentity? dragging = m_assets.edits.interactions.TryGetActiveDragIdentity(out RuntimeIdentity source) ? source : null; + RuntimeIdentity? editing = m_rename.activeTarget; + Span preserved = stackalloc int[4]; + int preservedCount = 0; + for (int index = 0; index < m_rows.Count; index++) + { + BrowserTreeRow row = m_rows[index]; + if (row.identity is { } identity && (identity == dragging || identity == editing || identity == m_drawKeyboardFocus) + || row.isRoot && m_drawRootFocus) + preserved[preservedCount++] = index; + } + EditorUI.current.widgets.ClippedRows(m_rows.Count, -1f, (tree: this, context), static ( + state, + start, + end + ) => + { + for (int index = start; index < end; index++) + state.tree.DrawRow(state.context, state.tree.m_rows[index]); + }, preserved[..preservedCount]); + } + + private void RefreshRows() + { + var widgets = EditorUI.current.widgets; + AssetPipeline pipeline = m_assets.edits.pipeline; + if (!m_rootOpenRequest && !m_currentDirectoryOpenRequest && m_rowsRoot == m_assets.browser.root + && m_sourceRevision == pipeline.revision && m_expansionRevision == widgets.treeExpansionRevision) + return; + var rows = new List(); + var pending = new Stack<(string path, string label, bool root, int depth)>(); + if (m_assets.browser.root == AssetBrowserRoot.Assets) + pending.Push((string.Empty, "Assets", true, 0)); + else + { + const string rootId = "tree_virtual_plugins"; + if (m_rootOpenRequest || m_currentDirectoryOpenRequest) + widgets.SetTreeNodeOpen(rootId, true); + rows.Add(new(string.Empty, "Plugins", rootId, 0, true, true, false, null)); + if (widgets.IsTreeNodeOpen(rootId)) + { + AssetSourceMount[] mounts = pipeline.sourceMounts.Where(mount => m_assets.edits.IsPluginSource(mount.id)) + .OrderBy(static mount => mount.id.value, StringComparer.OrdinalIgnoreCase).ToArray(); + for (int index = mounts.Length - 1; index >= 0; index--) + pending.Push((new AssetPath(mounts[index].id, string.Empty).ToString(), mounts[index].id.value, false, 1)); + } + } + while (pending.TryPop(out var next)) + { + AssetFileEntry? entry = null; + if (!next.root && (!pipeline.TryGetFileSystemEntry(AssetPath.Parse(next.path), out entry) || entry is null)) + continue; + bool directory = next.root || entry!.isDirectory; + IReadOnlyList children = directory ? m_data.SortTreeEntries(m_data.GetVisibleChildren(next.path)) : Array.Empty(); + string id = next.root ? "tree_root_" + next.path : "tree_" + next.path; + if (ShouldOpenTreeEntry(next.path, next.root, directory)) + widgets.SetTreeNodeOpen(id, true); + RuntimeIdentity? identity = next.root ? null : entry!.identity.runtimeIdentity + ?? throw new InvalidOperationException("A visible asset tree entry requires a registered identity."); + rows.Add(new(next.path, next.label, id, next.depth, next.root, false, + !directory || !next.root && children.Count == 0, identity)); + if (!directory || !widgets.IsTreeNodeOpen(id)) + continue; + for (int index = children.Count - 1; index >= 0; index--) + pending.Push((children[index].assetPath.ToString(), children[index].name, false, next.depth + 1)); + } + m_rows.Clear(); + m_rows.AddRange(rows); + m_navigationRows.Clear(); + m_visibleSelection.Clear(); + foreach (BrowserTreeRow row in rows) + { + m_navigationRows.Add(new TreeNavigationRow(row.identity)); + if (row.identity is RuntimeIdentity identity) + m_visibleSelection.Add(identity); + } + m_rowsRoot = m_assets.browser.root; + m_sourceRevision = pipeline.revision; + m_expansionRevision = widgets.treeExpansionRevision; + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.cs index 41f2814da..568aa0283 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserTree.cs @@ -1,4 +1,7 @@ - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Core.Identity; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.Linq; @@ -7,35 +10,29 @@ using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - -internal sealed class FileBrowserTree +internal sealed partial class FileBrowserTree { private readonly FileBrowserData m_data; private readonly FileBrowserNavigation m_navigation; private readonly FileBrowserDragDrop m_dragDrop; private readonly FileBrowserRename m_rename; private readonly FileBrowserContextMenu m_contextMenu; - private readonly AssetEditorModule m_assets; - + private readonly AssetBrowserModule m_assets; private bool m_rootOpenRequest = true; private bool m_currentDirectoryOpenRequest; private string m_currentDirectoryOpenTarget = string.Empty; private string? m_lastCurrentDirectoryOpenTarget; - internal FileBrowserTree( FileBrowserData data, FileBrowserNavigation navigation, FileBrowserDragDrop dragDrop, FileBrowserRename rename, FileBrowserContextMenu contextMenu, - AssetEditorModule assets + AssetBrowserModule assets ) { m_data = data; m_navigation = navigation; @@ -45,149 +42,58 @@ AssetEditorModule assets m_assets = assets; } - internal void DrawEntry( + private void DrawRow( EditorContext context, - string relativePath, - string label, - bool isRoot + BrowserTreeRow row ) { - List sorted = m_data.SortTreeEntries( - m_data.GetVisibleChildren(relativePath)); - bool isDirectory = isRoot || IsDirectoryPath(m_assets.pipeline, relativePath); - bool selected = string.Equals(m_assets.browser.GetSelectedPath(context), relativePath, StringComparison.Ordinal); - bool isLeaf = !isDirectory || (!isRoot && sorted.Count == 0); - bool isCurrentDirectory = isDirectory && - string.Equals(m_assets.browser.currentDirectory, relativePath, StringComparison.Ordinal) && - (m_assets.browser.root == AssetBrowserRoot.Assets) == - (AssetPath.Parse(relativePath).source == AssetSourceId.project); - string icon = m_assets.folderIcon; - if (!isDirectory - && m_assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry iconEntry)) - icon = m_assets.GetIcon(iconEntry); - bool editing = !isRoot && m_rename.IsEditing( - context, - relativePath, - FileBrowserPresentation.Tree); - string nodeId = $"tree_{(isRoot ? $"root_{relativePath}" : relativePath)}"; - if (ShouldOpenTreeEntry(relativePath, isRoot, isDirectory)) - EditorWidget.SetNextTreeNodeOpen(true); - - TreeNodeResult result = EditorWidget.TreeNode( - nodeId, - drawContext => DrawRowContent( - context, - nodeId, - relativePath, - label, - icon, - isCurrentDirectory, - editing, - drawContext.rowHeight), - new TreeNodeOptions { selected = selected, isLeaf = isLeaf }); - - AssetFileEntry? treeEntry = null; - if (!isRoot - && m_assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry resolvedEntry)) - treeEntry = resolvedEntry; - if (treeEntry is not null && result.isDoubleClicked) - { - m_rename.MarkInteraction(FileBrowserPresentation.Tree); - m_navigation.OpenEntry(context, treeEntry, this); - } - else if (treeEntry is not null && result.isClicked) - { - m_rename.MarkInteraction(FileBrowserPresentation.Tree); - m_assets.browser.Select(context, relativePath); - } - else if (isRoot && (result.isClicked || result.isDoubleClicked)) + AssetFileEntry? entry = null; + if (row.identity is { } identity) { - m_navigation.NavigateTo(context, relativePath); - m_assets.browser.Select(context, null); - } - if (treeEntry is not null) - { - if (!editing) + if (!m_assets.edits.interactions.TryResolveIdentity(identity, out Inno.Core.Identity.IdentityObject? resolved) + || resolved is not AssetFileEntry current) { - m_dragDrop.DrawAssetSource(context, treeEntry); - m_contextMenu.DrawEntry( - context, - $"##asset_tree_context_{treeEntry.assetPath.ToString()}", - treeEntry.assetPath.ToString(), - FileBrowserPresentation.Tree); - if (treeEntry.isDirectory && !treeEntry.isReadOnly) - m_dragDrop.DrawDirectoryTarget(context, treeEntry.assetPath.ToString()); + Ui.Dummy(new(1f, Ui.GetFrameHeight())); + return; } + entry = current; } - else if (isRoot) - { - m_contextMenu.DrawDirectory( - context, - $"##asset_tree_root_context_{relativePath}", - relativePath, - FileBrowserPresentation.Tree); - m_dragDrop.DrawDirectoryTarget(context, relativePath); - } - if (!result.isOpen) + bool selected = entry is not null && m_assets.edits.interactions.selection.Contains(entry); + bool currentDirectory = row.virtualRoot ? string.IsNullOrEmpty(m_assets.browser.currentDirectory) + : (entry?.isDirectory ?? row.isRoot) && string.Equals(m_assets.browser.currentDirectory, row.path, StringComparison.Ordinal) + && (m_assets.browser.root == AssetBrowserRoot.Assets) == (AssetPath.Parse(row.path).source == AssetSourceId.project); + string icon = entry is { isDirectory: false } ? m_assets.GetIcon(entry) : m_assets.folderIcon; + bool editing = !row.isRoot && m_rename.IsEditing(context, row.path, FileBrowserPresentation.Tree); + TreeNodeResult result = EditorUI.current.widgets.TreeNode( + row.nodeId, + drawContext => DrawRowContent(context, row.nodeId, row.path, row.label, icon, currentDirectory, editing, drawContext.rowHeight), + new TreeNodeOptions { depth = row.depth, selected = selected, isLeaf = row.isLeaf, + requestFocus = RequestsKeyboardFocus(entry, row.isRoot), keepContentActive = editing }); + if (!result.isSubmitted) return; - try + if (entry is not null && result.isDoubleClicked) { - for (int i = 0; i < sorted.Count; i++) - { - AssetFileEntry child = sorted[i]; - DrawEntry(context, child.assetPath.ToString(), child.name, false); - } - } - finally - { - NativeImGui.TreePop(); + m_rename.MarkInteraction(FileBrowserPresentation.Tree); + m_pendingSelection = entry.identity.runtimeIdentity; + m_pendingActivation = true; } - } - - internal void DrawPluginRoot( - EditorContext context, - IReadOnlyList sourceMounts - ) { - AssetSourceMount[] plugins = sourceMounts - .Where(mount => m_assets.IsPluginSource(mount.id)) - .OrderBy(static mount => mount.id.value, StringComparer.OrdinalIgnoreCase) - .ToArray(); - bool hasPluginDirectoryOpenRequest = m_currentDirectoryOpenRequest && - m_assets.IsPluginSource( - AssetPath.Parse(m_currentDirectoryOpenTarget).source); - if (m_rootOpenRequest || hasPluginDirectoryOpenRequest) - EditorWidget.SetNextTreeNodeOpen(true); - bool isCurrentDirectory = m_assets.browser.root == AssetBrowserRoot.Plugins && - string.IsNullOrEmpty(m_assets.browser.currentDirectory); - - TreeNodeResult result = EditorWidget.TreeNode( - "tree_virtual_plugins", - _ => EditorWidget.IconText(m_assets.folderIcon, "Plugins", isCurrentDirectory), - new TreeNodeOptions - { - selected = false, - isLeaf = false - }); - if (result.isClicked || result.isDoubleClicked) + else if (entry is not null && result.isClicked) { - m_navigation.NavigateToRoot(context, AssetBrowserRoot.Plugins); - m_assets.browser.Select(context, null); + m_rename.MarkInteraction(FileBrowserPresentation.Tree); + m_pendingSelection = entry.identity.runtimeIdentity; } - if (!result.isOpen) - return; - try + else if (row.isRoot && (result.isClicked || result.isDoubleClicked)) + m_pendingRootNavigation = true; + if (entry is not null && !editing) { - for (int i = 0; i < plugins.Length; i++) - { - AssetSourceMount mount = plugins[i]; - string root = new AssetPath(mount.id, string.Empty).ToString(); - // A mount root is a normal directory below the virtual Plugins root. Treating it - // as a browser root would make a single click navigate instead of only selecting. - DrawEntry(context, root, mount.id.value, false); - } + m_dragDrop.DrawAssetSource(context, entry); + m_contextMenu.DrawEntry(context, "##asset_tree_context_" + row.path, row.path, FileBrowserPresentation.Tree); + if (entry.isDirectory && !entry.isReadOnly) + m_dragDrop.DrawDirectoryTarget(context, row.path); } - finally + else if (row.isRoot && !row.virtualRoot) { - NativeImGui.TreePop(); + m_contextMenu.DrawBackground(context, "##asset_tree_root_context", FileBrowserPresentation.Tree); + m_dragDrop.DrawDirectoryTarget(context, row.path); } } @@ -203,26 +109,20 @@ float rowHeight ) { if (!editing) { - EditorWidget.IconText(icon, label, isCurrentDirectory); + EditorUI.current.widgets.IconText(icon, label, isCurrentDirectory); return; } - EditorWidget.IconText(icon, string.Empty, false); - NativeImGui.SameLine(0f, 0f); - m_rename.Draw( - context, - id, - relativePath, - FileBrowserPresentation.Tree, - NativeImGui.GetContentRegionAvail().X, - rowHeight); + EditorUI.current.widgets.IconText(icon, string.Empty, false); + Ui.SameLine(0f, 0f); + m_rename.Draw(context, id, relativePath, FileBrowserPresentation.Tree, Ui.GetContentRegionAvailable().X, rowHeight); } internal void PrepareOpenRequests() { + BeginNavigation(); string currentDirectory = NormalizePath(m_assets.browser.currentDirectory); - if (!m_currentDirectoryOpenRequest && - !string.Equals(m_lastCurrentDirectoryOpenTarget, currentDirectory, StringComparison.Ordinal)) + if (!m_currentDirectoryOpenRequest && !string.Equals(m_lastCurrentDirectoryOpenTarget, currentDirectory, StringComparison.Ordinal)) { m_currentDirectoryOpenTarget = currentDirectory; m_currentDirectoryOpenRequest = true; @@ -234,16 +134,14 @@ internal void ClearOpenRequests() { m_rootOpenRequest = false; m_currentDirectoryOpenRequest = false; + ClearNavigationFrame(); } internal void RequestOpenRoot() => m_rootOpenRequest = true; - internal void RequestOpenTreeToPath(string path) { string normalizedPath = NormalizePath(path); - string treePath = IsDirectoryPath(m_assets.pipeline, normalizedPath) - ? normalizedPath - : GetParentDirectory(normalizedPath); + string treePath = IsDirectoryPath(m_assets.edits.pipeline, normalizedPath) ? normalizedPath : GetParentDirectory(normalizedPath); m_currentDirectoryOpenTarget = treePath; m_currentDirectoryOpenRequest = true; m_lastCurrentDirectoryOpenTarget = treePath; @@ -260,13 +158,12 @@ bool isDirectory { if (string.IsNullOrEmpty(relativePath)) { - return m_rootOpenRequest || - m_currentDirectoryOpenRequest; + return m_rootOpenRequest || m_currentDirectoryOpenRequest; } - return m_currentDirectoryOpenRequest && - IsAncestorOrSelf(relativePath, m_currentDirectoryOpenTarget); + + return m_currentDirectoryOpenRequest && IsAncestorOrSelf(relativePath, m_currentDirectoryOpenTarget); } - return m_currentDirectoryOpenRequest && - IsAncestorOrSelf(relativePath, m_currentDirectoryOpenTarget); + + return m_currentDirectoryOpenRequest && IsAncestorOrSelf(relativePath, m_currentDirectoryOpenTarget); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserUtility.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserUtility.cs index 1424a36bd..72d39922e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserUtility.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/FileBrowserUtility.cs @@ -1,69 +1,65 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.FileBrowser; - internal static class FileBrowserUtility { + internal static AssetBrowserStyle CurrentBrowserStyle => new(EditorUI.current.widgets.style); + internal readonly record struct BreadcrumbPart( string label, AssetBrowserRoot root, string directory ); - internal static void PushBrowserStyle() { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.assetText); - NativeImGui.PushStyleColor((int)(ImGuiCol.WindowBg), EditorPalette.collectionHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.ChildBg), EditorPalette.collectionHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.Border), EditorPalette.assetBorder); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableHeaderBg), EditorPalette.collectionHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableBorderStrong), EditorPalette.assetBorder); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableBorderLight), EditorPalette.assetBorderSoft); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableRowBg), EditorPalette.collectionRow); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableRowBgAlt), EditorPalette.collectionRowAlternate); - NativeImGui.PushStyleColor((int)(ImGuiCol.Header), EditorPalette.assetAccent); - NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderHovered), EditorPalette.assetAccent); - NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderActive), EditorPalette.assetAccent); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.WindowPadding), EditorWidget.style.assetWindowPadding); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.ChildBorderSize), EditorWidget.style.borderSize); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.CellPadding), EditorWidget.style.assetCellPadding); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.ItemSpacing), EditorWidget.style.assetItemSpacing); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameRounding), EditorWidget.style.assetFrameRounding); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.contentText); + Ui.PushStyleColor((ImGuiCol.WindowBg), EditorPalette.collectionHeader); + Ui.PushStyleColor((ImGuiCol.ChildBg), EditorPalette.collectionHeader); + Ui.PushStyleColor((ImGuiCol.Border), EditorPalette.contentBorder); + Ui.PushStyleColor((ImGuiCol.TableHeaderBg), EditorPalette.collectionHeader); + Ui.PushStyleColor((ImGuiCol.TableBorderStrong), EditorPalette.contentBorder); + Ui.PushStyleColor((ImGuiCol.TableBorderLight), EditorPalette.contentBorderSoft); + Ui.PushStyleColor((ImGuiCol.TableRowBg), EditorPalette.collectionRow); + Ui.PushStyleColor((ImGuiCol.TableRowBgAlt), EditorPalette.collectionRowAlternate); + Ui.PushStyleColor((ImGuiCol.Header), EditorPalette.contentAccent); + Ui.PushStyleColor((ImGuiCol.HeaderHovered), EditorPalette.contentAccent); + Ui.PushStyleColor((ImGuiCol.HeaderActive), EditorPalette.contentAccent); + Ui.PushStyleVar((ImGuiStyleVar.WindowPadding), CurrentBrowserStyle.windowPadding); + Ui.PushStyleVar((ImGuiStyleVar.ChildBorderSize), EditorUI.current.widgets.style.borderSize); + Ui.PushStyleVar((ImGuiStyleVar.CellPadding), CurrentBrowserStyle.cellPadding); + Ui.PushStyleVar((ImGuiStyleVar.ItemSpacing), CurrentBrowserStyle.itemSpacing); + Ui.PushStyleVar((ImGuiStyleVar.FrameRounding), CurrentBrowserStyle.frameRounding); } internal static void PopBrowserStyle() { - NativeImGui.PopStyleVar(5); - NativeImGui.PopStyleColor(12); + Ui.PopStyleVar(5); + Ui.PopStyleColor(12); } internal static void PushButtonColors(Vector4 color) { - NativeImGui.PushStyleColor((int)(ImGuiCol.Button), color); - NativeImGui.PushStyleColor((int)(ImGuiCol.ButtonHovered), EditorPalette.GetHovered(color)); - NativeImGui.PushStyleColor((int)(ImGuiCol.ButtonActive), EditorPalette.GetActive(color)); + Ui.PushStyleColor((ImGuiCol.Button), color); + Ui.PushStyleColor((ImGuiCol.ButtonHovered), EditorPalette.GetHovered(color)); + Ui.PushStyleColor((ImGuiCol.ButtonActive), EditorPalette.GetActive(color)); } internal static bool IsDirectoryPath( AssetPipeline assets, string relativePath - ) - => assets.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry) - && entry.isDirectory; - + ) => assets.TryGetFileSystemEntry(AssetPath.Parse(relativePath), out AssetFileEntry entry) && entry.isDirectory; internal static string GetDirectoryLabel( AssetBrowserRoot root, string relativePath @@ -104,6 +100,7 @@ string currentDirectory result = string.IsNullOrEmpty(directory) ? "~" : $"~/{directory}"; } } + return entry.isReadOnly ? $"{result} (read-only)" : result; } @@ -113,9 +110,7 @@ internal static string GetTypeText(AssetFileEntry entry) return AssetSample.fileType.TrimStart('.').ToUpperInvariant(); if (entry.isDirectory) return "FOLDER"; - string extension = string.IsNullOrEmpty(entry.extension) - ? Path.GetExtension(entry.assetPath.localPath) - : entry.extension; + string extension = string.IsNullOrEmpty(entry.extension) ? Path.GetExtension(entry.assetPath.localPath) : entry.extension; return string.IsNullOrEmpty(extension) ? "FILE" : extension.TrimStart('.').ToUpperInvariant(); } @@ -124,25 +119,20 @@ internal static IReadOnlyList BuildBreadcrumbParts( string relativePath ) { if (root == AssetBrowserRoot.Plugins && string.IsNullOrEmpty(relativePath)) - return [new BreadcrumbPart("Plugins", AssetBrowserRoot.Plugins, string.Empty)]; + return[new BreadcrumbPart("Plugins", AssetBrowserRoot.Plugins, string.Empty)]; AssetPath sourcePath = AssetPath.Parse(NormalizePath(relativePath)); string sourceRoot = new AssetPath(sourcePath.source, string.Empty).ToString(); var parts = new List(); if (root == AssetBrowserRoot.Plugins) { parts.Add(new BreadcrumbPart("Plugins", AssetBrowserRoot.Plugins, string.Empty)); - parts.Add(new BreadcrumbPart( - GetSourceRootLabel(sourcePath.source), - AssetBrowserRoot.Plugins, - sourceRoot)); + parts.Add(new BreadcrumbPart(GetSourceRootLabel(sourcePath.source), AssetBrowserRoot.Plugins, sourceRoot)); } else { - parts.Add(new BreadcrumbPart( - "Assets", - AssetBrowserRoot.Assets, - sourceRoot)); + parts.Add(new BreadcrumbPart("Assets", AssetBrowserRoot.Assets, sourceRoot)); } + if (string.IsNullOrEmpty(sourcePath.localPath)) return parts; string[] segments = sourcePath.localPath.Split('/', StringSplitOptions.RemoveEmptyEntries); @@ -150,24 +140,17 @@ string relativePath for (int i = 0; i < segments.Length; i++) { path = string.IsNullOrEmpty(path) ? segments[i] : $"{path}/{segments[i]}"; - parts.Add(new BreadcrumbPart( - segments[i], - root, - new AssetPath(sourcePath.source, path).ToString())); + parts.Add(new BreadcrumbPart(segments[i], root, new AssetPath(sourcePath.source, path).ToString())); } + return parts; } internal static bool IsReadOnlyLocation( AssetPipeline assets, AssetBrowserState browser - ) - => browser.root == AssetBrowserRoot.Plugins || - IsReadOnlySource(assets, browser.currentDirectory); - - internal static string NormalizePath(string? path) - => AssetPath.Parse(string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/').Trim()).ToString(); - + ) => browser.root == AssetBrowserRoot.Plugins || IsReadOnlySource(assets, browser.currentDirectory); + internal static string NormalizePath(string? path) => AssetPath.Parse(string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/').Trim()).ToString(); internal static string GetParentDirectory(string relativePath) { AssetPath path = AssetPath.Parse(NormalizePath(relativePath)); @@ -185,6 +168,7 @@ string relativePath if (mount.id == source) return mount.isReadOnly; } + return true; } @@ -207,9 +191,7 @@ bool isDirectory if (isDirectory) return name; string extension = Path.GetExtension(name); - return string.IsNullOrEmpty(extension) - ? name - : name[..^extension.Length]; + return string.IsNullOrEmpty(extension) ? name : name[..^extension.Length]; } /// @@ -231,11 +213,7 @@ internal static string ComposeRenamedEntryName( string sourcePath, string editedName, bool isDirectory - ) - => isDirectory - ? editedName - : editedName + Path.GetExtension(sourcePath); - + ) => isDirectory ? editedName : editedName + Path.GetExtension(sourcePath); internal static string[] FitTextToLines( string text, float maxWidth, @@ -243,22 +221,22 @@ int maxLines ) { ArgumentOutOfRangeException.ThrowIfLessThan(maxLines, 1); if (string.IsNullOrEmpty(text)) - return [string.Empty]; + return[string.Empty]; List elements = []; TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(text); while (enumerator.MoveNext()) elements.Add(enumerator.GetTextElement()); - List lines = new(maxLines); int offset = 0; for (int lineIndex = 0; lineIndex < maxLines && offset < elements.Count; lineIndex++) { string remaining = string.Concat(elements.GetRange(offset, elements.Count - offset)); - if (NativeImGui.CalcTextSize(remaining).X <= maxWidth) + if (Ui.MeasureText(remaining).X <= maxWidth) { lines.Add(remaining); break; } + bool isLastLine = lineIndex == maxLines - 1; string suffix = isLastLine ? "..." : string.Empty; int count = 0; @@ -266,20 +244,23 @@ int maxLines while (offset + count < elements.Count) { string next = candidate + elements[offset + count]; - if (NativeImGui.CalcTextSize(next + suffix).X > maxWidth) + if (Ui.MeasureText(next + suffix).X > maxWidth) break; candidate = next; count++; } + if (count == 0) { - lines.Add(isLastLine && NativeImGui.CalcTextSize("...").X <= maxWidth ? "..." : elements[offset]); + lines.Add(isLastLine && Ui.MeasureText("...").X <= maxWidth ? "..." : elements[offset]); offset++; continue; } + lines.Add(candidate + suffix); offset += count; } + return lines.Count == 0 ? [string.Empty] : lines.ToArray(); } @@ -293,20 +274,20 @@ string path return false; if (ancestor.localPath.Length == 0 || string.Equals(ancestor.localPath, descendant.localPath, StringComparison.Ordinal)) return true; - return descendant.localPath.Length > ancestor.localPath.Length && - descendant.localPath.StartsWith(ancestor.localPath, StringComparison.Ordinal) && - descendant.localPath[ancestor.localPath.Length] == '/'; + return descendant.localPath.Length > ancestor.localPath.Length + && descendant.localPath.StartsWith( + ancestor.localPath, + StringComparison.Ordinal + ) + && descendant.localPath[ancestor.localPath.Length] == '/'; } private static string GetSourceRootLabel(AssetSourceId source) => source == AssetSourceId.project ? "Assets" : source.value; - private static string FormatSourcePath( AssetSourceId source, string localPath ) { - string root = source == AssetSourceId.project - ? GetSourceRootLabel(source) - : $"Plugins/{GetSourceRootLabel(source)}"; + string root = source == AssetSourceId.project ? GetSourceRootLabel(source) : $"Plugins/{GetSourceRootLabel(source)}"; return string.IsNullOrEmpty(localPath) ? root : $"{root}/{localPath}"; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/FileBrowserInteractionIds.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/FileBrowserInteractionIds.cs index d837d0d34..9a7526c75 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/FileBrowserInteractionIds.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/FileBrowserInteractionIds.cs @@ -1,5 +1,7 @@ -namespace Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; +namespace Inno.Editor.Panel.FileBrowser; internal static class FileBrowserInteractionIds { internal const string C_AREA = "panel/asset.file-browser"; diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj index 10dbd4e71..8fb3f1430 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Inno.Editor.Panel.FileBrowser.csproj @@ -4,47 +4,33 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetCommand.cs index 746fdefb0..ffb94a2c9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetCommand.cs @@ -5,12 +5,12 @@ using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Editor.Interactions; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_CREATE_ASSET, FileBrowserInteractionIds.C_AREA)] -internal sealed class CreateAssetCommand(AssetEditorModule assets) : EditorAction +internal sealed class CreateAssetCommand(AssetBrowserModule assets) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -21,27 +21,27 @@ internal sealed class CreateAssetCommand(AssetEditorModule assets) : EditorActio /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) - => assets.CanCreateAsset(context.target, context.argument) - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => assets.edits.CanCreateAsset( + context.target, + context.argument + ) ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { - AssetFileEntry created = assets.CreateAsset(context.target, context.argument); - assets.BeginCreatedSourceRename(created); + AssetFileEntry created = assets.edits.CreateAsset(context.target, context.argument); + _ = context.interactions.For(FileBrowserInteractionIds.C_AREA, created).Select(); + _ = context.interactions.For(FileBrowserInteractionIds.C_AREA, created).Execute(FileBrowserInteractionIds.C_RENAME); context.interactions.OpenPanel("scene.inspector"); } } [EditorMenuSource(FileBrowserInteractionIds.C_AREA)] -internal sealed class AssetCreationMenu(AssetEditorModule assets) : EditorMenuSource +internal sealed class AssetCreationMenu(AssetBrowserModule assets) : EditorMenuSource { /// /// Builds a validated result from the current immutable input snapshot. @@ -52,26 +52,23 @@ internal sealed class AssetCreationMenu(AssetEditorModule assets) : EditorMenuSo /// /// The builder consumed by build; ownership remains with the caller unless explicitly stated otherwise. /// -public override void Build( - EditorMenuContext context, - EditorMenuBuilder builder -) { - IReadOnlyList templates = assets.creationTemplates; + public override void Build( + EditorMenuContext context, + EditorMenuBuilder builder + ) { + IReadOnlyList templates = assets.edits.creationTemplates; var declaredGroups = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (AssetCreationRegistry.Registration registration in templates) + foreach (AssetCreationMenuAttribute declaration in templates) { - AssetCreationMenuAttribute declaration = registration.declaration; string[] segments = declaration.menuPath.Split('/'); if (segments.Length > 1) { string topLevel = "Create/" + segments[0]; if (declaredGroups.Add(topLevel)) { - builder.AddGroup( - topLevel, - declaration.groupOrder, - declaration.separatorBeforeGroup); + builder.AddGroup(topLevel, declaration.groupOrder, declaration.separatorBeforeGroup); } + for (int depth = 1; depth < segments.Length - 1; depth++) { string group = "Create/" + string.Join('/', segments.Take(depth + 1)); @@ -80,11 +77,7 @@ EditorMenuBuilder builder } } - builder.Add( - "Create/" + declaration.menuPath, - FileBrowserInteractionIds.C_CREATE_ASSET, - declaration.itemOrder, - argument: declaration.id); + builder.Add("Create/" + declaration.menuPath, FileBrowserInteractionIds.C_CREATE_ASSET, declaration.itemOrder, argument: declaration.id); } } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetFolderCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetFolderCommand.cs index 7d0e6c338..c63999f5d 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetFolderCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/CreateAssetFolderCommand.cs @@ -1,15 +1,15 @@ +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; using System.Linq; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_CREATE_FOLDER, FileBrowserInteractionIds.C_AREA)] [EditorMenu(FileBrowserInteractionIds.C_AREA, "Create/Folder", order: 100)] -internal sealed class CreateAssetFolderCommand(AssetEditorModule assets) : EditorAction +internal sealed class CreateAssetFolderCommand(AssetBrowserModule assets) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -20,11 +20,8 @@ internal sealed class CreateAssetFolderCommand(AssetEditorModule assets) : Edito /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => assets.pipeline.isInitialized && IsWritableDirectory(context.target) - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => assets.edits.pipeline.isInitialized + && IsWritableDirectory(context.target) ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -33,43 +30,7 @@ protected override EditorActionState Query(EditorActionContext context) /// protected override void Execute(EditorActionContext context) { - string parent = Normalize(context.target); - string candidate = Combine(parent, "New Folder"); - int suffix = 1; - while (assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(candidate), out _)) - candidate = Combine(parent, $"New Folder {suffix++}"); - assets.pipeline.CreateDirectory(AssetPath.Parse(candidate)); - var data = new AssetHistoryData( - AssetHistoryOperationKind.CreateDirectory, - candidate, - string.Empty, - isDirectory: true, - archive: []); - try - { - context.history.RecordApplied( - "Create Folder", - new EditorHistoryChange( - AssetHistoryKinds.SourceOperation, - EditorHistoryPayload.FromBytes(data.Encode()))); - } - catch (Exception failure) - { - try - { - assets.pipeline.Delete(AssetPath.Parse(candidate)); - } - catch (Exception rollbackException) - { - throw new AggregateException( - "The folder could not be recorded and its compensation also failed.", - failure, - rollbackException); - } - throw; - } - if (!assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(candidate), out AssetFileEntry target)) - return; + AssetFileEntry target = assets.edits.CreateDirectory(AssetPath.Parse(context.target)); EditorInteraction interaction = context.interactions.For(FileBrowserInteractionIds.C_AREA, target); _ = interaction.Select(); _ = interaction.Execute(FileBrowserInteractionIds.C_RENAME); @@ -79,21 +40,19 @@ private static string Combine( string parent, string name ) => string.IsNullOrEmpty(parent) ? name : $"{parent}/{name}"; - - private static string Normalize(string? path) - => string.IsNullOrWhiteSpace(path) - ? string.Empty - : path.Replace('\\', '/').Trim('/'); - + private static string Normalize(string? path) => string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/').Trim('/'); private bool IsWritableDirectory(string relativePath) { string normalized = Normalize(relativePath); AssetSourceId source = AssetPath.Parse(normalized).source; - AssetSourceMount? mount = assets.pipeline.sourceMounts.FirstOrDefault(candidate => candidate.id == source); + AssetSourceMount? mount = assets.edits.pipeline.sourceMounts.FirstOrDefault(candidate => candidate.id == source); if (mount is null || mount.isReadOnly) return false; - return string.IsNullOrEmpty(AssetPath.Parse(normalized).localPath) || - assets.pipeline.TryGetFileSystemEntry(AssetPath.Parse(normalized), out AssetFileEntry entry) - && entry.isDirectory; + return string.IsNullOrEmpty(AssetPath.Parse(normalized).localPath) + || assets.edits.pipeline.TryGetFileSystemEntry( + AssetPath.Parse(normalized), + out AssetFileEntry entry + ) + && entry.isDirectory; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/DeleteAssetCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/DeleteAssetCommand.cs index 5b3e2722e..eff199328 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/DeleteAssetCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/DeleteAssetCommand.cs @@ -1,13 +1,15 @@ +using System.Collections.Generic; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; using Inno.Core.Input; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_DELETE, priority: 100)] [EditorMenu(FileBrowserInteractionIds.C_AREA, "Delete", order: 200)] [EditorShortcut(FileBrowserInteractionIds.C_AREA, KeyCode.Delete)] -internal sealed class DeleteAssetCommand(AssetEditorModule assets) : EditorAction +internal sealed class DeleteAssetCommand(AssetBrowserModule assets) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -19,9 +21,10 @@ internal sealed class DeleteAssetCommand(AssetEditorModule assets) : EditorActio /// The validated editor action state that represents the completed operation. /// protected override EditorActionState Query(EditorActionContext context) - => !context.target.isReadOnly && TryGetAssetContext(context, out _) - ? EditorActionState.enabled - : EditorActionState.hidden; + { + AssetOperationValidation validation = assets.edits.ValidateDeleteSelection(context.editor, Targets(context)); + return validation.isValid ? EditorActionState.enabled : new EditorActionState(true, false, unavailableReason: validation.message); + } /// /// Executes the prepared operation and publishes only a completed result. @@ -31,13 +34,16 @@ protected override EditorActionState Query(EditorActionContext c /// protected override void Execute(EditorActionContext context) { - if (TryGetAssetContext(context, out AssetEditorContext? assetContext) && assetContext is not null) - _ = assets.DeleteWithHistory(context, assetContext); + assets.edits.DeleteSelection(context.editor, Targets(context)); + context.interactions.SetSelection((object? )null); } - private bool TryGetAssetContext( - EditorActionContext context, - out AssetEditorContext? assetContext - ) - => assets.TryCreateContext(context.editor, context.target.assetPath.ToString(), out assetContext); + private static IReadOnlyList Targets(EditorActionContext context) + { + if (!context.interactions.selection.Contains(context.target)) + return[context.target]; + var values = new List(); + context.interactions.selection.CopyTargets(values); + return values.Count == context.interactions.selection.count ? values : []; + } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/ImportAssetSampleCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/ImportAssetSampleCommand.cs index 471b7607b..fc4f2cc11 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/ImportAssetSampleCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/ImportAssetSampleCommand.cs @@ -1,15 +1,15 @@ +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_IMPORT_SAMPLE, FileBrowserInteractionIds.C_AREA)] [EditorMenu(FileBrowserInteractionIds.C_AREA, "Import Sample", order: 120)] internal sealed class ImportAssetSampleCommand( - AssetEditorModule assets, + AssetBrowserModule assets, AssetSampleImportModule imports ) : EditorAction { @@ -31,9 +31,7 @@ protected override EditorActionState Query(EditorActionContext c try { AssetPath target = AssetPath.Project(AssetSample.GetImportName(context.target.assetPath)); - return assets.pipeline.TryGetFileSystemEntry(target, out _) - ? EditorActionState.disabled - : EditorActionState.enabled; + return assets.edits.pipeline.TryGetFileSystemEntry(target, out _) ? EditorActionState.disabled : EditorActionState.enabled; } catch (ArgumentException) { @@ -47,6 +45,5 @@ protected override EditorActionState Query(EditorActionContext c /// /// The action context containing the selected sample and history transaction. /// - protected override void Execute(EditorActionContext context) - => imports.Request(context.target.assetPath); + protected override void Execute(EditorActionContext context) => imports.Request(context.target.assetPath); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/MoveAssetEntryDropHandler.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/MoveAssetEntryDropHandler.cs index dd286f2b0..ac60f5765 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/MoveAssetEntryDropHandler.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/MoveAssetEntryDropHandler.cs @@ -1,8 +1,10 @@ +using System.Collections.Generic; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; namespace Inno.Editor.Panel.FileBrowser; - /// /// Moves directory drag payloads into File Browser directory targets. /// @@ -10,8 +12,7 @@ namespace Inno.Editor.Panel.FileBrowser; /// The assets used to initialize this instance. /// [EditorDrop(FileBrowserInteractionIds.C_AREA, priority: 200)] -internal sealed class MoveAssetEntryDropHandler(AssetEditorModule assets) - : EditorDrop +internal sealed class MoveAssetEntryDropHandler(AssetBrowserModule assets) : EditorDrop { /// /// Evaluates the operation's current availability and presentation state. @@ -22,11 +23,10 @@ internal sealed class MoveAssetEntryDropHandler(AssetEditorModule assets) /// /// The context that supplies state and services for this operation. /// - protected override EditorDropStatus Query(EditorDropContext context) - => assets.CanMoveToDirectory(context.source.assetPath.ToString(), context.target) - ? EditorDropStatus.Accept() - : EditorDropStatus.rejected; - + protected override EditorDropStatus Query(EditorDropContext context) => assets.edits.CanMoveSelection( + SelectedSources(context), + context.target + ) ? EditorDropStatus.Accept() : EditorDropStatus.rejected; /// /// Validates and applies the current editor drag-and-drop interaction atomically. /// @@ -40,10 +40,16 @@ protected override EditorDropResult Drop(EditorDropContext SelectedSources(EditorDropContext context) + { + if (!context.interactions.selection.Contains(context.source)) + return[context.source]; + var values = new List(); + context.interactions.selection.CopyTargets(values); + return values.Count == context.interactions.selection.count ? values : []; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/OpenAssetCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/OpenAssetCommand.cs index 4732a5d59..98c547ef0 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/OpenAssetCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/OpenAssetCommand.cs @@ -1,11 +1,12 @@ +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Interactions; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_OPEN, FileBrowserInteractionIds.C_AREA, priority: 100)] -internal sealed class OpenAssetCommand(AssetEditorModule assets) : EditorAction +internal sealed class OpenAssetCommand(AssetBrowserModule assets) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -20,9 +21,7 @@ protected override EditorActionState Query(EditorActionContext c { if (!TryGetAssetContext(context, out AssetEditorContext? assetContext) || assetContext is null) return EditorActionState.hidden; - return assetContext.isDirectory - ? EditorActionState.hidden - : EditorActionState.enabled; + return assetContext.isDirectory ? EditorActionState.hidden : EditorActionState.enabled; } /// @@ -35,20 +34,27 @@ protected override void Execute(EditorActionContext context) { if (!TryGetAssetContext(context, out AssetEditorContext? assetContext) || assetContext is null) return; - if (assets.pipeline.TryLoad(AssetPath.Parse(assetContext.relativePath), out AssetObject? asset) && - asset is not null && - context.interactions.For(FileBrowserInteractionIds.C_AREA, asset).Execute( - FileBrowserInteractionIds.C_OPEN, - assetContext.relativePath)) + if (assets.edits.pipeline.TryLoad( + AssetPath.Parse(assetContext.relativePath), + out AssetObject? asset + ) + && asset is not null + && context.interactions.For( + FileBrowserInteractionIds.C_AREA, + asset + ).Execute( + FileBrowserInteractionIds.C_OPEN, + assetContext.relativePath + )) { return; } - _ = assets.Open(assetContext); + + _ = assets.edits.Open(assetContext); } private bool TryGetAssetContext( EditorActionContext context, out AssetEditorContext? assetContext - ) - => assets.TryCreateContext(context.editor, context.target.assetPath.ToString(), out assetContext); + ) => assets.edits.TryCreateContext(context.editor, context.target.assetPath.ToString(), out assetContext); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/RenameAssetCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/RenameAssetCommand.cs index 027a8c82f..0eeae9549 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/RenameAssetCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Interactions/RenameAssetCommand.cs @@ -1,31 +1,26 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System; - using Inno.Assets.Pipeline; using Inno.Core.Input; using Inno.Core.Logging; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Interactions; - -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using static Inno.Editor.Panel.FileBrowser.FileBrowserUtility; namespace Inno.Editor.Panel.FileBrowser; - [EditorAction(FileBrowserInteractionIds.C_RENAME, priority: 100)] [EditorMenu(FileBrowserInteractionIds.C_AREA, "Rename", order: 100)] [EditorShortcut(FileBrowserInteractionIds.C_AREA, KeyCode.F2)] internal sealed class RenameAssetCommand( - AssetEditorModule assets, + AssetBrowserModule assets, LogRouter logs -) : - EditorPresentationAction +) : EditorPresentationAction { - private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))) - .CreateLogger(); - private AssetEditorContext? m_asset; + private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))).CreateLogger(); private string m_buffer = string.Empty; private bool m_requestFocus; - /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -35,11 +30,11 @@ LogRouter logs /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => !context.target.isReadOnly && TryGetAssetContext(context, out _) - ? EditorActionState.enabled - : EditorActionState.hidden; - + protected override EditorActionState Query(EditorActionContext context) => !context.target.isReadOnly + && TryGetAssetContext( + context, + out _ + ) ? EditorActionState.enabled : EditorActionState.hidden; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -51,7 +46,6 @@ protected override void Execute(EditorActionContext context) if (!TryGetAssetContext(context, out AssetEditorContext? assetContext) || assetContext is null) return; Activate(context); - m_asset = assetContext; m_buffer = GetEditableName(assetContext.name, assetContext.isDirectory); m_requestFocus = true; } @@ -67,27 +61,29 @@ protected override void Execute(EditorActionContext context) /// protected override bool Present(EditorActionContext context) { - if (m_asset is null) + if (!TryResolveAsset(out AssetEditorContext? asset) || asset is null) return false; InlineRenamePresentation presentation = context.argument; - - InlineRenameResult result = EditorWidget.InlineRename( + InlineRenameResult result = EditorUI.current.widgets.InlineRename( presentation.id, ref m_buffer, ref m_requestFocus, presentation.rowHeight, presentation.bufferSize, - presentation.width); + presentation.width + ); if (result == InlineRenameResult.Cancel) { Cancel(); return true; } + if (result == InlineRenameResult.FocusLost) { _ = TryCommit(keepActiveWhenInvalid: false); return true; } + if (result != InlineRenameResult.Commit) return true; _ = TryCommit(keepActiveWhenInvalid: true); @@ -98,26 +94,23 @@ protected override bool Present(EditorActionContext protected override void OnCompleted() => ClearState(); - /// /// Cancels the interaction without committing its pending value. /// protected override void OnCancelled() => ClearState(); - /// /// Cancels pending presentation state when its editor surface disappears. /// protected override void OnPresentationLost() => _ = TryCommit(keepActiveWhenInvalid: false); - private bool TryCommit(bool keepActiveWhenInvalid) { - if (m_asset is null) + if (!TryResolveAsset(out AssetEditorContext? asset) || asset is null) { Cancel(); return false; } - EditorValidationResult validation = assets.ValidateRename(m_asset, m_buffer); + EditorValidationResult validation = assets.edits.ValidateRename(asset, m_buffer); if (!validation.isValid) { m_log.Write(LogLevel.Warn, "Asset rename was rejected: {0}", [validation.message]); @@ -128,16 +121,20 @@ private bool TryCommit(bool keepActiveWhenInvalid) return false; } - AssetEditorContext asset = m_asset; string name = m_buffer; + assets.edits.Rename(asset, name); Complete(); - assets.Rename(asset, name); return true; } + private bool TryResolveAsset(out AssetEditorContext? asset) + { + asset = null; + return activeTarget is AssetFileEntry entry && activeEditorContext is { } editor && assets.edits.TryCreateContext(editor, entry.assetPath.ToString(), out asset); + } + private void ClearState() { - m_asset = null; m_buffer = string.Empty; m_requestFocus = false; } @@ -145,6 +142,5 @@ private void ClearState() private bool TryGetAssetContext( EditorActionContext context, out AssetEditorContext? assetContext - ) - => assets.TryCreateContext(context.editor, context.target.assetPath.ToString(), out assetContext); + ) => assets.edits.TryCreateContext(context.editor, context.target.assetPath.ToString(), out assetContext); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Properties/ScriptingApi.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Properties/ScriptingApi.cs deleted file mode 100644 index d4fa16c72..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Properties/ScriptingApi.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Inno.Scripting.Api; -using Inno.Assets.Pipeline; -using Inno.Editor.Panel.FileBrowser; - -[assembly: ScriptingApiNamespace( - "InnoEditor.Assets", - "Inno.Editor.Panel.FileBrowser", - ScriptingApiScope.Editor)] - -[assembly: ScriptingApiExport(typeof(AssetEditor), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetFileEntry), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetEditorAttribute), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetEditorContext), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetOperationValidation), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetBrowserRoot), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetBrowserState), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetIconAttribute), ScriptingApiScope.Editor)] -[assembly: ScriptingApiExport(typeof(AssetImportSettingsEdits), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModal.cs b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModal.cs index 30889751a..cdcae798d 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModal.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Samples/AssetSampleImportModal.cs @@ -1,10 +1,10 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using System.Numerics; - using Inno.Editor.Core; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; namespace Inno.Editor.Panel.FileBrowser; - [EditorModal("asset-sample-import.progress", "Importing Sample", order: 110)] internal sealed class AssetSampleImportModal(AssetSampleImportModule imports) : EditorModal { @@ -12,7 +12,6 @@ internal sealed class AssetSampleImportModal(AssetSampleImportModule imports) : /// Shows progress while the import owns pending background work. /// public override bool isVisible => imports.isBusy; - /// /// Blocks unrelated interaction until import publication or cancellation completes. /// @@ -26,9 +25,9 @@ internal sealed class AssetSampleImportModal(AssetSampleImportModule imports) : /// protected override void OnDraw(EditorContext context) { - EditorWidget.WrappedText(imports.status); - EditorWidget.CenteredProgressBar(imports.progress, new Vector2(-1f, 0f), $"{imports.progress:P0}"); - if (EditorWidget.CenteredButton("Cancel", EditorWidget.style.itemSpacing.Y)) + EditorUI.current.widgets.WrappedText(imports.status); + EditorUI.current.widgets.CenteredProgressBar(imports.progress, new Vector2(-1f, 0f), $"{imports.progress:P0}"); + if (EditorUI.current.widgets.CenteredButton("Cancel", EditorUI.current.widgets.style.itemSpacing.Y)) imports.Cancel(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.GameView/GamePresentationSetting.cs b/src/composition/editor/panels/Inno.Editor.Panel.GameView/GamePresentationSetting.cs index 58acca79e..ba83d4480 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.GameView/GamePresentationSetting.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.GameView/GamePresentationSetting.cs @@ -1,23 +1,17 @@ using System; - using Inno.Core.Settings; using Inno.Editor.Settings; using Inno.Runtime; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.GameView; - [ProjectSettingPath("Project/Player/Presentation")] internal sealed class GamePresentationSetting : ProjectSettingEditor { - /// - /// Gets the stable project-setting identity edited by this presentation. - /// /// /// Gets the section that groups Player presentation controls. /// public override string section => "Display"; - /// /// Gets the explanation shared by Game View and deployed Player presentation. /// @@ -32,22 +26,21 @@ internal sealed class GamePresentationSetting : ProjectSettingEditor /// Gets a new value initialized to this setting's canonical default state. /// public override EditorSettingObject defaultValue => CreateDefault(); - /// /// Gets the presentation section that groups this setting. /// public override string section => "Viewport Backgrounds"; - /// /// Gets the user-facing explanation of this feature or setting. /// @@ -37,13 +31,12 @@ internal sealed class GameViewBackgroundSetting : EditorSetting protected override void OnDraw(EditorSettingObject setting) { Vector4 value = ReadVector(setting); - NativeImGui.SetNextItemWidth(-1f); - if (EditorImGui.ColorEditLinear4("##game_view_background", ref value)) + Ui.SetNextItemWidth(-1f); + if (Ui.ColorEditLinear4("##game_view_background", ref value)) setting.SetAsSingleArray("value", [value.X, value.Y, value.Z, value.W]); } internal static Vector4 Read(EditorSettings settings) => ReadVector(settings.Get(C_PATH)); - private static EditorSettingObject CreateDefault() { var result = new EditorSettingObject(); @@ -54,8 +47,6 @@ private static EditorSettingObject CreateDefault() private static Vector4 ReadVector(EditorSettingObject setting) { float[] values = setting.GetAsSingleArray("value", S_DEFAULT); - return values.Length == 4 - ? new Vector4(values[0], values[1], values[2], values[3]) - : new Vector4(S_DEFAULT[0], S_DEFAULT[1], S_DEFAULT[2], S_DEFAULT[3]); + return values.Length == 4 ? new Vector4(values[0], values[1], values[2], values[3]) : new Vector4(S_DEFAULT[0], S_DEFAULT[1], S_DEFAULT[2], S_DEFAULT[3]); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.GameView/GameViewPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.GameView/GameViewPanel.cs index 3fd3884f0..00544e39b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.GameView/GameViewPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.GameView/GameViewPanel.cs @@ -1,9 +1,8 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using Inno.References; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Core.Settings; using Inno.Core.Input; using Inno.Editor.Core; @@ -14,23 +13,20 @@ using Inno.Editor.Settings; using Inno.Rendering; using Inno.Runtime; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using Ui = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Runtime; namespace Inno.Editor.Panel.GameView; - /// /// Presents the active Plugin provider for the open Game viewport purpose. /// [EditorPanel("rendering.game-view", "Game", order: 220, menuPath: "Viewports")] -internal sealed unsafe class GameViewPanel : EditorPanel +internal sealed class GameViewPanel : EditorPanel { + private IDisposable? m_settingsSubscription; private const string C_VIEWPORT_ID = "game-view"; private static readonly EditorViewportKindId S_KIND = new("inno.editor.viewport.game"); private static readonly Vector2 S_UNAVAILABLE_PADDING = new(48f, 32f); - private readonly EditorRenderingModule m_rendering; private readonly IEditorGameScenePresentation m_scenePresentation; private readonly IEditorPlayMode m_playMode; @@ -42,7 +38,6 @@ internal sealed unsafe class GameViewPanel : EditorPanel private GamePresentationSettings m_presentation = new(); private long m_presentationRevision = -1; private bool m_leftPointerActive; - internal GameViewPanel( EditorRenderingModule rendering, IEditorGameScenePresentation scenePresentation, @@ -65,7 +60,6 @@ ProjectSettingsStore projectSettings /// Gets whether use window padding is enabled for this implementation. /// public override bool useWindowPadding => false; - /// /// Gets whether allow scrolling is enabled for this implementation. /// @@ -81,101 +75,85 @@ protected override void OnDraw(EditorContext context) { using IDisposable presentationScope = m_playModeLoop.EnterPresentationScope(); _ = context; - Vector2 available = NativeImGui.GetContentRegionAvail(); + Vector2 available = Ui.GetContentRegionAvailable(); if (available.X <= 0f || available.Y <= 0f) return; RefreshPresentationSettings(); - float framebufferScale = NativeImGui.Raw.GetIO()->DisplayFramebufferScale.X; + float framebufferScale = Ui.GetInput().framebufferScale.X; if (!float.IsFinite(framebufferScale) || framebufferScale <= 0f) framebufferScale = 1f; GameViewportLayout layout = CalculateLayout(available, m_presentation, framebufferScale); m_rendering.SetPresentation( C_VIEWPORT_ID, - new EditorViewportPresentation(new Inno.Core.Mathematics.Color( - m_backgroundColor.X, - m_backgroundColor.Y, - m_backgroundColor.Z, - m_backgroundColor.W), framebufferScale)); + new EditorViewportPresentation(new Inno.Core.Mathematics.Color(m_backgroundColor.X, m_backgroundColor.Y, m_backgroundColor.Z, m_backgroundColor.W), framebufferScale) + ); m_rendering.SetContentScope(C_VIEWPORT_ID, CreateContentScope()); - Vector2 outputOrigin = NativeImGui.GetCursorScreenPos() + layout.offset; - Vector2 mouse = NativeImGui.GetMousePos(); + Vector2 outputOrigin = Ui.GetCursorScreenPosition() + layout.offset; + Vector2 mouse = Ui.GetMousePos(); Vector2 local = mouse - outputOrigin; - bool inside = local.X >= 0f && local.Y >= 0f - && local.X < layout.size.X && local.Y < layout.size.Y; - ImGuiViewport* viewport = NativeImGui.Raw.GetWindowViewport(); - bool gameViewFocused = m_playMode.isPlaying - && NativeImGui.IsWindowFocused((int)ImGuiFocusedFlags.RootAndChildWindows); - bool imageHovered = inside - && NativeImGui.IsWindowHovered((int)ImGuiHoveredFlags.RootAndChildWindows); - bool focused = m_inputCapture.Report( - viewport->ID, - outputOrigin - new Vector2(viewport->Pos.X, viewport->Pos.Y), - layout.size, - gameViewFocused, - imageHovered); + bool inside = local.X >= 0f && local.Y >= 0f && local.X < layout.size.X && local.Y < layout.size.Y; + var viewport = Ui.GetViewport(); + bool gameViewFocused = m_playMode.isPlaying && Ui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); + bool imageHovered = inside && Ui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows); + bool focused = m_inputCapture.Report(viewport.id, outputOrigin - viewport.position, layout.size, gameViewFocused, imageHovered); bool acceptsPointer = focused && imageHovered; - if (!NativeImGui.IsMouseDown((int)ImGuiMouseButton.Left) - && !NativeImGui.IsMouseReleased((int)ImGuiMouseButton.Left)) + if (!Ui.IsMouseButtonDown(ImGuiMouseButton.Left) && !Ui.IsMouseReleased(ImGuiMouseButton.Left)) { m_leftPointerActive = false; } - bool leftPressed = acceptsPointer && NativeImGui.IsMouseClicked((int)ImGuiMouseButton.Left); + + bool leftPressed = acceptsPointer && Ui.IsMouseClicked(ImGuiMouseButton.Left); if (leftPressed) m_leftPointerActive = true; - bool leftReleased = m_leftPointerActive && NativeImGui.IsMouseReleased((int)ImGuiMouseButton.Left); + bool leftReleased = m_leftPointerActive && Ui.IsMouseReleased(ImGuiMouseButton.Left); if (leftReleased || !focused) m_leftPointerActive = false; - var io = NativeImGui.Raw.GetIO(); + var io = Ui.GetInput(); KeyModifier modifiers = KeyModifier.None; - if (io->KeyAlt != 0) + if (io.alt) modifiers |= KeyModifier.Alt; - if (io->KeyCtrl != 0) + if (io.control) modifiers |= KeyModifier.Control; - if (io->KeyShift != 0) + if (io.shift) modifiers |= KeyModifier.Shift; - if (io->KeySuper != 0) + if (io.super) modifiers |= KeyModifier.Super; - m_rendering.SetOutputInput(C_VIEWPORT_ID, focused ? new RenderOutputInput( - new Inno.Core.Mathematics.Vector2(local.X * layout.pixelWidth / layout.size.X, local.Y * layout.pixelHeight / layout.size.Y), - acceptsPointer, - acceptsPointer ? new Inno.Core.Mathematics.Vector2(0f, io->MouseWheel) : default, - modifiers, - [], [], - leftPressed ? [MouseButton.Left] : [], - leftReleased ? [MouseButton.Left] : [], - []) : RenderOutputInput.suspended); - if (!m_rendering.TrySubmit( - S_KIND, - C_VIEWPORT_ID, - layout.pixelWidth, - layout.pixelHeight, - out EditorViewportOutput output)) + m_rendering.SetOutputInput( + C_VIEWPORT_ID, + focused ? new RenderOutputInput( + new Inno.Core.Mathematics.Vector2(local.X * layout.pixelWidth / layout.size.X, local.Y * layout.pixelHeight / layout.size.Y), + acceptsPointer, + acceptsPointer ? new Inno.Core.Mathematics.Vector2(0f, io.wheel) : default, + modifiers, + [], + [], + leftPressed ? [MouseButton.Left] : [], + leftReleased ? [MouseButton.Left] : [], + [] + ) : RenderOutputInput.suspended + ); + if (!m_rendering.TrySubmit(S_KIND, C_VIEWPORT_ID, layout.pixelWidth, layout.pixelHeight, out EditorViewportOutput output)) { - DrawUnavailable( - available, - m_rendering.GetCompositionError(C_VIEWPORT_ID) ?? "No rendering model contributes to Game View."); + DrawUnavailable(available, m_rendering.GetCompositionError(C_VIEWPORT_ID) ?? "No rendering model contributes to Game View."); return; } + if (!output.isReady) { DrawUnavailable(available, "Preparing Game View GPU target..."); return; } - Vector2 origin = NativeImGui.GetCursorScreenPos(); + Vector2 origin = Ui.GetCursorScreenPosition(); if (m_presentation.preserveAspectRatio) { - EditorImGui.DrawFilledRectangle( - origin, - origin + available, - NativeImGui.ColorConvertFloat4ToU32(new Vector4(0f, 0f, 0f, EditorPalette.opacityOpaque)), - 0 - ); + Ui.DrawFilledRectangle(origin, origin + available, Ui.ToPackedColor(new Vector4(0f, 0f, 0f, EditorPalette.opacityOpaque)), 0); } - NativeImGui.SetCursorScreenPos(origin + layout.offset); + + Ui.SetCursorScreenPosition(origin + layout.offset); m_rendering.Draw(output, layout.size); - NativeImGui.SetCursorScreenPos(origin); - NativeImGui.Dummy(available); + Ui.SetCursorScreenPosition(origin); + Ui.Dummy(available); } /// @@ -189,7 +167,7 @@ protected override void OnAttach(EditorContext context) _ = context; ApplyEditorSettings(m_editorSettings); RefreshPresentationSettings(); - m_editorSettings.changed += ApplyEditorSettings; + m_settingsSubscription = m_editorSettings.ListenChanges(ApplyEditorSettings); } /// @@ -201,14 +179,13 @@ protected override void OnAttach(EditorContext context) protected override void OnDetach(EditorContext context) { _ = context; - m_editorSettings.changed -= ApplyEditorSettings; + m_settingsSubscription?.Dispose(); m_rendering.Release(C_VIEWPORT_ID); m_presentationRevision = -1; m_leftPointerActive = false; } private void ApplyEditorSettings(EditorSettings settings) => m_backgroundColor = GameViewBackgroundSetting.Read(settings); - private void RefreshPresentationSettings() { if (m_presentationRevision == m_projectSettings.revision) @@ -224,37 +201,30 @@ float framebufferScale ) { int availableWidth = Math.Max(1, (int)MathF.Floor(available.X)); int availableHeight = Math.Max(1, (int)MathF.Floor(available.Y)); - GamePresentationViewport viewport = presentation.CalculateViewport( - availableWidth, - availableHeight); + GamePresentationViewport viewport = presentation.CalculateViewport(availableWidth, availableHeight); return new GameViewportLayout( new Vector2(viewport.x, viewport.y), new Vector2(viewport.width, viewport.height), Math.Max(1, (int)MathF.Ceiling(viewport.width * framebufferScale)), - Math.Max(1, (int)MathF.Ceiling(viewport.height * framebufferScale))); + Math.Max(1, (int)MathF.Ceiling(viewport.height * framebufferScale)) + ); } private ContentReadScope CreateContentScope() => m_scenePresentation.Capture(); - private void DrawUnavailable( Vector2 size, string message ) { - Vector2 minimum = NativeImGui.GetCursorScreenPos(); - EditorImGui.DrawFilledRectangle( - minimum, - minimum + size, - NativeImGui.ColorConvertFloat4ToU32(m_backgroundColor), - 0 - ); - NativeImGui.PushStyleColor((int)ImGuiCol.Text, EditorPalette.textDisabled); + Vector2 minimum = Ui.GetCursorScreenPosition(); + Ui.DrawFilledRectangle(minimum, minimum + size, Ui.ToPackedColor(m_backgroundColor), 0); + Ui.PushStyleColor(ImGuiCol.Text, EditorPalette.textDisabled); try { - EditorWidget.CenteredWrappedText(message, size, S_UNAVAILABLE_PADDING); + EditorUI.current.widgets.CenteredWrappedText(message, size, S_UNAVAILABLE_PADDING); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj b/src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj index 0c8ceccf1..73db8d4b7 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.GameView/Inno.Editor.Panel.GameView.csproj @@ -4,38 +4,22 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - - + - - + + - - - + + + @@ -43,5 +27,4 @@ - diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Inno.Editor.Panel.Global.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Global/Inno.Editor.Panel.Global.csproj deleted file mode 100644 index 00c9aa675..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Inno.Editor.Panel.Global.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - net10.0 - 14.0 - disable - enable - true - true - $(WarningsAsErrors);CS1572;CS1573;CS1591 - Editor - - - - - - - - - - - - - - - - - diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/IconSettings.cs b/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/IconSettings.cs deleted file mode 100644 index 4d95d8d33..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/IconSettings.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Runtime.CompilerServices; -using NumericsVector2 = System.Numerics.Vector2; -using NativeVector2 = Inno.Native.ImGui.ImVec2C; -using BGCS.Runtime; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Reflection; - -using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Editor.Settings; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Panel.Global; - -internal abstract unsafe class IconSetting : EditorSetting -{ - private static readonly IReadOnlyList<(string Name, string Glyph)> C_ICONS = - typeof(ImGuiIcon) - .GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(static field => field.IsLiteral && field.FieldType == typeof(string)) - .Select(static field => (field.Name, (string)field.GetRawConstantValue()!)) - .OrderBy(static value => value.Name, StringComparer.Ordinal) - .ToArray(); - - private static int s_iconSlotFrame = -1; - private static float s_iconSlotWidth; - - private readonly string m_glyph; - - /// - /// Creates a validated icon setting instance. - /// - /// - /// The glyph text validated by the icon setting operation. - /// - protected IconSetting(string glyph) - { - ArgumentException.ThrowIfNullOrWhiteSpace(glyph); - m_glyph = glyph; - } - - /// - /// Gets a new value initialized to this setting's canonical default state. - /// - public override EditorSettingObject defaultValue => CreateDefault(m_glyph); - - /// - /// Gets the presentation section that groups this setting. - /// - public override string section => "Editor Icons"; - - /// - /// Renders this feature using the current editor presentation context. - /// - /// - /// The mutable editor setting value currently being presented. - /// - protected override void OnDraw(EditorSettingObject setting) - { - string value = setting.GetAsString("value", m_glyph) ?? m_glyph; - float iconSlotWidth = GetIconSlotWidth(); - ImDrawList* comboDrawList = NativeImGui.Raw.GetWindowDrawList(); - NativeImGui.SetNextItemWidth(-1f); - bool isOpen = EditorWidget.BeginBoundedCombo("##icon", string.Empty); - Vector2 comboMinimum = NativeImGui.GetItemRectMin(); - Vector2 comboMaximum = NativeImGui.GetItemRectMax(); - ImGuiStyle* comboStyle = NativeImGui.Raw.GetStyle(); - float comboLineHeight = NativeImGui.GetTextLineHeight(); - DrawIconTextAt( - comboDrawList, - new Vector2( - comboMinimum.X + comboStyle->FramePadding.X, - comboMinimum.Y + MathF.Max( - 0f, - (comboMaximum.Y - comboMinimum.Y - comboLineHeight) * 0.5f)), - value, - GetIconName(value), - iconSlotWidth); - if (isOpen) - { - try - { - for (int i = 0; i < C_ICONS.Count; i++) - { - (string name, string candidate) = C_ICONS[i]; - NativeImGui.PushID(name); - try - { - bool selected = string.Equals(candidate, value, StringComparison.Ordinal); - if (NativeImGui.Selectable( - "##icon_option", - selected, - (int)ImGuiSelectableFlags.None, - new Vector2(0f, NativeImGui.GetFrameHeight()))) - { - value = candidate; - setting.SetAsString("value", candidate); - } - Vector2 minimum = NativeImGui.GetItemRectMin(); - Vector2 maximum = NativeImGui.GetItemRectMax(); - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - float lineHeight = NativeImGui.GetTextLineHeight(); - DrawIconTextAt( - NativeImGui.Raw.GetWindowDrawList(), - new Vector2( - minimum.X + style->FramePadding.X, - minimum.Y + MathF.Max(0f, (maximum.Y - minimum.Y - lineHeight) * 0.5f)), - candidate, - name, - iconSlotWidth); - if (selected) - NativeImGui.SetItemDefaultFocus(); - } - finally - { - NativeImGui.PopID(); - } - } - } - finally - { - EditorWidget.EndBoundedCombo(); - } - } - } - - private static EditorSettingObject CreateDefault(string glyph) - { - ArgumentException.ThrowIfNullOrWhiteSpace(glyph); - var result = new EditorSettingObject(); - result.SetAsString("value", glyph); - return result; - } - - private static float GetIconSlotWidth() - { - int frame = NativeImGui.GetFrameCount(); - if (s_iconSlotFrame == frame) - return s_iconSlotWidth; - - ImFont* font = NativeImGui.Raw.GetFont(); - float fontSize = NativeImGui.GetFontSize(); - float result = NativeImGui.GetTextLineHeight(); - for (int i = 0; i < C_ICONS.Count; i++) - { - Vector4 bounds = ImGuiWidget.GetGlyphVisualBounds(font, fontSize, C_ICONS[i].Glyph); - result = MathF.Max(result, bounds.Z - bounds.X); - } - s_iconSlotFrame = frame; - s_iconSlotWidth = result; - return s_iconSlotWidth; - } - - private static string GetIconName(string glyph) - { - for (int i = 0; i < C_ICONS.Count; i++) - { - if (string.Equals(C_ICONS[i].Glyph, glyph, StringComparison.Ordinal)) - return C_ICONS[i].Name; - } - return glyph; - } - - private static void DrawIconTextAt( - ImDrawList* drawList, - Vector2 origin, - string glyph, - string text, - float iconSlotWidth - ) { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - ImFont* font = NativeImGui.Raw.GetFont(); - float fontSize = NativeImGui.GetFontSize(); - float lineHeight = NativeImGui.GetTextLineHeight(); - uint color = NativeImGui.GetColorU32((int)ImGuiCol.Text); - ImGuiWidget.AddGlyphCentered( - drawList, - font, - fontSize, - glyph, - new Vector2(origin.X + iconSlotWidth * 0.5f, origin.Y + lineHeight * 0.5f), - color); - Inno.Editor.ImGui.ImGui.DrawText( - new Vector2(origin.X + iconSlotWidth + style->ItemInnerSpacing.X, origin.Y), - color, - text); - } -} - -[EditorSettingPath("Editor/Appearance/Icons/Scene")] -internal sealed unsafe class SceneIconSetting() : IconSetting(ImGuiIcon.Cubes) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a scene document or loaded scene."; -} - -[EditorSettingPath("Editor/Appearance/Icons/GameObject")] -internal sealed unsafe class GameObjectIconSetting() : IconSetting(ImGuiIcon.Cube) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a scene GameObject."; -} - -[EditorSettingPath("Editor/Appearance/Icons/Prefab")] -internal sealed unsafe class PrefabIconSetting() : IconSetting(ImGuiIcon.DiceD6) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a reusable prefab object."; -} - -[EditorSettingPath("Editor/Appearance/Icons/Layers")] -internal sealed unsafe class LayersIconSetting() : IconSetting(ImGuiIcon.LayerGroup) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a project layer configuration."; -} - -[EditorSettingPath("Editor/Appearance/Icons/Folder")] -internal sealed unsafe class FolderIconSetting() : IconSetting(ImGuiIcon.Folder) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a source directory."; -} - -[EditorSettingPath("Editor/Appearance/Icons/File")] -internal sealed unsafe class FileIconSetting() : IconSetting(ImGuiIcon.File) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// - public override string description => "Selects the icon used wherever the editor presents a generic source file."; -} - -[EditorSettingPath("Editor/Appearance/Icons/Shader")] -internal sealed unsafe class ShaderIconSetting() : IconSetting(ImGuiIcon.Shader) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// -public override string description => "Selects the icon used consistently for Shader graph assets and their Inspector headers."; -} - -[EditorSettingPath("Editor/Appearance/Icons/ShaderSource")] -internal sealed unsafe class ShaderSourceIconSetting() : IconSetting(ImGuiIcon.RupeeSign) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// -public override string description - => "Selects the icon used consistently for Shader source-function libraries. The default function symbol distinguishes reusable code from complete Shader graphs."; -} - -[EditorSettingPath("Editor/Appearance/Icons/Material")] -internal sealed unsafe class MaterialIconSetting() : IconSetting(ImGuiIcon.Palette) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// -public override string description => "Selects the icon used consistently for Material assets and their Inspector headers."; -} - -[EditorSettingPath("Editor/Appearance/Icons/RenderPipeline")] -internal sealed unsafe class RenderPipelineIconSetting() : IconSetting(ImGuiIcon.RupiahSign) -{ - /// - /// Gets the user-facing explanation of this feature or setting. - /// -public override string description => "Selects the icon used consistently for Render Pipeline assets and their Inspector headers."; -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj index 5432c5883..1c653775e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Inno.Editor.Panel.Hierarchy.csproj @@ -4,7 +4,6 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor @@ -13,38 +12,16 @@ - - + + - - - - - - - + + + + + - + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/DeleteGameObjectCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/DeleteGameObjectCommand.cs index 0e751a295..1f7a00942 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/DeleteGameObjectCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/DeleteGameObjectCommand.cs @@ -1,10 +1,11 @@ +using System.Collections.Generic; +using System.Linq; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Core.Input; using Inno.Scene; namespace Inno.Editor.Panel.Hierarchy; - [EditorAction(HierarchyInteractionIds.C_DELETE_GAME_OBJECT, priority: 100)] [EditorMenu(HierarchyInteractionIds.C_AREA, "Delete", order: 300)] [EditorShortcut(HierarchyInteractionIds.C_AREA, KeyCode.Delete)] @@ -20,9 +21,17 @@ internal sealed class DeleteGameObjectCommand(SceneEdits edits) : EditorAction protected override EditorActionState Query(EditorActionContext context) - => context.target.isRuntimeValid - ? edits.CanEdit(context.target) ? EditorActionState.enabled : EditorActionState.disabled - : EditorActionState.hidden; + { + if (!context.target.isRuntimeValid) + return EditorActionState.hidden; + GameObject[] targets = Targets(context); + return targets.Length != 0 + && targets.All(edits.CanEdit) ? EditorActionState.enabled : new EditorActionState( + isVisible: true, + isEnabled: false, + unavailableReason: "Every selected object must belong to an editable scene." + ); + } /// /// Executes the prepared operation and publishes only a completed result. @@ -32,10 +41,16 @@ protected override EditorActionState Query(EditorActionContext conte /// protected override void Execute(EditorActionContext context) { - if (!context.target.isRuntimeValid) - return; - _ = edits.DeleteGameObject(context.target); - if (context.interactions.selection.TryGet(out GameObject? selected) && ReferenceEquals(selected, context.target)) - _ = context.interactions.For(context.area).Select(); + edits.DeleteGameObjects(Targets(context)); + context.interactions.SetSelection((object? )null); + } + + private static GameObject[] Targets(EditorActionContext context) + { + if (!context.interactions.selection.Contains(context.target)) + return[context.target]; + var values = new List(); + context.interactions.selection.CopyTargets(values); + return values.Count == context.interactions.selection.count ? values.ToArray() : []; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/MoveGameObjectToSceneDropHandler.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/MoveGameObjectToSceneDropHandler.cs index 3c7e30e60..fbb131719 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/MoveGameObjectToSceneDropHandler.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/MoveGameObjectToSceneDropHandler.cs @@ -1,15 +1,14 @@ using System; - +using System.Collections.Generic; +using System.Linq; using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Scene; namespace Inno.Editor.Panel.Hierarchy; - [EditorDrop(HierarchyInteractionIds.C_AREA)] -internal sealed class MoveGameObjectToSceneDropHandler(SceneEdits edits) - : EditorDrop +internal sealed class MoveGameObjectToSceneDropHandler(SceneEdits edits) : EditorDrop { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -22,12 +21,12 @@ internal sealed class MoveGameObjectToSceneDropHandler(SceneEdits edits) /// protected override EditorDropStatus Query(EditorDropContext context) { - GameObject source = context.source; + GameObject[] sources = SelectedSources(context); GameScene target = context.target.scene; - return source.isRuntimeValid && edits.CanEdit(source) && edits.CanEdit(target) - && target is { isLoaded: true, isDestroyed: false } - ? EditorDropStatus.Accept() - : EditorDropStatus.rejected; + return sources.Length > 0 + && sources.All(value => value.isRuntimeValid && edits.CanEdit(value)) + && edits.CanEdit(target) + && target is { isLoaded: true, isDestroyed: false } ? EditorDropStatus.Accept() : EditorDropStatus.rejected; } /// @@ -41,30 +40,18 @@ protected override EditorDropStatus Query(EditorDropContext protected override EditorDropResult Drop(EditorDropContext context) { - GameObject source = context.source; - GameScene target = context.target.scene; - _ = edits.ChangeHierarchy( - source, - hierarchy => - { - if (!ReferenceEquals(source.scene, target)) - hierarchy.MoveToScene(source, target); - else - source.transform.SetParent(null); - source.transform.SetSiblingIndex(GetRootCount(target) - 1); - }); - _ = context.interactions.For(context.area, source).Select(); - return EditorDropResult.Accepted(source, target); + if (!Query(context).canDrop) + return EditorDropResult.rejected; + edits.MoveGameObjects(SelectedSources(context), context.target.scene); + return EditorDropResult.Accepted(context.source, context.target); } - private static int GetRootCount(GameScene scene) + private static GameObject[] SelectedSources(EditorDropContext context) { - int count = 0; - foreach (GameObject gameObject in scene.GetObjects()) - { - if (gameObject.transform.parent is null) - count++; - } - return count; + if (!context.interactions.selection.Contains(context.source)) + return[context.source]; + var values = new List(); + context.interactions.selection.CopyTargets(values); + return values.Count == context.interactions.selection.count ? values.ToArray() : []; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/RenameHierarchyTargetCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/RenameHierarchyTargetCommand.cs index 6a21e0d37..5bae41e51 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/RenameHierarchyTargetCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/RenameHierarchyTargetCommand.cs @@ -1,25 +1,20 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - using Inno.Core.Input; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Scene; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; namespace Inno.Editor.Panel.Hierarchy; - [EditorAction(HierarchyInteractionIds.C_RENAME, priority: 100)] [EditorMenu(HierarchyInteractionIds.C_AREA, "Rename", order: 200)] [EditorShortcut(HierarchyInteractionIds.C_AREA, KeyCode.F2)] -internal sealed class RenameHierarchyTargetCommand(SceneEdits edits) : - EditorPresentationAction +internal sealed class RenameHierarchyTargetCommand(SceneEdits edits) : EditorPresentationAction { - private EngineObject? m_target; private string m_buffer = string.Empty; private bool m_requestFocus; private string m_originalName = string.Empty; - /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -29,11 +24,7 @@ internal sealed class RenameHierarchyTargetCommand(SceneEdits edits) : /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => IsAvailable(context.target) - ? edits.CanEdit(context.target) ? EditorActionState.enabled : EditorActionState.disabled - : EditorActionState.hidden; - + protected override EditorActionState Query(EditorActionContext context) => IsAvailable(context.target) ? edits.CanEdit(context.target) ? EditorActionState.enabled : EditorActionState.disabled : EditorActionState.hidden; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -45,7 +36,6 @@ protected override void Execute(EditorActionContext context) if (!TryGetName(context.target, out string name)) return; Activate(context); - m_target = context.target; m_buffer = name; m_originalName = name; m_requestFocus = true; @@ -62,21 +52,23 @@ protected override void Execute(EditorActionContext context) /// protected override bool Present(EditorActionContext context) { - if (m_target is null) + if (activeTarget is not EngineObject target) return false; InlineRenamePresentation presentation = context.argument; - InlineRenameResult result = EditorWidget.InlineRename( + InlineRenameResult result = EditorUI.current.widgets.InlineRename( presentation.id, ref m_buffer, ref m_requestFocus, presentation.rowHeight, presentation.bufferSize, - presentation.width); + presentation.width + ); if (result == InlineRenameResult.Cancel) { Cancel(); return true; } + if (result is InlineRenameResult.FocusLost or InlineRenameResult.Commit) CommitName(); return true; @@ -86,34 +78,33 @@ protected override bool Present(EditorActionContext protected override void OnCompleted() => ClearState(); - /// /// Cancels the interaction without committing its pending value. /// protected override void OnCancelled() => ClearState(); - /// /// Cancels pending presentation state when its editor surface disappears. /// protected override void OnPresentationLost() { - if (m_target is null || !IsAvailable(m_target)) + if (activeTarget is not EngineObject target || !IsAvailable(target)) { Cancel(); return; } + CommitName(); } private void CommitName() { - if (m_target is null) + if (activeTarget is not EngineObject target) return; - string fallback = m_target is GameScene ? "Scene" : "GameObject"; + string fallback = target is GameScene ? "Scene" : "GameObject"; string name = string.IsNullOrWhiteSpace(m_buffer) ? fallback : m_buffer.Trim(); if (!string.Equals(m_originalName, name, StringComparison.Ordinal)) { - switch (m_target) + switch (target) { case GameScene scene: edits.RenameScene(scene, name); @@ -123,25 +114,23 @@ private void CommitName() break; } } + Complete(); } private void ClearState() { - m_target = null; m_buffer = string.Empty; m_requestFocus = false; m_originalName = string.Empty; } - private static bool IsAvailable(EngineObject target) - => target switch - { - GameScene scene => scene.isLoaded && !scene.isDestroyed, - GameObject gameObject => gameObject.isRuntimeValid, - _ => false - }; - + private static bool IsAvailable(EngineObject target) => target switch + { + GameScene scene => scene.isLoaded && !scene.isDestroyed, + GameObject gameObject => gameObject.isRuntimeValid, + _ => false + }; private static bool TryGetName( EngineObject target, out string name diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/ReparentGameObjectDropHandler.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/ReparentGameObjectDropHandler.cs index ee538f3c1..041664d6e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/ReparentGameObjectDropHandler.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/ReparentGameObjectDropHandler.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; - using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.Scene; @@ -9,10 +8,8 @@ using Inno.Scene.Components; namespace Inno.Editor.Panel.Hierarchy; - [EditorDrop(HierarchyInteractionIds.C_AREA)] -internal sealed class ReparentGameObjectDropHandler(SceneEdits edits) - : EditorDrop +internal sealed class ReparentGameObjectDropHandler(SceneEdits edits) : EditorDrop { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -25,10 +22,15 @@ internal sealed class ReparentGameObjectDropHandler(SceneEdits edits) /// protected override EditorDropStatus Query(EditorDropContext context) { - GameObject source = context.source; GameObject target = context.target.gameObject; - if (ReferenceEquals(source, target) || !source.isRuntimeValid || !target.isRuntimeValid - || !edits.CanEdit(source) || !edits.CanEdit(target)) + GameObject[] sources = SelectedSources(context); + if (sources.Length == 0 || !target.isRuntimeValid || !edits.CanEdit(target) || sources.Any(value => !value.isRuntimeValid || !edits.CanEdit(value))) + return EditorDropStatus.rejected; + Transform? parent = context.placement == EditorDropPlacement.Into ? target.transform : target.transform.parent; + for (Transform? ancestor = parent; ancestor is not null; ancestor = ancestor.parent) + if (sources.Any(value => ReferenceEquals(value.transform, ancestor))) + return EditorDropStatus.rejected; + if (sources.Contains(target)) return EditorDropStatus.rejected; return EditorDropStatus.Accept(context.placement switch { @@ -49,73 +51,30 @@ protected override EditorDropStatus Query(EditorDropContext protected override EditorDropResult Drop(EditorDropContext context) { - GameObject source = context.source; + if (!Query(context).canDrop) + return EditorDropResult.rejected; GameObject target = context.target.gameObject; - Transform sourceTransform = source.transform; - Transform targetTransform = target.transform; - GameObject[] relatedObjects = sourceTransform.children - .Select(static child => child.gameObject) - .ToArray(); - _ = edits.ChangeHierarchy( - source, - hierarchy => - { - if (!ReferenceEquals(source.scene, target.scene)) - hierarchy.MoveToScene(source, target.scene); - ApplyDrop(context, sourceTransform, targetTransform); - }, - "Reparent GameObject", - relatedObjects); - _ = context.interactions.For(context.area, source).Select(); - return EditorDropResult.Accepted(source, target); - } - - private static void ApplyDrop( - EditorDropContext context, - Transform sourceTransform, - Transform targetTransform - ) { - if (context.placement == EditorDropPlacement.Into) + GameObject[] sources = SelectedSources(context); + Transform? parent = context.placement == EditorDropPlacement.Into ? target.transform : target.transform.parent; + int insertionIndex = int.MaxValue; + if (context.placement != EditorDropPlacement.Into) { - if (IsDescendantOf(targetTransform, sourceTransform)) - PromoteDirectChildren(sourceTransform); - sourceTransform.SetParent(targetTransform); - sourceTransform.SetSiblingIndex(targetTransform.children.Count - 1); - return; + insertionIndex = target.transform.siblingIndex; + insertionIndex -= sources.Count(value => ReferenceEquals(value.transform.parent, parent) && ReferenceEquals(value.scene, target.scene) && value.transform.siblingIndex < target.transform.siblingIndex); + if (context.placement == EditorDropPlacement.After) + insertionIndex++; } - Transform? targetParent = targetTransform.parent; - sourceTransform.SetParent(targetParent); - int sourceIndex = sourceTransform.siblingIndex; - int targetIndex = targetTransform.siblingIndex; - if (sourceIndex < targetIndex) - targetIndex--; - sourceTransform.SetSiblingIndex( - targetIndex + (context.placement == EditorDropPlacement.After ? 1 : 0)); + edits.MoveGameObjects(sources, target.scene, parent?.gameObject, insertionIndex); + return EditorDropResult.Accepted(context.source, context.target); } - private static bool IsDescendantOf( - Transform transform, - Transform possibleAncestor - ) { - for (Transform? current = transform.parent; current is not null; current = current.parent) - { - if (ReferenceEquals(current, possibleAncestor)) - return true; - } - return false; - } - - private static void PromoteDirectChildren(Transform transform) + private static GameObject[] SelectedSources(EditorDropContext context) { - Transform? previousParent = transform.parent; - int insertionIndex = transform.siblingIndex; - List children = new(transform.children); - for (int i = 0; i < children.Count; i++) - { - Transform child = children[i]; - child.SetParent(previousParent); - child.SetSiblingIndex(insertionIndex + i); - } + if (!context.interactions.selection.Contains(context.source)) + return[context.source]; + var values = new List(); + context.interactions.selection.CopyTargets(values); + return values.Count == context.interactions.selection.count ? values.ToArray() : []; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/SaveCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/SaveCommand.cs index 285f94344..139fef8ab 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/SaveCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Interactions/SaveCommand.cs @@ -1,27 +1,21 @@ using System; using System.Collections.Generic; - using Inno.Core.Logging; using Inno.Core.Input; using Inno.Editor.Interactions; -using Inno.Editor.Panel.FileBrowser; using Inno.Editor.Scene; using Inno.Scene; namespace Inno.Editor.Panel.Hierarchy; - [EditorAction(HierarchyInteractionIds.C_SAVE)] [EditorMenu(HierarchyInteractionIds.C_MAIN_MENU_AREA, "File/Save", order: 100)] [EditorShortcut(KeyCode.S, primary: true)] internal sealed class SaveCommand( IEditorSceneWorkspace workspace, - AssetEditorModule assets, LogRouter logs ) : EditorAction { - private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))) - .CreateLogger(); - + private readonly Logger m_log = (logs ?? throw new ArgumentNullException(nameof(logs))).CreateLogger(); /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -31,11 +25,8 @@ LogRouter logs /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => workspace.canPersist && workspace.activeScene is not null - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => workspace.canPersist + && workspace.activeScene is not null ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -49,9 +40,7 @@ protected override void Execute(EditorActionContext context) IReadOnlyList scenes = workspace.scenes; for (int i = 0; i < scenes.Count; i++) { - _ = workspace.Save( - scenes[i], - assets.browser.projectDirectory); + _ = workspace.Save(scenes[i], context.editor.projectDirectory); } } catch (Exception exception) diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyDropVisual.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyDropVisual.cs index 36adfca23..2c9913245 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyDropVisual.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyDropVisual.cs @@ -1,25 +1,19 @@ +using UiDrawing = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; - using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.Panel.Hierarchy; - -internal sealed unsafe class HierarchyDropVisual +internal sealed class HierarchyDropVisual { internal EditorDropPlacement GetScenePlacement( in TreeNodeResult result, float mouseY - ) - => mouseY >= (result.min.Y + result.max.Y) * 0.5f - ? EditorDropPlacement.After - : EditorDropPlacement.Before; - + ) => mouseY >= (result.min.Y + result.max.Y) * 0.5f ? EditorDropPlacement.After : EditorDropPlacement.Before; internal EditorDropPlacement GetObjectPlacement( in TreeNodeResult result, float mouseY @@ -38,18 +32,16 @@ EditorDropVisual visual switch (visual) { case EditorDropVisual.InsertBefore: - EditorWidget.InsertionLine(result.min.X, result.max.X, result.min.Y); + EditorUI.current.widgets.InsertionLine(result.min.X, result.max.X, result.min.Y); break; case EditorDropVisual.InsertAfter: - EditorWidget.InsertionLine(result.min.X, result.max.X, result.max.Y); + EditorUI.current.widgets.InsertionLine(result.min.X, result.max.X, result.max.Y); break; case EditorDropVisual.Highlight: { Vector2 highlightMin = result.contentMin; - highlightMin.X = MathF.Max( - result.min.X, - highlightMin.X - NativeImGui.Raw.GetStyle()->ItemInnerSpacing.X); - EditorWidget.DropTargetHighlight(highlightMin, result.max); + highlightMin.X = MathF.Max(result.min.X, highlightMin.X - UiDrawing.GetLayout().itemInnerSpacing.X); + EditorUI.current.widgets.DropTargetHighlight(highlightMin, result.max); break; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.Rows.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.Rows.cs new file mode 100644 index 000000000..3b9dc8e31 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.Rows.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using Inno.Core.Identity; +using Inno.Editor.Core; +using Inno.Scene; +using Inno.Scene.Components; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Panel.Hierarchy; + +internal sealed partial class HierarchyPanel +{ + private readonly record struct HierarchyRow( + RuntimeIdentity identity, + int depth + ); + + private readonly List m_rows = []; + private readonly List<(RuntimeIdentity identity, ulong revision)> m_sceneInputs = []; + private ulong m_expansionRevision = ulong.MaxValue; + + private void RefreshRows(IReadOnlyList scenes) + { + var widgets = EditorUI.current.widgets; + foreach (GameScene scene in scenes) + if (m_initializedSceneIds.Add(scene.identity.persistentId)) + widgets.SetTreeNodeOpen("scene_" + scene.identity.persistentId.ToString("N"), true); + foreach (Guid id in m_forceOpenSceneIds) + widgets.SetTreeNodeOpen("scene_" + id.ToString("N"), true); + foreach (Guid id in m_forceOpenIds) + widgets.SetTreeNodeOpen("hierarchy_" + id.ToString("N"), true); + m_forceOpenSceneIds.Clear(); + m_forceOpenIds.Clear(); + + bool changed = m_expansionRevision != widgets.treeExpansionRevision || m_sceneInputs.Count != scenes.Count; + for (int index = 0; !changed && index < scenes.Count; index++) + changed = m_sceneInputs[index] != (RequireIdentity(scenes[index]), scenes[index].hierarchyRevision); + if (!changed) + return; + + var rows = new List(); + var inputs = new List<(RuntimeIdentity, ulong)>(); + var pending = new Stack<(GameObject target, int depth)>(); + var visited = new HashSet(); + foreach (GameScene scene in scenes) + { + RuntimeIdentity sceneId = RequireIdentity(scene); + inputs.Add((sceneId, scene.hierarchyRevision)); + rows.Add(new(sceneId, 0)); + if (!widgets.IsTreeNodeOpen("scene_" + scene.identity.persistentId.ToString("N"))) + continue; + IReadOnlyList roots = m_selection.GetRootObjects(scene); + for (int index = roots.Count - 1; index >= 0; index--) + pending.Push((roots[index], 1)); + while (pending.TryPop(out var next)) + { + if (!next.target.isRuntimeValid) + continue; + RuntimeIdentity identity = RequireIdentity(next.target); + if (!visited.Add(identity)) + throw new InvalidOperationException("A hierarchy view encountered a duplicate object identity."); + rows.Add(new(identity, next.depth)); + if (!widgets.IsTreeNodeOpen("hierarchy_" + next.target.identity.persistentId.ToString("N"))) + continue; + IReadOnlyList children = next.target.transform.children; + for (int index = children.Count - 1; index >= 0; index--) + pending.Push((children[index].gameObject, next.depth + 1)); + } + } + + m_rows.Clear(); + m_rows.AddRange(rows); + m_sceneInputs.Clear(); + m_sceneInputs.AddRange(inputs); + m_visibleSelection.Clear(); + foreach (HierarchyRow row in rows) + m_visibleSelection.Add(row.identity); + m_expansionRevision = widgets.treeExpansionRevision; + } + + private void DrawRows(EditorContext context) + { + RuntimeIdentity? selected = (m_interactions.selection.selectedTarget as IdentityObject)?.identity.runtimeIdentity; + RuntimeIdentity? dragging = m_interactions.TryGetActiveDragIdentity(out RuntimeIdentity source) ? source : null; + Span preserved = stackalloc int[3]; + int count = 0; + for (int index = 0; index < m_rows.Count; index++) + if (m_rows[index].identity == selected || m_rows[index].identity == dragging || m_rows[index].identity == m_drawKeyboardFocus) + preserved[count++] = index; + EditorUI.current.widgets.ClippedRows(m_rows.Count, -1f, (panel: this, context), static ( + state, + start, + end + ) => + { + for (int index = start; index < end; index++) + { + state.panel.m_visibleRowIndex = index; + HierarchyRow row = state.panel.m_rows[index]; + if (state.panel.m_interactions.TryResolveIdentity(row.identity, out IdentityObject? target)) + { + if (target is GameScene scene) + state.panel.DrawScene(state.context, scene, row.depth); + else if (target is GameObject gameObject) + state.panel.DrawObject(state.context, gameObject, row.depth); + else + Ui.Dummy(new(1f, Ui.GetFrameHeight())); + } + else + Ui.Dummy(new(1f, Ui.GetFrameHeight())); + } + }, preserved[..count]); + } + + private static RuntimeIdentity RequireIdentity(IdentityObject target) + => target.identity.runtimeIdentity ?? throw new InvalidOperationException("A visible hierarchy row requires a registered identity."); +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.cs index d2190d8da..8603953df 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchyPanel.cs @@ -1,7 +1,7 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Core.Identity; using Inno.Editor.Core; using Inno.Editor.Interactions; @@ -9,23 +9,25 @@ using Inno.Editor.Settings; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Scene; using Inno.Scene.Components; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Hierarchy; - /// /// Edits the active scene object hierarchy. /// [EditorPanel("scene.hierarchy", "Hierarchy", order: 100, menuPath: "Workspace")] -internal sealed class HierarchyPanel : EditorPanel +internal sealed partial class HierarchyPanel : EditorPanel { private const nuint C_NAME_BUFFER_SIZE = 512; + private readonly List m_visibleSelection = []; + private RuntimeIdentity? m_pendingSelection; + private RuntimeIdentity? m_keyboardFocus; + private RuntimeIdentity? m_requestedKeyboardFocus; + private RuntimeIdentity? m_drawKeyboardFocus; + private int m_keyboardPageSize = 1; private readonly IEditorSceneWorkspace m_workspace; private readonly EditorInteractions m_interactions; private readonly SceneEdits m_edits; @@ -34,16 +36,13 @@ internal sealed class HierarchyPanel : EditorPanel private readonly HierarchyDropVisual m_dropVisual = new(); private readonly HashSet m_forceOpenIds = []; private readonly HashSet m_forceOpenSceneIds = []; - private readonly HashSet m_drawnIds = []; private readonly HashSet m_initializedSceneIds = []; private ulong m_lastRevealedSelectionVersion = ulong.MaxValue; private int m_visibleRowIndex; - /// /// Gets whether use window padding is enabled for this implementation. /// public override bool useWindowPadding => false; - /// /// Lets the hierarchy content child own both scroll directions. /// @@ -73,208 +72,172 @@ EditorSettings settings /// protected override void OnDraw(EditorContext context) { - bool visible = NativeImGui.BeginChild( - "##HierarchyContent", - Vector2.Zero, - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.NoSavedSettings | - ImGuiWindowFlags.HorizontalScrollbar)); + bool visible = Ui.BeginChild("##HierarchyContent", Vector2.Zero, (ImGuiChildFlags.None), (ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.HorizontalScrollbar)); try { if (visible) + { + m_keyboardPageSize = Math.Max(1, (int)(Ui.GetContentRegionAvailable().Y / Math.Max(1f, Ui.GetFrameHeight()))); DrawContent(context); + } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } - private unsafe void DrawContent(EditorContext context) + private static HierarchyStyle hierarchyStyle => new(EditorUI.current.widgets.style); + + private void DrawContent(EditorContext context) { - m_drawnIds.Clear(); + m_drawKeyboardFocus = m_requestedKeyboardFocus; + m_requestedKeyboardFocus = null; + m_pendingSelection = null; m_visibleRowIndex = 0; - m_selection.Prune(context); + m_selection.Prune(); RevealSelection(); - - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.ItemSpacing), EditorWidget.style.hierarchyItemSpacing); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.compactFramePadding); + Ui.PushStyleVar((ImGuiStyleVar.ItemSpacing), hierarchyStyle.itemSpacing); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.compactFramePadding); try { IReadOnlyList scenes = m_workspace.scenes; if (scenes.Count > 0) { - Vector2 contentOrigin = NativeImGui.GetCursorScreenPos(); - NativeImGui.SetCursorScreenPos(new Vector2( - contentOrigin.X, - contentOrigin.Y - NativeImGui.Raw.GetCurrentWindow()->WindowPadding.Y)); - } - for (int i = 0; i < scenes.Count; i++) - { - DrawScene(context, scenes[i]); + Vector2 contentOrigin = Ui.GetCursorScreenPosition(); + Ui.SetCursorScreenPosition(new Vector2(contentOrigin.X, contentOrigin.Y - Ui.GetWindowBounds().padding.Y)); } + RefreshRows(scenes); + DrawRows(context); + DrawBlankArea(context); + if (m_pendingSelection is RuntimeIdentity pending && m_interactions.TryResolveIdentity(pending, out IdentityObject? target) && target is not null) + { + EditorUI.current.widgets.SelectTarget(m_interactions, target, m_visibleSelection); + m_keyboardFocus = pending; + m_interactions.For(HierarchyInteractionIds.C_AREA, target).Focus(); + } + HandleKeyboardNavigation(); } finally { - NativeImGui.PopStyleVar(2); + m_pendingSelection = null; + Ui.PopStyleVar(2); } } - private void DrawScene( - EditorContext context, - GameScene scene - ) { - Guid sceneId = ((IdentityObject)scene).identity.persistentId; - string id = sceneId.ToString("N"); - bool shouldOpen = m_initializedSceneIds.Add(sceneId); - shouldOpen |= m_forceOpenSceneIds.Remove(sceneId); - if (shouldOpen) + private void HandleKeyboardNavigation() + { + int focused = -1; + int selected = -1; + RuntimeIdentity? selectedTarget = (m_interactions.selection.selectedTarget as IdentityObject)?.identity.runtimeIdentity; + for (int index = 0; index < m_visibleSelection.Count; index++) { - EditorWidget.SetNextTreeNodeOpen(true); + RuntimeIdentity item = m_visibleSelection[index]; + if (item == m_keyboardFocus) + focused = index; + if (item == selectedTarget) + selected = index; + } + if (focused < 0) + focused = selected; + ListNavigationResult navigation = EditorUI.current.widgets.NavigateList(m_interactions, m_visibleSelection.Count, focused, m_keyboardPageSize); + if (navigation.focusedIndex >= 0 && (navigation.focusChanged || navigation.selectionRequested || navigation.activate)) + { + RuntimeIdentity identity = m_visibleSelection[navigation.focusedIndex]; + if (!m_interactions.TryResolveIdentity(identity, out IdentityObject? target) || target is null) + return; + m_keyboardFocus = identity; + if (navigation.focusChanged) + m_requestedKeyboardFocus = m_keyboardFocus; + if (navigation.selectionRequested) + { + if (navigation.extendRange) + m_interactions.SelectRange(m_visibleSelection, target, navigation.additive); + else if (navigation.toggle) + m_interactions.ToggleSelection(target); + else + m_interactions.SetSelection(target); + } + m_interactions.For(HierarchyInteractionIds.C_AREA, m_interactions.selection.selectedTarget).Focus(); + if (navigation.activate) + { + if (!m_interactions.selection.Contains(target)) + m_interactions.SetSelection(target); + _ = m_interactions.For(HierarchyInteractionIds.C_AREA, target).Execute(HierarchyInteractionIds.C_RENAME); + } } + if (navigation.cancel) + m_keyboardFocus = null; + } - bool selected = m_interactions.selection.TryGet(out GameScene? selectedScene) && - ReferenceEquals(selectedScene, scene); - TreeNodeResult result = EditorWidget.TreeNode( + private void DrawScene( + EditorContext context, + GameScene scene, + int depth + ) { + string id = scene.identity.persistentId.ToString("N"); + bool selected = m_interactions.selection.Contains(scene); + TreeNodeResult result = EditorUI.current.widgets.TreeNode( $"scene_{id}", - drawContext => DrawSceneRowContent(context, scene, id, drawContext.rowHeight), - new TreeNodeOptions - { - isLeaf = false, - selected = selected, - showBackground = true, - backgroundColor = EditorPalette.hierarchySceneRow, - suppressHoverHighlight = false + drawContext => DrawSceneRowContent(scene, id, drawContext.rowHeight), + new TreeNodeOptions { + depth = depth, selected = selected, requestFocus = scene.identity.runtimeIdentity == m_drawKeyboardFocus, + keepContentActive = m_interactions.For(HierarchyInteractionIds.C_AREA, scene).IsActive(HierarchyInteractionIds.C_RENAME), + showBackground = true, backgroundColor = EditorPalette.title }); - try - { - + if (!result.isSubmitted) + return; if (result.isClicked || result.isDoubleClicked) - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, scene).Select(); - + m_pendingSelection = scene.identity.runtimeIdentity; if (result.isDoubleClicked) - { - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, scene) - .Execute(HierarchyInteractionIds.C_RENAME); - } - - _ = EditorDragDropRenderer.Source( - m_interactions.For(HierarchyInteractionIds.C_AREA, scene), - new EditorDragData(scene, scene.name), - () => NativeImGui.TextUnformatted(scene.name)); - - EditorDropPlacement scenePlacement = m_dropVisual.GetScenePlacement(result, NativeImGui.GetMousePos().Y); - EditorDropWidgetResult sceneDrop = EditorDragDropRenderer.Target( - m_interactions.For(HierarchyInteractionIds.C_AREA, new HierarchySceneDropTarget(scene)), - scenePlacement); - if (sceneDrop.isPreviewing && sceneDrop.status.canDrop) - m_dropVisual.Draw(result, sceneDrop.status.visual); - ApplyDropResult(sceneDrop.result); - + _ = m_interactions.For(HierarchyInteractionIds.C_AREA, scene).Execute(HierarchyInteractionIds.C_RENAME); + _ = EditorDragDropRenderer.Source(m_interactions.For(HierarchyInteractionIds.C_AREA, scene), + new EditorDragData(scene, scene.name), () => Ui.Text(scene.name)); + EditorDropPlacement placement = m_dropVisual.GetScenePlacement(result, Ui.GetMousePos().Y); + EditorDropWidgetResult drop = EditorDragDropRenderer.Target( + m_interactions.For(HierarchyInteractionIds.C_AREA, new HierarchySceneDropTarget(scene)), placement); + if (drop.isPreviewing && drop.status.canDrop) + m_dropVisual.Draw(result, drop.status.visual); + ApplyDropResult(drop.result); DrawSceneContextMenu(context, scene, id); - if (!result.isOpen) - { - return; - } - - IReadOnlyList roots = m_selection.GetRootObjects(scene); - for (int i = 0; i < roots.Count; i++) - { - DrawObject(context, scene, roots[i]); - } - - } - finally - { - if (result.isOpen) - NativeImGui.TreePop(); - } } private void DrawObject( EditorContext context, - GameScene scene, - GameObject gameObject + GameObject gameObject, + int depth ) { - if (!gameObject.isRuntimeValid || !m_drawnIds.Add(gameObject.identity.persistentId)) - { + if (!gameObject.isRuntimeValid) return; - } - - Transform transform = gameObject.GetComponent(); - bool selected = m_interactions.selection.TryGet(out GameObject? selectedObject) - && ReferenceEquals(selectedObject, gameObject); string id = gameObject.identity.persistentId.ToString("N"); - if (m_forceOpenIds.Remove(gameObject.identity.persistentId)) - { - EditorWidget.SetNextTreeNodeOpen(true); - } - - TreeNodeResult result = EditorWidget.TreeNode( + TreeNodeResult result = EditorUI.current.widgets.TreeNode( $"hierarchy_{id}", drawContext => DrawRowContent(context, gameObject, drawContext.rowHeight), - new TreeNodeOptions - { - selected = selected, - isLeaf = transform.children.Count == 0, - showBackground = true, - backgroundColor = m_visibleRowIndex % 2 == 0 - ? EditorPalette.collectionRow - : EditorPalette.collectionRowAlternate, + new TreeNodeOptions { + depth = depth, selected = m_interactions.selection.Contains(gameObject), + requestFocus = gameObject.identity.runtimeIdentity == m_drawKeyboardFocus, + keepContentActive = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject).IsActive(HierarchyInteractionIds.C_RENAME), + isLeaf = gameObject.transform.children.Count == 0, showBackground = true, + backgroundColor = m_visibleRowIndex % 2 == 0 ? EditorPalette.collectionRow : EditorPalette.collectionRowAlternate, drawViewportOverlay = () => DrawVisibilityButton(gameObject, id) }); - m_visibleRowIndex++; - try - { - + if (!result.isSubmitted) + return; if (result.isClicked || result.isDoubleClicked) - { - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject).Select(); - } - + m_pendingSelection = gameObject.identity.runtimeIdentity; if (result.isDoubleClicked) - { - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject) - .Execute(HierarchyInteractionIds.C_RENAME); - } - - _ = EditorDragDropRenderer.Source( - m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject), - new EditorDragData(gameObject, gameObject.name), - () => NativeImGui.TextUnformatted(gameObject.name)); - EditorDropPlacement placement = m_dropVisual.GetObjectPlacement(result, NativeImGui.GetMousePos().Y); - EditorDropWidgetResult objectDrop = EditorDragDropRenderer.Target( - m_interactions.For(HierarchyInteractionIds.C_AREA, new HierarchyObjectDropTarget(gameObject)), - placement); - if (objectDrop.isPreviewing && objectDrop.status.canDrop) - m_dropVisual.Draw(result, objectDrop.status.visual); - ApplyDropResult(objectDrop.result); - + _ = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject).Execute(HierarchyInteractionIds.C_RENAME); + _ = EditorDragDropRenderer.Source(m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject), + new EditorDragData(gameObject, gameObject.name), () => Ui.Text(gameObject.name)); + EditorDropPlacement placement = m_dropVisual.GetObjectPlacement(result, Ui.GetMousePos().Y); + EditorDropWidgetResult drop = EditorDragDropRenderer.Target( + m_interactions.For(HierarchyInteractionIds.C_AREA, new HierarchyObjectDropTarget(gameObject)), placement); + if (drop.isPreviewing && drop.status.canDrop) + m_dropVisual.Draw(result, drop.status.visual); + ApplyDropResult(drop.result); DrawObjectContextMenu(context, gameObject, id); - - if (!result.isOpen) - { - return; - } - - IReadOnlyList children = transform.children; - for (int i = 0; i < children.Count; i++) - { - GameObject? child = children[i].gameObject; - if (child is not null) - { - DrawObject(context, scene, child); - } - } - - } - finally - { - if (result.isOpen) - NativeImGui.TreePop(); - } } private void DrawRowContent( @@ -286,55 +249,37 @@ float rowHeight bool dimmed = !gameObject.activeInHierarchy; if (dimmed) { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.hierarchyInactiveText); + Ui.PushStyleColor((ImGuiCol.Text), HierarchyStyle.inactiveText); } + try { EditorInteraction interaction = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject); bool isRenaming = interaction.IsActive(HierarchyInteractionIds.C_RENAME); if (isRenaming) { - EditorWidget.IconText( - m_settings - .Get("Editor/Appearance/Icons/GameObject") - .GetAsString("value", ImGuiIcon.Cube)!, - string.Empty, - false); - NativeImGui.SameLine(0f, 0f); + EditorUI.current.widgets.IconText(m_settings.Get("Editor/Appearance/Icons/GameObject").GetAsString("value", ImGuiIcon.Cube)!, string.Empty, false); + Ui.SameLine(0f, 0f); float visibilityWidth = GetVisibilityButtonWidth(); float renameWidth = MathF.Max( - EditorWidget.style.hierarchyRenameMinimumWidth, - NativeImGui.GetContentRegionAvail().X - - visibilityWidth - - EditorWidget.style.hierarchyRenameTrailingGap); - _ = interaction.Present( - HierarchyInteractionIds.C_RENAME, - new InlineRenamePresentation( - $"hierarchy_{id}", - renameWidth, - rowHeight, - C_NAME_BUFFER_SIZE)); + hierarchyStyle.renameMinimumWidth, + Ui.GetContentRegionAvailable().X - visibilityWidth - hierarchyStyle.renameTrailingGap + ); + _ = interaction.Present(HierarchyInteractionIds.C_RENAME, new InlineRenamePresentation($"hierarchy_{id}", renameWidth, rowHeight, C_NAME_BUFFER_SIZE)); } else { - EditorWidget.IconText( - m_settings - .Get("Editor/Appearance/Icons/GameObject") - .GetAsString("value", ImGuiIcon.Cube)!, - gameObject.name, - false); + EditorUI.current.widgets.IconText(m_settings.Get("Editor/Appearance/Icons/GameObject").GetAsString("value", ImGuiIcon.Cube)!, gameObject.name, false); } } finally { if (dimmed) - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } - } private void DrawSceneRowContent( - EditorContext context, GameScene scene, string id, float rowHeight @@ -342,61 +287,48 @@ float rowHeight EditorInteraction interaction = m_interactions.For(HierarchyInteractionIds.C_AREA, scene); if (!interaction.IsActive(HierarchyInteractionIds.C_RENAME)) { - m_selection.DrawSceneRowContent(context, scene); + m_selection.DrawSceneRowContent(scene); return; } - EditorWidget.IconText( - m_settings - .Get("Editor/Appearance/Icons/Scene") - .GetAsString("value", ImGuiIcon.Cubes)!, + EditorUI.current.widgets.IconText( + m_settings.Get("Editor/Appearance/Icons/Scene").GetAsString("value", ImGuiIcon.Cubes)!, string.Empty, - ReferenceEquals(scene, m_workspace.activeScene)); - NativeImGui.SameLine(0f, 0f); + ReferenceEquals(scene, m_workspace.activeScene) + ); + Ui.SameLine(0f, 0f); float renameWidth = MathF.Max( - EditorWidget.style.hierarchyRenameMinimumWidth, - NativeImGui.GetContentRegionAvail().X - - EditorWidget.style.hierarchyRenameTrailingGap); - _ = interaction.Present( - HierarchyInteractionIds.C_RENAME, - new InlineRenamePresentation( - $"scene_{id}", - renameWidth, - rowHeight, - C_NAME_BUFFER_SIZE)); + hierarchyStyle.renameMinimumWidth, + Ui.GetContentRegionAvailable().X - hierarchyStyle.renameTrailingGap + ); + _ = interaction.Present(HierarchyInteractionIds.C_RENAME, new InlineRenamePresentation($"scene_{id}", renameWidth, rowHeight, C_NAME_BUFFER_SIZE)); } - private unsafe void DrawVisibilityButton( + private void DrawVisibilityButton( GameObject gameObject, string id ) { string icon = gameObject.activeSelf ? ImGuiIcon.Eye : ImGuiIcon.EyeSlash; float buttonWidth = GetVisibilityButtonWidth(); - float right = NativeImGui.Raw.GetCurrentWindow()->WorkRect.Max.X - - EditorWidget.style.windowPadding.X - - buttonWidth; - NativeImGui.SameLine(0f, -1f); - Vector2 cursor = NativeImGui.GetCursorScreenPos(); - NativeImGui.SetCursorScreenPos(new Vector2(MathF.Max(cursor.X, right), cursor.Y)); - if (EditorWidget.ClickableIcon($"hierarchy_visibility_{id}", icon, - gameObject.activeSelf ? "Deactivate" : "Activate")) + float right = Ui.GetWindowBounds().workMaximum.X - EditorUI.current.widgets.style.windowPadding.X - buttonWidth; + Ui.SameLine(0f, -1f); + Vector2 cursor = Ui.GetCursorScreenPosition(); + Ui.SetCursorScreenPosition(new Vector2(MathF.Max(cursor.X, right), cursor.Y)); + if (EditorUI.current.widgets.ClickableIcon($"hierarchy_visibility_{id}", icon, gameObject.activeSelf ? "Deactivate" : "Activate")) { m_edits.SetGameObjectActive(gameObject, !gameObject.activeSelf); } } - private static float GetVisibilityButtonWidth() => EditorWidget.GetCompactIconSize().X; - + private static float GetVisibilityButtonWidth() => EditorUI.current.widgets.GetCompactIconSize().X; private void DrawSceneContextMenu( EditorContext context, GameScene scene, string id ) { - if (NativeImGui.IsItemClicked((int)(ImGuiMouseButton.Right))) - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, scene).Select(); - _ = EditorMenuRenderer.ContextMenu( - $"##scene_context_{id}", - m_interactions.For(HierarchyInteractionIds.C_AREA, scene)); + if (Ui.IsItemClicked(ImGuiMouseButton.Right) && !m_interactions.selection.Contains(scene)) + m_interactions.SetSelection(scene); + _ = EditorUI.current.menus.ContextMenu($"##scene_context_{id}", m_interactions.For(HierarchyInteractionIds.C_AREA, scene)); } private void DrawObjectContextMenu( @@ -404,21 +336,17 @@ private void DrawObjectContextMenu( GameObject gameObject, string id ) { - if (NativeImGui.IsItemClicked((int)(ImGuiMouseButton.Right))) - _ = m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject).Select(); - _ = EditorMenuRenderer.ContextMenu( - $"##hierarchy_context_{id}", - m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject)); + if (Ui.IsItemClicked(ImGuiMouseButton.Right) && !m_interactions.selection.Contains(gameObject)) + m_interactions.SetSelection(gameObject); + _ = EditorUI.current.menus.ContextMenu($"##hierarchy_context_{id}", m_interactions.For(HierarchyInteractionIds.C_AREA, gameObject)); } private void DrawBlankArea(EditorContext context) { - Vector2 available = NativeImGui.GetContentRegionAvail(); - Vector2 size = new( - MathF.Max(1f, available.X), - MathF.Max(EditorWidget.style.hierarchyBlankMinimumHeight, available.Y)); - _ = NativeImGui.InvisibleButton("##hierarchy_blank", size); - if (NativeImGui.IsItemClicked((int)(ImGuiMouseButton.Left))) + Vector2 available = Ui.GetContentRegionAvailable(); + Vector2 size = new(MathF.Max(1f, available.X), MathF.Max(hierarchyStyle.blankMinimumHeight, available.Y)); + _ = Ui.InvisibleButton("##hierarchy_blank", size); + if (Ui.IsItemClicked((ImGuiMouseButton.Left))) { _ = m_interactions.For(HierarchyInteractionIds.C_AREA).Select(); } @@ -427,18 +355,16 @@ private void DrawBlankArea(EditorContext context) if (activeScene is not null) { EditorDropWidgetResult drop = EditorDragDropRenderer.Target( - m_interactions.For( - HierarchyInteractionIds.C_AREA, - new HierarchySceneDropTarget(activeScene)), - EditorDropPlacement.Into); + m_interactions.For(HierarchyInteractionIds.C_AREA, new HierarchySceneDropTarget(activeScene)), + EditorDropPlacement.Into + ); ApplyDropResult(drop.result); } - _ = EditorMenuRenderer.ContextMenu( + _ = EditorUI.current.menus.ContextMenu( "##hierarchy_blank_context", - activeScene is null - ? m_interactions.For(HierarchyInteractionIds.C_AREA) - : m_interactions.For(HierarchyInteractionIds.C_AREA, activeScene)); + activeScene is null ? m_interactions.For(HierarchyInteractionIds.C_AREA) : m_interactions.For(HierarchyInteractionIds.C_AREA, activeScene) + ); } private void RevealSelection() @@ -447,7 +373,6 @@ private void RevealSelection() if (m_lastRevealedSelectionVersion == selectionVersion) return; m_lastRevealedSelectionVersion = selectionVersion; - if (!m_interactions.selection.TryGet(out GameObject? gameObject) || !gameObject.isRuntimeValid) return; m_forceOpenSceneIds.Add(gameObject.scene.identity.persistentId); @@ -471,5 +396,4 @@ private void ApplyDropResult(EditorDropResult result) m_forceOpenIds.Add(gameObject.identity.persistentId); } } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchySelection.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchySelection.cs index 9ed4bced2..89405238e 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchySelection.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Presentation/HierarchySelection.cs @@ -1,63 +1,29 @@ - -using System; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Collections.Generic; - -using Inno.Core.Identity; -using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Editor.Settings; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Scene; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Hierarchy; - internal sealed class HierarchySelection( IEditorSceneWorkspace workspace, EditorInteractions interactions, EditorSettings settings ) { - internal void Prune(EditorContext context) + internal void Prune() { - if (interactions.selection.TryGet(out GameScene? selectedScene) && - (!selectedScene.isLoaded || !ContainsScene(workspace.scenes, selectedScene))) + if (interactions.selection.TryGet(out GameScene? selectedScene) && (!selectedScene.isLoaded || !ContainsScene(workspace.scenes, selectedScene))) { _ = interactions.For(HierarchyInteractionIds.C_AREA).Select(); return; } - if (interactions.selection.TryGet(out GameObject? gameObject) && - (!gameObject.isRuntimeValid || !ContainsScene(workspace.scenes, gameObject.scene))) - _ = interactions.For(HierarchyInteractionIds.C_AREA).Select(); - } - internal bool DeleteObject( - EditorContext context, - Guid persistentId - ) { - GameObject? gameObject = IdentityAllocator.current.Get(persistentId); - if (gameObject is null || !gameObject.isRuntimeValid || !ContainsScene(workspace.scenes, gameObject.scene)) - return false; - - _ = gameObject.scene.DestroyObject(gameObject); - if (interactions.selection.TryGet(out GameObject? selected) && ReferenceEquals(selected, gameObject)) + if (interactions.selection.TryGet(out GameObject? gameObject) && (!gameObject.isRuntimeValid || !ContainsScene(workspace.scenes, gameObject.scene))) _ = interactions.For(HierarchyInteractionIds.C_AREA).Select(); - return true; - } - - private static bool ContainsScene( - IReadOnlyList scenes, - GameScene scene - ) { - for (int i = 0; i < scenes.Count; i++) - { - if (ReferenceEquals(scenes[i], scene)) - return true; - } - return false; } internal IReadOnlyList GetRootObjects(GameScene scene) @@ -69,6 +35,7 @@ internal IReadOnlyList GetRootObjects(GameScene scene) if (objects[i].transform.parent is null) roots.Add(objects[i]); } + roots.Sort(static ( left, right @@ -76,20 +43,30 @@ internal IReadOnlyList GetRootObjects(GameScene scene) return roots; } - internal void DrawSceneRowContent( - EditorContext context, - GameScene scene - ) { - EditorWidget.IconText( - settings - .Get("Editor/Appearance/Icons/Scene") - .GetAsString("value", ImGuiIcon.Cubes)!, + internal void DrawSceneRowContent(GameScene scene) + { + EditorUI.current.widgets.IconText( + settings.Get("Editor/Appearance/Icons/Scene").GetAsString("value", ImGuiIcon.Cubes)!, scene.name, - ReferenceEquals(scene, workspace.activeScene)); + ReferenceEquals(scene, workspace.activeScene) + ); if (!workspace.IsDirty(scene)) return; - NativeImGui.SameLine(0f, 0f); + Ui.SameLine(0f, 0f); using ImGuiFontScope font = ImGuiFont.PushStyle(ImGuiFontStyle.Italic); - NativeImGui.TextUnformatted(" *"); + Ui.Text(" *"); + } + + private static bool ContainsScene( + IReadOnlyList scenes, + GameScene scene + ) { + for (int i = 0; i < scenes.Count; i++) + { + if (ReferenceEquals(scenes[i], scene)) + return true; + } + + return false; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Styling/HierarchyStyle.cs b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Styling/HierarchyStyle.cs new file mode 100644 index 000000000..b8aeb77c1 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Hierarchy/Styling/HierarchyStyle.cs @@ -0,0 +1,36 @@ +using System.Numerics; +using Inno.Editor.ImGui; + +namespace Inno.Editor.Panel.Hierarchy; + +internal readonly struct HierarchyStyle +{ + private readonly float m_zoom; + + internal HierarchyStyle(EditorStyleMetrics metrics) => m_zoom = metrics.zoom; + + /// + /// Gets the text color of inactive hierarchy entries. + /// + internal static Vector4 inactiveText { get; } = new(0.52f, 0.52f, 0.54f, EditorPalette.opacityOpaque); + + /// + /// Gets hierarchy item spacing. + /// + internal Vector2 itemSpacing => new Vector2(3f, 2f) * m_zoom; + + /// + /// Gets the minimum inline hierarchy rename width. + /// + internal float renameMinimumWidth => 48f * m_zoom; + + /// + /// Gets the hierarchy rename trailing-control gap. + /// + internal float renameTrailingGap => 8f * m_zoom; + + /// + /// Gets the minimum height of the hierarchy blank drop area. + /// + internal float blankMinimumHeight => 24f * m_zoom; +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs index cbd20cb6d..b78dff422 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/AssetReferenceDropTarget.cs @@ -3,8 +3,12 @@ namespace Inno.Editor.Panel.Inspector; /// -/// Provides an assignable asset-reference property drop target. +/// Borrows an asset property's type and assignment callback for one synchronous drop request. /// +/// +/// Recreate this target for each presentation request. Its type and callback must not be retained +/// across frames or extension generations, or included in History or persistent state. +/// public sealed class AssetReferenceDropTarget { private readonly Action m_assign; @@ -22,9 +26,6 @@ public sealed class AssetReferenceDropTarget /// /// Thrown when either argument is . /// - /// - /// Thrown when the type does not belong to the active type catalog. - /// public AssetReferenceDropTarget( Type expectedType, Action assign diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs index c0c91194d..443c58f84 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/DragDrop/EngineObjectReferenceDropTarget.cs @@ -6,8 +6,12 @@ namespace Inno.Editor.Panel.Inspector; /// -/// Provides an assignable engine-object-reference property drop target. +/// Borrows an object property's type and assignment callback for one synchronous drop request. /// +/// +/// Recreate this target for each presentation request. Its type and callback must not be retained +/// across frames or extension generations, or included in History or persistent state. +/// public sealed class EngineObjectReferenceDropTarget { private readonly Action m_assign; @@ -25,9 +29,6 @@ public sealed class EngineObjectReferenceDropTarget /// /// Thrown when either argument is . /// - /// - /// Thrown when the type does not belong to the active type catalog. - /// public EngineObjectReferenceDropTarget( Type expectedType, Action assign diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj index 67876fa28..ae4a0bb24 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Inno.Editor.Panel.Inspector.csproj @@ -4,62 +4,32 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - + + + + - + - + - - - - - - - - - - - + + + + + + + + + - + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/AddComponentCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/AddComponentCommand.cs index d23ea7452..ce240b94a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/AddComponentCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/AddComponentCommand.cs @@ -1,16 +1,16 @@ +using System.Collections.Generic; +using System.Linq; using Inno.Extensibility.Types; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Scene; namespace Inno.Editor.Panel.Inspector; - [EditorAction(InspectorInteractionIds.C_ADD_COMPONENT, InspectorInteractionIds.C_COMPONENT_AREA)] internal sealed class AddComponentCommand( SceneEdits edits, TypeCatalog types -) - : EditorAction +) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -21,11 +21,9 @@ TypeCatalog types /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => context.target.isRuntimeValid && context.argument.IsValid(types) - ? EditorActionState.enabled - : EditorActionState.hidden; - + protected override EditorActionState Query(EditorActionContext context) => Targets(context).Count > 0 + && Targets(context).All(value => value.isRuntimeValid && edits.CanEdit(value)) + && context.argument.IsValid(types) ? EditorActionState.enabled : EditorActionState.hidden; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -33,5 +31,19 @@ protected override EditorActionState Query(EditorActionContext protected override void Execute(EditorActionContext context) - => _ = edits.AddComponent(context.target, context.argument.Resolve(types)); + { + using EditorHistoryTransaction transaction = context.interactions.history.BeginTransaction("Add Components"); + foreach (GameObject target in Targets(context)) + _ = edits.AddComponent(target, context.argument.Resolve(types)); + transaction.Commit(); + } + + private static IReadOnlyList Targets(EditorActionContext context) + { + if (!context.interactions.selection.Contains(context.target)) + return[context.target]; + var targets = new List(); + context.interactions.selection.CopyTargets(targets); + return targets.Count == context.interactions.selection.count ? targets : []; + } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveComponentCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveComponentCommand.cs index 2b5926c7a..f3ce5e32a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveComponentCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveComponentCommand.cs @@ -1,12 +1,12 @@ +using Inno.Scene; using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Scene.Components; namespace Inno.Editor.Panel.Inspector; - [EditorAction(InspectorInteractionIds.C_REMOVE_COMPONENT, priority: 100)] [EditorMenu(InspectorInteractionIds.C_COMPONENT_AREA, "Remove Component", order: 200)] -internal sealed class RemoveComponentCommand(SceneEdits edits) : EditorAction +internal sealed class RemoveComponentCommand(SceneEdits edits) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -17,20 +17,16 @@ internal sealed class RemoveComponentCommand(SceneEdits edits) : EditorAction /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => context.target.component is not Transform - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => context.target is not Transform ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { - if (context.target.component is not Transform) - _ = edits.RemoveComponent(context.target.component); + if (context.target is not Transform) + _ = edits.RemoveComponent(context.target); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveSystemCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveSystemCommand.cs index e1e0ec380..57ec6d7ce 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveSystemCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/RemoveSystemCommand.cs @@ -1,11 +1,11 @@ +using Inno.Scene; using Inno.Editor.Interactions; using Inno.Editor.Scene; namespace Inno.Editor.Panel.Inspector; - [EditorAction(InspectorInteractionIds.C_REMOVE_SYSTEM, priority: 100)] [EditorMenu(InspectorInteractionIds.C_SYSTEM_AREA, "Remove System", order: 200)] -internal sealed class RemoveSystemCommand(SceneEdits edits) : EditorAction +internal sealed class RemoveSystemCommand(SceneEdits edits) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -16,17 +16,12 @@ internal sealed class RemoveSystemCommand(SceneEdits edits) : EditorAction /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => !context.target.system.isDestroyed - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => !context.target.isDestroyed ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) - => _ = edits.RemoveSystem(context.target.scene, context.target.system); + protected override void Execute(EditorActionContext context) => _ = edits.RemoveSystem(context.target); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetComponentCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetComponentCommand.cs index 0fb6a052a..4c900abca 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetComponentCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetComponentCommand.cs @@ -3,10 +3,9 @@ using Inno.Scene; namespace Inno.Editor.Panel.Inspector; - [EditorAction(InspectorInteractionIds.C_RESET_COMPONENT, priority: 100)] [EditorMenu(InspectorInteractionIds.C_COMPONENT_AREA, "Reset Component", order: 100)] -internal sealed class ResetComponentCommand(SceneEdits edits) : EditorAction +internal sealed class ResetComponentCommand(SceneEdits edits) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -17,16 +16,13 @@ internal sealed class ResetComponentCommand(SceneEdits edits) : EditorAction /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => !context.target.component.isDestroyed && context.target.component is not MissingGameComponent - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => !context.target.isDestroyed + && context.target is not MissingGameComponent ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) => edits.ResetComponent(context.target.component); + protected override void Execute(EditorActionContext context) => edits.ResetComponent(context.target); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetSystemCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetSystemCommand.cs index 4c058124d..0f7b5c51d 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetSystemCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Interactions/ResetSystemCommand.cs @@ -3,10 +3,9 @@ using Inno.Scene; namespace Inno.Editor.Panel.Inspector; - [EditorAction(InspectorInteractionIds.C_RESET_SYSTEM, priority: 100)] [EditorMenu(InspectorInteractionIds.C_SYSTEM_AREA, "Reset System", order: 100)] -internal sealed class ResetSystemCommand(SceneEdits edits) : EditorAction +internal sealed class ResetSystemCommand(SceneEdits edits) : EditorAction { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -17,17 +16,13 @@ internal sealed class ResetSystemCommand(SceneEdits edits) : EditorAction /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => !context.target.system.isDestroyed && context.target.system is not MissingGameSystem - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => !context.target.isDestroyed + && context.target is not MissingGameSystem ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) - => edits.ResetSystem(context.target.scene, context.target.system); + protected override void Execute(EditorActionContext context) => edits.ResetSystem(context.target); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameLayersSetting.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameLayersSetting.cs index 913ef6807..b4d38694c 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameLayersSetting.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameLayersSetting.cs @@ -1,42 +1,32 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; - using Inno.Core.Settings; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Settings; using Inno.Scene.Layers; -using Inno.Native.ImGui; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [ProjectSettingPath("Project/Scene/Layers")] -internal sealed unsafe class GameLayersSetting : ProjectSettingEditor +internal sealed class GameLayersSetting : ProjectSettingEditor { private const int C_LAYER_NAME_BUFFER_SIZE = 128; - private readonly string[] m_nameBuffers = new string[GameLayer.C_MAX_COUNT]; private string m_error = string.Empty; - private string?[]? m_observedNames; - - /// - /// Gets the stable project-setting identity used for discovery and persistence. - /// + private string? []? m_observedNames; /// /// Gets the presentation section that groups this setting. /// public override string section => "Definitions"; - /// /// Gets the user-facing explanation of this feature or setting. /// - public override string description - => "Define layer names and slots. Stable IDs are generated as projectId.name and are never authored manually."; + public override string description => "Define layer names and slots. Stable IDs are generated as projectId.name and are never authored manually."; /// /// Draws this feature using the current editor presentation context. @@ -46,46 +36,33 @@ public override string description /// protected override void OnDraw(GameLayerCatalog setting) { - string?[] names = CaptureNames(setting); + string? [] names = CaptureNames(setting); SynchronizeBuffers(names); DrawLayerRows(setting, names); if (!string.IsNullOrEmpty(m_error)) - ImGuiWidget.ColoredText(EditorPalette.error, m_error); + EditorUI.current.widgets.ColoredText(EditorPalette.error, m_error); } private void DrawLayerRows( GameLayerCatalog setting, - string?[] names + string? [] names ) { int definedCount = names.Count(static name => !string.IsNullOrWhiteSpace(name)); DrawLayerToolbar(setting, names, definedCount); - NativeImGui.Spacing(); - - ImGuiTableFlags flags = ImGuiTableFlags.RowBg | - ImGuiTableFlags.BordersInnerH | - ImGuiTableFlags.BordersInnerV | - ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoSavedSettings; - NativeImGui.PushStyleVar((int)ImGuiStyleVar.CellPadding, ImGuiWidget.style.cellPadding); - if (!NativeImGui.BeginTable("##game_layer_definitions", 3, (int)flags, default(System.Numerics.Vector2), 0f)) + Ui.Spacing(); + ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerH | ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoSavedSettings; + Ui.PushStyleVar(ImGuiStyleVar.CellPadding, EditorUI.current.widgets.style.cellPadding); + if (!Ui.BeginTable("##game_layer_definitions", 3, flags, default(System.Numerics.Vector2), 0f)) { - NativeImGui.PopStyleVar(); + Ui.PopStyleVar(); return; } + try { - NativeImGui.TableSetupColumn( - "Slot", - (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), - 48f * ImGuiWidget.style.zoom); - NativeImGui.TableSetupColumn( - "Name", - (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize)); - NativeImGui.TableSetupColumn( - "Action", - (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), - 72f * ImGuiWidget.style.zoom); + Ui.TableSetupColumn("Slot", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), 48f * EditorUI.current.widgets.style.zoom); + Ui.TableSetupColumn("Name", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize)); + Ui.TableSetupColumn("Action", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), 72f * EditorUI.current.widgets.style.zoom); DrawLayerTableHeader(); for (int index = 0; index < GameLayer.C_MAX_COUNT; index++) { @@ -95,8 +72,8 @@ private void DrawLayerRows( } finally { - NativeImGui.EndTable(); - NativeImGui.PopStyleVar(); + Ui.EndTable(); + Ui.PopStyleVar(); } } @@ -106,23 +83,23 @@ private void DrawLayerRow( int index ) { var layer = new GameLayer(index); - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); - NativeImGui.AlignTextToFramePadding(); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); + Ui.AlignTextToFramePadding(); InsetPlainCell(); - NativeImGui.TextUnformatted(index.ToString("00", CultureInfo.InvariantCulture)); - _ = NativeImGui.TableSetColumnIndex(1); + Ui.Text(index.ToString("00", CultureInfo.InvariantCulture)); + _ = Ui.TableSetColumnIndex(1); if (layer == GameLayer.defaultLayer) { InsetPlainCell(); - NativeImGui.TextUnformatted(names[index] ?? "Default"); - _ = NativeImGui.TableSetColumnIndex(2); + Ui.Text(names[index] ?? "Default"); + _ = Ui.TableSetColumnIndex(2); InsetPlainCell(); - ImGuiWidget.ColoredText(EditorPalette.textDisabled, "Fixed"); + EditorUI.current.widgets.ColoredText(EditorPalette.textDisabled, "Fixed"); return; } - NativeImGui.PushID(index); + Ui.PushId(index); try { string name = m_nameBuffers[index]; @@ -130,17 +107,10 @@ int index m_nameBuffers[index] = name; if (committed) CommitDefinition(setting, layer, name); - - _ = NativeImGui.TableSetColumnIndex(2); + _ = Ui.TableSetColumnIndex(2); InsetPlainCell(); - Vector2 removeSize = new( - NativeImGui.CalcTextSize("Remove").X, - NativeImGui.GetFrameHeight()); - if (ImGuiWidget.ClickableText( - $"remove_layer_{index}", - "Remove", - removeSize, - "Remove this definition while preserving Scene slot assignments.")) + Vector2 removeSize = new(Ui.MeasureText("Remove").X, Ui.GetFrameHeight()); + if (EditorUI.current.widgets.ClickableText($"remove_layer_{index}", "Remove", removeSize, "Remove this definition while preserving Scene slot assignments.")) { _ = setting.Remove(layer); SynchronizeSlot(setting, layer); @@ -149,7 +119,7 @@ int index } finally { - NativeImGui.PopID(); + Ui.PopId(); } } @@ -158,36 +128,31 @@ private static bool DrawTextField( string hint, ref string value ) { - NativeImGui.SetNextItemWidth(-1f); - NativeImGui.PushStyleColor((int)ImGuiCol.FrameBg, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.FrameBgHovered, EditorPalette.transparent); - NativeImGui.PushStyleColor((int)ImGuiCol.FrameBgActive, EditorPalette.transparent); + Ui.SetNextItemWidth(-1f); + Ui.PushStyleColor(ImGuiCol.FrameBg, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.FrameBgHovered, EditorPalette.transparent); + Ui.PushStyleColor(ImGuiCol.FrameBgActive, EditorPalette.transparent); try { - bool submitted = EditorImGui.InputTextWithHint( - id, - hint, - ref value, - C_LAYER_NAME_BUFFER_SIZE, - ImGuiInputTextFlags.EnterReturnsTrue); - return submitted || NativeImGui.IsItemDeactivatedAfterEdit(); + bool submitted = Ui.InputTextWithHint(id, hint, ref value, C_LAYER_NAME_BUFFER_SIZE, ImGuiInputTextFlags.EnterReturnsTrue); + return submitted || Ui.IsItemDeactivatedAfterEdit(); } finally { - NativeImGui.PopStyleColor(3); + Ui.PopStyleColor(3); } } private void DrawLayerToolbar( GameLayerCatalog setting, - string?[] names, + string? [] names, int definedCount ) { - float spacing = NativeImGui.Raw.GetStyle()->ItemSpacing.X; - ImGuiWidget.LabelChip("Defined", EditorPalette.collectionRowAlternate); - NativeImGui.SameLine(0f, 0f); - DrawReadOnlyCount($"{definedCount} / {GameLayer.C_MAX_COUNT}", 78f * ImGuiWidget.style.zoom); - NativeImGui.SameLine(0f, spacing); + float spacing = Ui.GetLayout().itemSpacing.X; + EditorUI.current.widgets.LabelChip("Defined", EditorPalette.collectionRowAlternate); + Ui.SameLine(0f, 0f); + DrawReadOnlyCount($"{definedCount} / {GameLayer.C_MAX_COUNT}", 78f * EditorUI.current.widgets.style.zoom); + Ui.SameLine(0f, spacing); DrawAddLayer(setting, names, definedCount); } @@ -195,35 +160,23 @@ private static void DrawReadOnlyCount( string value, float width ) { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - Vector2 minimum = NativeImGui.GetCursorScreenPos(); - Vector2 size = new(MathF.Max(1f, width), NativeImGui.GetFrameHeight()); - NativeImGui.Dummy(size); + EditorUiLayout style = Ui.GetLayout(); + Vector2 minimum = Ui.GetCursorScreenPosition(); + Vector2 size = new(MathF.Max(1f, width), Ui.GetFrameHeight()); + Ui.Dummy(size); Vector2 maximum = minimum + size; - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - EditorImGui.DrawFilledRectangle( - minimum, - maximum, - NativeImGui.GetColorU32((int)ImGuiCol.FrameBg), - style->FrameRounding - ); - EditorImGui.DrawRectangle( - minimum, - maximum, - NativeImGui.GetColorU32((int)ImGuiCol.Border), - style->FrameRounding, - ImGuiWidget.style.borderSize - ); - Vector2 textSize = NativeImGui.CalcTextSize(value); - EditorImGui.DrawText(minimum + (size - textSize) * 0.5f, NativeImGui.GetColorU32((int)ImGuiCol.Text), value); + Ui.DrawFilledRectangle(minimum, maximum, Ui.GetStyleColor(ImGuiCol.FrameBg), style.frameRounding); + Ui.DrawRectangle(minimum, maximum, Ui.GetStyleColor(ImGuiCol.Border), style.frameRounding, EditorUI.current.widgets.style.borderSize); + Vector2 textSize = Ui.MeasureText(value); + Ui.DrawText(minimum + (size - textSize) * 0.5f, Ui.GetStyleColor(ImGuiCol.Text), value); } private static void DrawLayerTableHeader() { - uint background = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionHeader); - NativeImGui.TableNextRow(); - NativeImGui.TableSetBgColor((int)ImGuiTableBgTarget.RowBg0, background); - NativeImGui.TableSetBgColor((int)ImGuiTableBgTarget.RowBg1, background); + uint background = Ui.ToPackedColor(EditorPalette.collectionHeader); + Ui.TableNextRow(); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, background); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, background); DrawHeaderCell(0, "Slot"); DrawHeaderCell(1, "Name"); DrawHeaderCell(2, "Action"); @@ -233,32 +186,26 @@ private static void DrawHeaderCell( int column, string label ) { - _ = NativeImGui.TableSetColumnIndex(column); + _ = Ui.TableSetColumnIndex(column); InsetPlainCell(); - NativeImGui.TextUnformatted(label); + Ui.Text(label); } - private static void InsetPlainCell() - => NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + NativeImGui.Raw.GetStyle()->FramePadding.X); - + private static void InsetPlainCell() => Ui.SetCursorPosX(Ui.GetCursorPosX() + Ui.GetLayout().framePadding.X); private void DrawAddLayer( GameLayerCatalog setting, - string?[] names, + string? [] names, int definedCount ) { - NativeImGui.BeginDisabled(definedCount >= GameLayer.C_MAX_COUNT); + Ui.BeginDisabled(definedCount >= GameLayer.C_MAX_COUNT); try { - float selectorWidth = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); - if (!ImGuiWidget.BeginMenuSelector( - "add_game_layer", - "Add layer...", - selectorWidth, - selectorWidth)) + float selectorWidth = MathF.Max(1f, Ui.GetContentRegionAvailable().X); + if (!EditorUI.current.widgets.BeginMenuSelector("add_game_layer", "Add layer...", selectorWidth, selectorWidth)) { return; } + try { for (int index = 1; index < GameLayer.C_MAX_COUNT; index++) @@ -266,18 +213,18 @@ int definedCount if (!string.IsNullOrWhiteSpace(names[index])) continue; string slot = index.ToString("00", CultureInfo.InvariantCulture); - if (NativeImGui.Selectable($"{slot} Layer {index}")) + if (Ui.Selectable($"{slot} Layer {index}")) CommitDefinition(setting, new GameLayer(index), $"Layer {index}"); } } finally { - ImGuiWidget.EndMenuSelector(); + EditorUI.current.widgets.EndMenuSelector(); } } finally { - NativeImGui.EndDisabled(); + Ui.EndDisabled(); } } @@ -301,11 +248,11 @@ string name private void SynchronizeBuffers(IReadOnlyList names) { - if (m_observedNames is not null - && m_observedNames.SequenceEqual(names, StringComparer.Ordinal)) + if (m_observedNames is not null && m_observedNames.SequenceEqual(names, StringComparer.Ordinal)) { return; } + for (int index = 0; index < GameLayer.C_MAX_COUNT; index++) m_nameBuffers[index] = names[index] ?? string.Empty; m_observedNames = names.ToArray(); @@ -319,9 +266,9 @@ GameLayer layer m_observedNames = CaptureNames(setting); } - private static string?[] CaptureNames(GameLayerCatalog stack) + private static string? [] CaptureNames(GameLayerCatalog stack) { - var result = new string?[GameLayer.C_MAX_COUNT]; + var result = new string? [GameLayer.C_MAX_COUNT]; for (int index = 0; index < GameLayer.C_MAX_COUNT; index++) result[index] = stack.GetName(new GameLayer(index)); return result; diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameObjectLayerSelector.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameObjectLayerSelector.cs index fe2149682..c7cc565ac 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameObjectLayerSelector.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Layers/GameObjectLayerSelector.cs @@ -1,16 +1,16 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - +using System.Linq; +using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Scene; using Inno.Scene; using Inno.Scene.Layers; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - /// /// Draws the project layer picker used by GameObject target headers. /// @@ -18,7 +18,6 @@ internal sealed class GameObjectLayerSelector { private readonly SceneProjectSettingsModule m_settings; private readonly SceneEdits m_edits; - /// /// Creates a layer selector backed by the project Settings layer catalog. /// @@ -29,7 +28,7 @@ internal sealed class GameObjectLayerSelector /// The Scene editing service used to record GameObject layer changes. /// /// - /// Thrown when or is . + /// Thrown when or is . /// internal GameObjectLayerSelector( SceneProjectSettingsModule settings, @@ -42,27 +41,33 @@ SceneEdits edits /// /// Draws a compact layer selector with an explicit width. /// - /// + /// /// The GameObject whose layer should be displayed and edited. /// + /// + /// The authoritative shared History owner. + /// /// /// The width reserved for the combo control. /// internal void Draw( - GameObject target, + IReadOnlyList targets, + EditorInteractions interactions, float width ) { - ArgumentNullException.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(targets); + if (targets.Count == 0) + return; + GameObject target = targets[0]; + bool mixed = targets.Any(value => value.layer != target.layer); GameLayerCatalog stack = m_settings.layerStack; string preview = FormatLayerPreview(stack.GetName(target.layer)); - ImGuiWidget.LabelChip("Layer", EditorPalette.inspectorLayerLabel); - NativeImGui.SameLine(0f, 0f); + if (mixed) + preview = "Mixed values"; + EditorUI.current.widgets.LabelChip("Layer", InspectorTargetStyle.layerLabel); + Ui.SameLine(0f, 0f); float selectorWidth = MathF.Max(1f, width); - if (!ImGuiWidget.BeginMenuSelector( - $"game_object_layer_{target.identity.persistentId:N}", - preview, - selectorWidth, - selectorWidth)) + if (!EditorUI.current.widgets.BeginMenuSelector($"game_object_layer_{target.identity.persistentId:N}", preview, selectorWidth, selectorWidth)) { return; } @@ -74,13 +79,18 @@ float width { GameLayerDefinition definition = definitions[i]; string label = FormatLayerLabel(definition.layer, definition.name); - if (NativeImGui.Selectable(label, definition.layer == target.layer)) - m_edits.SetGameObjectLayer(target, definition.layer); + if (Ui.Selectable(label, definition.layer == target.layer)) + { + using EditorHistoryTransaction transaction = interactions.history.BeginTransaction("Set Selection Layer"); + foreach (GameObject selected in targets) + m_edits.SetGameObjectLayer(selected, definition.layer); + transaction.Commit(); + } } } finally { - ImGuiWidget.EndMenuSelector(); + EditorUI.current.widgets.EndMenuSelector(); } } @@ -94,11 +104,9 @@ float width /// when the layer can be selected. /// internal bool IsLayerDefined(GameLayer layer) => m_settings.layerStack.IsDefined(layer); - private static string FormatLayerLabel( GameLayer layer, string name ) => $"({layer.index}) {name}"; - private static string FormatLayerPreview(string? name) => name ?? "Undefined"; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorCardControls.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorCardControls.cs index 15a7c364a..8a9721041 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorCardControls.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorCardControls.cs @@ -1,38 +1,29 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; -using BGCS.Runtime; - -using Inno.Adapter.Presentation.ImGui; using Inno.Core.Identity; using Inno.Core.Logging; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Interactions; using Inno.Editor.Scene; -using Inno.Native.ImGui; using Inno.Scene; using Inno.Scene.Components; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - -internal sealed unsafe class InspectorCardControls +internal sealed class InspectorCardControls { private const string C_COMPONENT_PAYLOAD = "INNO_INSPECTOR_COMPONENT"; private const string C_SYSTEM_PAYLOAD = "INNO_INSPECTOR_SYSTEM"; - private readonly Logger m_log; - internal InspectorCardControls(LogRouter logs) { ArgumentNullException.ThrowIfNull(logs); m_log = logs.CreateLogger(); } - internal float GetWidth(bool canRemove) => EditorWidget.GetCompactClickableTextSize().X * (canRemove ? 2f : 1f); - + internal float GetWidth(bool canRemove) => EditorUI.current.widgets.GetCompactClickableTextSize().X * (canRemove ? 2f : 1f); internal void DrawComponent( SceneEdits edits, GameComponent component, @@ -43,21 +34,15 @@ Action requestRemove ArgumentNullException.ThrowIfNull(component); ArgumentNullException.ThrowIfNull(requestRemove); Guid componentId = component.identity.persistentId; - if (EditorWidget.ClickableText( - $"reset_Component_{componentId:N}", - ImGuiIcon.ArrowRotateLeft, - "Reset Component")) + if (EditorUI.current.widgets.ClickableText($"reset_Component_{componentId:N}", ImGuiIcon.ArrowRotateLeft, "Reset Component")) { TryEdit(() => edits.ResetComponent(component), "Component reset"); } + if (!canRemove) return; - - NativeImGui.SameLine(0f, 0f); - if (EditorWidget.ClickableText( - $"remove_Component_{componentId:N}", - ImGuiIcon.Xmark, - "Remove Component")) + Ui.SameLine(0f, 0f); + if (EditorUI.current.widgets.ClickableText($"remove_Component_{componentId:N}", ImGuiIcon.Xmark, "Remove Component")) { requestRemove(); } @@ -74,18 +59,13 @@ Action requestRemove ArgumentNullException.ThrowIfNull(system); ArgumentNullException.ThrowIfNull(requestRemove); Guid systemId = system.identity.persistentId; - if (EditorWidget.ClickableText( - $"reset_System_{systemId:N}", - ImGuiIcon.ArrowRotateLeft, - "Reset System")) + if (EditorUI.current.widgets.ClickableText($"reset_System_{systemId:N}", ImGuiIcon.ArrowRotateLeft, "Reset System")) { - TryEdit(() => edits.ResetSystem(owner, system), "System reset"); + TryEdit(() => edits.ResetSystem(system), "System reset"); } - NativeImGui.SameLine(0f, 0f); - if (EditorWidget.ClickableText( - $"remove_System_{systemId:N}", - ImGuiIcon.Xmark, - "Remove System")) + + Ui.SameLine(0f, 0f); + if (EditorUI.current.widgets.ClickableText($"remove_System_{systemId:N}", ImGuiIcon.Xmark, "Remove System")) { requestRemove(); } @@ -100,11 +80,7 @@ bool dimmed ArgumentNullException.ThrowIfNull(title); if (component.identity.runtimeIdentity is not RuntimeIdentity identity) return; - _ = EditorWidget.DragDropSource( - C_COMPONENT_PAYLOAD, - identity, - () => DrawDragPreview(title, dimmed), - allowHoldToOpenOthers: false); + _ = EditorUI.current.widgets.DragDropSource(C_COMPONENT_PAYLOAD, identity, () => DrawDragPreview(title, dimmed), allowHoldToOpenOthers: false); } internal void DrawSystemDragSource( @@ -116,11 +92,7 @@ bool dimmed ArgumentNullException.ThrowIfNull(title); if (system.identity.runtimeIdentity is not RuntimeIdentity identity) return; - _ = EditorWidget.DragDropSource( - C_SYSTEM_PAYLOAD, - identity, - () => DrawDragPreview(title, dimmed), - allowHoldToOpenOthers: false); + _ = EditorUI.current.widgets.DragDropSource(C_SYSTEM_PAYLOAD, identity, () => DrawDragPreview(title, dimmed), allowHoldToOpenOthers: false); } internal void DrawComponentDropTarget( @@ -134,29 +106,25 @@ Vector2 cardMaximum ArgumentNullException.ThrowIfNull(interactions); ArgumentNullException.ThrowIfNull(edits); ArgumentNullException.ThrowIfNull(target); - DrawDropTarget( - C_COMPONENT_PAYLOAD, - $"component_{target.identity.persistentId:N}", - interactions, - cardMinimum, - cardMaximum, - source => source is GameComponent component && - ReferenceEquals(component.gameObject, target.gameObject) && - !ReferenceEquals(component, target), - ( - source, - insertAfter - ) => - { - var component = (GameComponent)source; - int sourceIndex = component.gameObject.GetComponentIndex(component); - int insertionBoundary = targetIndex + (insertAfter ? 1 : 0); - int destinationIndex = insertionBoundary > sourceIndex - ? insertionBoundary - 1 - : insertionBoundary; - edits.SetComponentIndex(component, destinationIndex); - }, - "Component reorder"); + DrawDropTarget(C_COMPONENT_PAYLOAD, $"component_{target.identity.persistentId:N}", interactions, cardMinimum, cardMaximum, source => source is GameComponent component + && ReferenceEquals( + component.gameObject, + target.gameObject + ) + && !ReferenceEquals( + component, + target + ), ( + source, + insertAfter + ) => + { + var component = (GameComponent)source; + int sourceIndex = component.gameObject.GetComponentIndex(component); + int insertionBoundary = targetIndex + (insertAfter ? 1 : 0); + int destinationIndex = insertionBoundary > sourceIndex ? insertionBoundary - 1 : insertionBoundary; + edits.SetComponentIndex(component, destinationIndex); + }, "Component reorder"); } internal void DrawSystemDropTarget( @@ -172,29 +140,25 @@ Vector2 cardMaximum ArgumentNullException.ThrowIfNull(edits); ArgumentNullException.ThrowIfNull(scene); ArgumentNullException.ThrowIfNull(target); - DrawDropTarget( - C_SYSTEM_PAYLOAD, - $"system_{target.identity.persistentId:N}", - interactions, - cardMinimum, - cardMaximum, - source => source is GameSystem system && - !ReferenceEquals(system, target) && - IsOwnedBy(scene, system), - ( - source, - insertAfter - ) => - { - var system = (GameSystem)source; - int sourceIndex = scene.GetSystemIndex(system); - int insertionBoundary = targetIndex + (insertAfter ? 1 : 0); - int destinationIndex = insertionBoundary > sourceIndex - ? insertionBoundary - 1 - : insertionBoundary; - edits.SetSystemIndex(scene, system, destinationIndex); - }, - "System reorder"); + DrawDropTarget(C_SYSTEM_PAYLOAD, $"system_{target.identity.persistentId:N}", interactions, cardMinimum, cardMaximum, source => source is GameSystem system + && !ReferenceEquals( + system, + target + ) + && IsOwnedBy( + scene, + system + ), ( + source, + insertAfter + ) => + { + var system = (GameSystem)source; + int sourceIndex = scene.GetSystemIndex(system); + int insertionBoundary = targetIndex + (insertAfter ? 1 : 0); + int destinationIndex = insertionBoundary > sourceIndex ? insertionBoundary - 1 : insertionBoundary; + edits.SetSystemIndex(scene, system, destinationIndex); + }, "System reorder"); } private void DrawDropTarget( @@ -207,38 +171,32 @@ private void DrawDropTarget( Action move, string operation ) { - ImGuiPayload* activePayload = NativeImGui.Raw.GetDragDropPayload(); - using NativeStringLease nativeType = Utf8Codec.EncodeNullTerminated(payloadType, allowsNull: false); - if ((activePayload == null) || - NativeImGui.Raw.IsDataType(activePayload, (sbyte*)nativeType.pointer) == 0 || - cardMaximum.X <= cardMinimum.X || - cardMaximum.Y <= cardMinimum.Y) + if (!Inno.Editor.ImGui.ImGui.HasDragPayload(payloadType) || cardMaximum.X <= cardMinimum.X || cardMaximum.Y <= cardMinimum.Y) { return; } - uint targetId = NativeImGui.GetID($"##inspector_card_drop_{id}"); - bool delivered = EditorWidget.DragDropTarget( + uint targetId = Ui.GetControlId($"##inspector_card_drop_{id}"); + bool delivered = EditorUI.current.widgets.DragDropTarget( payloadType, cardMinimum, cardMaximum, targetId, out RuntimeIdentity identity, out bool isPreviewing, - drawDefaultHighlight: false); + drawDefaultHighlight: false + ); if (!isPreviewing && !delivered) return; - if (!interactions.TryResolveIdentity(identity, out IdentityObject? source) || - source is null || - !accepts(source)) + if (!interactions.TryResolveIdentity(identity, out IdentityObject? source) || source is null || !accepts(source)) { return; } - bool insertAfter = NativeImGui.GetMousePos().Y >= (cardMinimum.Y + cardMaximum.Y) * 0.5f; + bool insertAfter = Ui.GetMousePos().Y >= (cardMinimum.Y + cardMaximum.Y) * 0.5f; float markerY = insertAfter ? cardMaximum.Y : cardMinimum.Y; if (isPreviewing) - EditorWidget.InsertionLine(cardMinimum.X, cardMaximum.X, markerY); + EditorUI.current.widgets.InsertionLine(cardMinimum.X, cardMaximum.X, markerY); if (delivered) TryEdit(() => move(source, insertAfter), operation); } @@ -247,29 +205,20 @@ private static void DrawDragPreview( string title, bool dimmed ) { - Vector2 origin = NativeImGui.GetCursorScreenPos(); - Vector2 padding = EditorWidget.style.inspectorCardHeaderPadding; - Vector2 gripSize = NativeImGui.CalcTextSize(ImGuiIcon.GripVertical); - Vector2 titleSize = NativeImGui.CalcTextSize(title); + Vector2 origin = Ui.GetCursorScreenPosition(); + Vector2 padding = EditorUI.current.widgets.style.cardHeaderPadding; + Vector2 gripSize = Ui.MeasureText(ImGuiIcon.GripVertical); + Vector2 titleSize = Ui.MeasureText(title); Vector2 size = new( - padding.X * 2f + gripSize.X + EditorWidget.style.inspectorHeaderControlSpacing + titleSize.X, - MathF.Max(gripSize.Y, titleSize.Y) + padding.Y * 2f); - ImDrawList* draw = NativeImGui.Raw.GetWindowDrawList(); - EditorImGui.DrawFilledRectangle( - origin, - origin + size, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorCardHeader), - EditorWidget.style.frameRounding + padding.X * 2f + gripSize.X + EditorUI.current.widgets.style.headerControlSpacing + titleSize.X, + MathF.Max(gripSize.Y, titleSize.Y) + padding.Y * 2f ); - uint color = NativeImGui.ColorConvertFloat4ToU32( - dimmed ? EditorPalette.inspectorCardDisabledText : EditorPalette.text); + Ui.DrawFilledRectangle(origin, origin + size, Ui.ToPackedColor(EditorPalette.cardHeader), EditorUI.current.widgets.style.frameRounding); + uint color = Ui.ToPackedColor(dimmed ? EditorPalette.cardDisabledText : EditorPalette.text); Vector2 textOrigin = origin + padding; - EditorImGui.DrawText(textOrigin, color, ImGuiIcon.GripVertical); - EditorImGui.DrawText( - new Vector2(textOrigin.X + gripSize.X + EditorWidget.style.inspectorHeaderControlSpacing, textOrigin.Y), - color, - title); - NativeImGui.Dummy(size); + Ui.DrawText(textOrigin, color, ImGuiIcon.GripVertical); + Ui.DrawText(new Vector2(textOrigin.X + gripSize.X + EditorUI.current.widgets.style.headerControlSpacing, textOrigin.Y), color, title); + Ui.Dummy(size); } private void TryEdit( diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorPanel.cs index 643702398..aa7077e08 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorPanel.cs @@ -1,21 +1,18 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; - using Inno.Core.Logging; using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Inspection; using Inno.Editor.Interactions; using Inno.Editor.PlayMode; using Inno.Editor.Scene; using Inno.Scene; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - /// /// Draws the registered inspector for the current editor selection. /// @@ -29,12 +26,10 @@ internal sealed class InspectorPanel : EditorPanel private readonly InspectorTargetHeader m_targetHeader; private readonly Logger m_log; private string m_failureState = string.Empty; - /// /// Gets whether use window padding is enabled for this implementation. /// public override bool useWindowPadding => false; - /// /// Lets the inspector content child own vertical scrolling. /// @@ -59,7 +54,7 @@ internal sealed class InspectorPanel : EditorPanel /// The application log router used for inspector presentation failures. /// /// - /// Thrown when or is . + /// Thrown when or is . /// internal InspectorPanel( SceneInspectionModule inspection, @@ -85,82 +80,63 @@ LogRouter logs /// protected override void OnDraw(EditorContext context) { - bool visible = NativeImGui.BeginChild( - "##InspectorScrollRegion", - Vector2.Zero, - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.NoSavedSettings)); + bool visible = Ui.BeginChild("##InspectorScrollRegion", Vector2.Zero, (ImGuiChildFlags.None), (ImGuiWindowFlags.NoSavedSettings)); try { if (visible) { - EditorWidget.ConstrainedContent( - "##InspectorContent", - () => DrawContent(context)); + EditorUI.current.widgets.ConstrainedContent("##InspectorContent", () => DrawContent(context)); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } private void DrawContent(EditorContext context) { - object? target = m_targetHeader.Resolve(m_interactions, m_interactions.selection.selectedTarget); + object? target = m_targetHeader.Resolve(m_interactions, m_interactions.selection.count > 1 ? m_interactions.selection.Capture() : m_interactions.selection.selectedTarget); if (target is null) { m_failureState = string.Empty; - EditorWidget.Hint("Select an asset or scene object."); + EditorUI.current.widgets.Hint("Select an asset or scene object."); return; } - if (!m_inspection.TryResolve( - context, - target, - out IInspectionDrawer? drawer, - out InspectionDrawContext? drawContext) || - drawer is null || - drawContext is null) + if (!m_inspection.TryResolve(context, target, out IInspectionDrawer? drawer, out InspectionDrawContext? drawContext) || drawer is null || drawContext is null) { m_failureState = string.Empty; - EditorWidget.Hint($"No inspector drawer is registered for {target.GetType().Name}."); + EditorUI.current.widgets.Hint($"No inspector drawer is registered for {target.GetType().Name}."); return; } bool sceneTarget = target is EngineObject; bool readOnlyScene = target is EngineObject sceneObject && !m_sceneEdits.CanEdit(sceneObject); - string? notice = readOnlyScene - ? "Read-only scene source. Import it into Project Assets to edit." - : sceneTarget && m_playMode.isPlaying - ? "Play Mode edits are temporary. Stopping Play restores the Edit scene." - : null; + string? notice = readOnlyScene ? "Read-only scene source. Import it into Project Assets to edit." : sceneTarget + && m_playMode.isPlaying ? "Play Mode edits are temporary. Stopping Play restores the Edit scene." : null; try { m_targetHeader.Draw(drawer, drawContext, readOnlyScene, notice); - NativeImGui.BeginDisabled(readOnlyScene); + Ui.BeginDisabled(readOnlyScene); try { - EditorWidget.SectionLayout(() => drawer.Draw(drawContext)); + EditorUI.current.widgets.SectionLayout(() => drawer.Draw(drawContext)); } finally { - NativeImGui.EndDisabled(); + Ui.EndDisabled(); } + m_failureState = string.Empty; } catch (Exception exception) { - EditorWidget.ColoredText( - EditorPalette.error, - $"Inspector failed: {exception.Message}"); + EditorUI.current.widgets.ColoredText(EditorPalette.error, $"Inspector failed: {exception.Message}"); string failureState = $"{target.GetType().FullName}:{exception}"; if (!string.Equals(m_failureState, failureState, StringComparison.Ordinal)) { - m_log.Write( - LogLevel.Error, - "Inspector failed for target '{0}': {1}", - [target.GetType().FullName ?? target.GetType().Name, exception]); + m_log.Write(LogLevel.Error, "Inspector failed for target '{0}': {1}", [target.GetType().FullName ?? target.GetType().Name, exception]); m_failureState = failureState; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTargetHeader.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTargetHeader.cs index a25e2e9e7..8a571c9a9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTargetHeader.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTargetHeader.cs @@ -1,30 +1,20 @@ -using System.Runtime.CompilerServices; -using NumericsVector2 = System.Numerics.Vector2; -using NativeVector2 = Inno.Native.ImGui.ImVec2C; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; - using Inno.Editor.Inspection; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Interactions; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - /// /// Draws the persistent identity header shared by every Inspector target type. /// -internal sealed unsafe class InspectorTargetHeader +internal sealed class InspectorTargetHeader { private const int C_NAME_BUFFER_SIZE = 512; - private readonly InspectorLockControl m_lock = new(); - /// /// Resolves the target that should be presented after applying Inspector lock state. /// @@ -41,7 +31,6 @@ internal sealed unsafe class InspectorTargetHeader EditorInteractions interactions, object? selectedTarget ) => m_lock.Resolve(interactions, selectedTarget); - /// /// Draws the common framed header for a resolved Inspector target. /// @@ -65,59 +54,53 @@ internal void Draw( ) { ArgumentNullException.ThrowIfNull(drawer); ArgumentNullException.ThrowIfNull(context); - EditorWidget.HeaderSurface( - "##inspector_target_header", - () => + EditorUI.current.widgets.HeaderSurface("##inspector_target_header", () => + { + Ui.BeginDisabled(readOnly); + try { - NativeImGui.BeginDisabled(readOnly); - try - { - DrawContent(drawer, context); - } - finally - { - NativeImGui.EndDisabled(); - } - if (notice is null) - return; - NativeImGui.Spacing(); - EditorWidget.HelpBox( - notice, - ImGuiIcon.CircleInfo, - new Vector4(0.42f, 0.66f, 0.88f, EditorPalette.opacityOpaque)); - }, - spanWindowPadding: true); + DrawContent(drawer, context); + } + finally + { + Ui.EndDisabled(); + } + + if (notice is null) + return; + Ui.Spacing(); + EditorUI.current.widgets.HelpBox(notice, ImGuiIcon.CircleInfo, new Vector4(0.42f, 0.66f, 0.88f, EditorPalette.opacityOpaque)); + }, spanWindowPadding: true); } private void DrawContent( IInspectionDrawer drawer, InspectionDrawContext context ) { - NativeImGui.PushStyleVar((int)ImGuiStyleVar.ItemSpacing, EditorWidget.style.compactItemSpacing); - NativeImGui.PushStyleVar((int)ImGuiStyleVar.FramePadding, EditorWidget.style.compactFramePadding); + Ui.PushStyleVar(ImGuiStyleVar.ItemSpacing, EditorUI.current.widgets.style.compactItemSpacing); + Ui.PushStyleVar(ImGuiStyleVar.FramePadding, EditorUI.current.widgets.style.compactFramePadding); try { - float rowHeight = NativeImGui.GetFrameHeight(); - float rowSpacing = EditorWidget.style.inspectorTargetHeaderRowSpacing; + float rowHeight = Ui.GetFrameHeight(); + float rowSpacing = EditorUI.current.widgets.style.targetHeaderRowSpacing; float headerContentHeight = rowHeight * 2f + rowSpacing; DrawIcon(drawer.GetIcon(context), headerContentHeight); - NativeImGui.SameLine(0f, -1f); - - NativeImGui.BeginGroup(); + Ui.SameLine(0f, -1f); + Ui.BeginGroup(); try { DrawNameRow(drawer, context, rowHeight); - NativeImGui.SetCursorPosY(NativeImGui.GetCursorPosY() + rowSpacing); + Ui.SetCursorPosY(Ui.GetCursorPosY() + rowSpacing); DrawCustomRow(drawer, context, rowHeight); } finally { - NativeImGui.EndGroup(); + Ui.EndGroup(); } } finally { - NativeImGui.PopStyleVar(2); + Ui.PopStyleVar(2); } } @@ -126,53 +109,42 @@ private void DrawNameRow( InspectionDrawContext context, float rowHeight ) { - Vector2 lockSize = EditorWidget.GetCompactIconSize(); - float itemSpacing = NativeImGui.Raw.GetStyle()->ItemSpacing.X; - float nameWidth = MathF.Max( - 1f, - NativeImGui.GetContentRegionAvail().X - lockSize.X - itemSpacing); + Vector2 lockSize = EditorUI.current.widgets.GetCompactIconSize(); + float itemSpacing = Ui.GetLayout().itemSpacing.X; + float nameWidth = MathF.Max(1f, Ui.GetContentRegionAvailable().X - lockSize.X - itemSpacing); (string name, Action? nameSetter) = drawer.BindName(context); if (nameSetter is not null) { - NativeImGui.SetNextItemWidth(nameWidth); - if (EditorImGui.InputText( - $"##inspector_target_name_{GetTargetId(context.target)}", - ref name, - C_NAME_BUFFER_SIZE, - ImGuiInputTextFlags.None)) + Ui.SetNextItemWidth(nameWidth); + if (Ui.InputText($"##inspector_target_name_{GetTargetId(context.target)}", ref name, C_NAME_BUFFER_SIZE, ImGuiInputTextFlags.None)) { nameSetter(name); } - NativeImGui.SameLine(0f, itemSpacing); + + Ui.SameLine(0f, itemSpacing); } else { - NativeImGui.AlignTextToFramePadding(); - Vector2 textOrigin = NativeImGui.GetCursorScreenPos(); - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - NativeVector2 clipMinimum = Unsafe.BitCast(textOrigin); - NativeVector2 clipMaximum = Unsafe.BitCast(textOrigin + new Vector2(nameWidth, rowHeight)); - NativeImGui.Raw.PushClipRect( - drawList, - &clipMinimum, - &clipMaximum, - 1 - ); + Ui.AlignTextToFramePadding(); + Vector2 textOrigin = Ui.GetCursorScreenPosition(); + EditorDrawList drawList = Ui.GetDrawList(); + drawList.PushClip(textOrigin, textOrigin + new Vector2(nameWidth, rowHeight)); try { - EditorImGui.DrawText(textOrigin, NativeImGui.GetColorU32((int)ImGuiCol.Text), name); + Ui.DrawText(textOrigin, Ui.GetStyleColor(ImGuiCol.Text), name); } finally { - NativeImGui.Raw.PopClipRect(drawList); + drawList.PopClip(); } - NativeImGui.Dummy(new Vector2(nameWidth, rowHeight)); - NativeImGui.SameLine(0f, itemSpacing); + + Ui.Dummy(new Vector2(nameWidth, rowHeight)); + Ui.SameLine(0f, itemSpacing); } string lockIcon = m_lock.isLocked ? ImGuiIcon.Lock : ImGuiIcon.LockOpen; string tooltip = m_lock.isLocked ? "Unlock Inspector" : "Lock Inspector"; - if (EditorWidget.ClickableIcon("inspector_target_lock", lockIcon, tooltip)) + if (EditorUI.current.widgets.ClickableIcon("inspector_target_lock", lockIcon, tooltip)) m_lock.Toggle(context.interactions, context.target); } @@ -180,15 +152,14 @@ private static void DrawIcon( string icon, float slotSize ) { - Vector2 minimum = NativeImGui.GetCursorScreenPos(); - EditorWidget.AddGlyphCentered( - NativeImGui.Raw.GetWindowDrawList(), - NativeImGui.Raw.GetFont(), - NativeImGui.GetFontSize() * EditorWidget.style.inspectorTargetIconScale, + Vector2 minimum = Ui.GetCursorScreenPosition(); + EditorUI.current.widgets.AddGlyphCentered( + Ui.GetFontSize() * EditorUI.current.widgets.style.targetIconScale, icon, minimum + new Vector2(slotSize * 0.5f), - NativeImGui.GetColorU32((int)ImGuiCol.Text)); - NativeImGui.Dummy(new Vector2(slotSize)); + Ui.GetStyleColor(ImGuiCol.Text) + ); + Ui.Dummy(new Vector2(slotSize)); } private static void DrawCustomRow( @@ -196,14 +167,8 @@ private static void DrawCustomRow( InspectionDrawContext context, float rowHeight ) { - ImGuiWindowFlags flags = ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoSavedSettings; - bool visible = NativeImGui.BeginChild( - $"##inspector_target_custom_{GetTargetId(context.target)}", - new Vector2(0f, rowHeight), - (int)ImGuiChildFlags.None, - (int)flags); + ImGuiWindowFlags flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; + bool visible = Ui.BeginChild($"##inspector_target_custom_{GetTargetId(context.target)}", new Vector2(0f, rowHeight), ImGuiChildFlags.None, flags); try { if (visible) @@ -211,10 +176,9 @@ float rowHeight } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } - private static string GetTargetId(object target) - => $"{target.GetType().FullName}_{System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(target)}"; + private static string GetTargetId(object target) => $"{target.GetType().FullName}_{System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(target)}"; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTypeOrigin.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTypeOrigin.cs index 850dc2423..b500609c3 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTypeOrigin.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Presentation/InspectorTypeOrigin.cs @@ -1,27 +1,25 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Reflection; - using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Extensibility.Modules; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - internal static class InspectorTypeOrigin { internal static void Draw(Type type) { ArgumentNullException.ThrowIfNull(type); - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.assetBreadcrumbText); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.contextText); try { - ImGuiWidget.WrappedText("Source: " + Describe(type)); + EditorUI.current.widgets.WrappedText("Source: " + Describe(type)); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/AssetReferencePropertyDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/AssetReferencePropertyDrawer.cs index 45603e72d..6734731c7 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/AssetReferencePropertyDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/AssetReferencePropertyDrawer.cs @@ -1,10 +1,9 @@ - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Numerics; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; @@ -12,22 +11,17 @@ using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [PropertyDrawer(typeof(AssetObject), useForChildren: true, priority: 100)] internal sealed class AssetReferencePropertyDrawer : IPropertyDrawer, IDisposable { private const nuint C_SEARCH_BUFFER_SIZE = 256; - private readonly object m_sync = new(); private readonly AssetPipeline m_assets; private ConditionalWeakTable m_candidatesByType = new(); private readonly Dictionary m_searchByPath = new(StringComparer.Ordinal); - internal AssetReferencePropertyDrawer(AssetPipeline assets) { m_assets = assets ?? throw new ArgumentNullException(nameof(assets)); @@ -60,76 +54,58 @@ public void Draw(PropertyDrawContext context) object? currentValue = context.GetValue(); Guid persistentId = ReadPersistentId(currentValue); AssetCandidate? selected = Array.Find(candidates, candidate => candidate.persistentId == persistentId); - string preview = persistentId == Guid.Empty - ? "None" - : currentValue is AssetObject { isMissing: true } missing - ? $"Missing {missing.GetType().Name} [{persistentId}]" - : selected is null - ? $"Missing ({persistentId})" - : selected.displayName; - - bool open = EditorWidget.BeginBoundedCombo($"##{context.path}", preview); - Vector2 dropMinimum = NativeImGui.GetItemRectMin(); - Vector2 dropMaximum = NativeImGui.GetItemRectMax(); - EditorDropWidgetResult drop = EditorDragDropRenderer.Target( - context.interactions.For( - InspectorInteractionIds.C_ASSET_REFERENCE_AREA, - new AssetReferenceDropTarget( - assetType, - persistentIdToAssign => - { - AssetCandidate? dropped = Array.Find( - candidates, - candidate => candidate.persistentId == persistentIdToAssign); - if (dropped is not null) - AssignAsset(context, assetType, dropped); - }))); + string preview = persistentId == Guid.Empty ? "None" : currentValue is AssetObject { isMissing: true } missing ? $"Missing {missing.GetType().Name} [{persistentId}]" : selected is null ? $"Missing ({persistentId})" : selected.displayName; + bool open = EditorUI.current.widgets.BeginBoundedCombo($"##{context.path}", preview); + Vector2 dropMinimum = Ui.GetItemMinimum(); + Vector2 dropMaximum = Ui.GetItemMaximum(); + EditorDropWidgetResult drop = EditorDragDropRenderer.Target(context.interactions.For(InspectorInteractionIds.C_ASSET_REFERENCE_AREA, new AssetReferenceDropTarget(assetType, persistentIdToAssign => + { + AssetCandidate? dropped = Array.Find(candidates, candidate => candidate.persistentId == persistentIdToAssign); + if (dropped is not null) + AssignAsset(context, assetType, dropped); + }))); if (drop.isPreviewing && drop.status.canDrop) - EditorWidget.DropTargetHighlight(dropMinimum, dropMaximum); - + EditorUI.current.widgets.DropTargetHighlight(dropMinimum, dropMaximum); if (!open) return; try { - string search = m_searchByPath.TryGetValue(context.path, out string? existingSearch) - ? existingSearch - : string.Empty; - _ = EditorWidget.SearchInput( - context.path, - "Search assets...", - ref search, - C_SEARCH_BUFFER_SIZE); + string search = m_searchByPath.TryGetValue(context.path, out string? existingSearch) ? existingSearch : string.Empty; + _ = EditorUI.current.widgets.SearchInput(context.path, "Search assets...", ref search, C_SEARCH_BUFFER_SIZE); m_searchByPath[context.path] = search; - - if (NativeImGui.Selectable("None", persistentId == Guid.Empty)) + if (Ui.Selectable("None", persistentId == Guid.Empty)) context.SetValue(null); - for (int i = 0; i < candidates.Length; i++) { AssetCandidate candidate = candidates[i]; - if (!string.IsNullOrWhiteSpace(search) && - candidate.displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0 && - candidate.fullPath.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) + if (!string.IsNullOrWhiteSpace(search) + && candidate.displayName.IndexOf( + search, + StringComparison.OrdinalIgnoreCase + ) < 0 + && candidate.fullPath.IndexOf( + search, + StringComparison.OrdinalIgnoreCase + ) < 0) { continue; } - if (NativeImGui.Selectable( - $"{candidate.displayName}##{candidate.persistentId:N}", - candidate.persistentId == persistentId)) + if (Ui.Selectable($"{candidate.displayName}##{candidate.persistentId:N}", candidate.persistentId == persistentId)) { AssignAsset(context, assetType, candidate); } - if (NativeImGui.IsItemHovered() && EditorWidget.BeginMenuTooltip()) + + if (Ui.IsItemHovered() && EditorUI.current.widgets.BeginMenuTooltip()) { - NativeImGui.TextUnformatted(candidate.fullPath); - EditorWidget.EndMenuTooltip(); + Ui.Text(candidate.fullPath); + EditorUI.current.widgets.EndMenuTooltip(); } } } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } @@ -139,15 +115,12 @@ private AssetCandidate[] GetCandidates(Type targetAssetType) { if (m_candidatesByType.TryGetValue(targetAssetType, out CandidatesBox? cached)) return cached.candidates; - IReadOnlyList entries = m_assets.GetFileSystemEntries(includeDirectories: false); var candidates = new List(entries.Count); for (int i = 0; i < entries.Count; i++) { AssetFileEntry entry = entries[i]; - if (!m_assets.TryGetAssetType(entry.assetPath, out Type? assetType) || - assetType is null || - !targetAssetType.IsAssignableFrom(assetType)) + if (!m_assets.TryGetAssetType(entry.assetPath, out Type? assetType) || assetType is null || !targetAssetType.IsAssignableFrom(assetType)) { continue; } @@ -156,22 +129,14 @@ assetType is null || { string name = Path.GetFileNameWithoutExtension(entry.assetPath.localPath); string displayName = $"{entry.assetPath.source.value}:{name}"; - candidates.Add(new AssetCandidate( - entry.assetPath, - persistentId, - displayName, - entry.assetPath.ToString())); + candidates.Add(new AssetCandidate(entry.assetPath, persistentId, displayName, entry.assetPath.ToString())); } } candidates.Sort(static ( left, right - ) => - string.Compare( - left.displayName, - right.displayName, - StringComparison.OrdinalIgnoreCase)); + ) => string.Compare(left.displayName, right.displayName, StringComparison.OrdinalIgnoreCase)); AssetCandidate[] result = candidates.ToArray(); m_candidatesByType.Add(targetAssetType, new CandidatesBox(result)); return result; @@ -179,7 +144,6 @@ assetType is null || } private static Guid ReadPersistentId(object? reference) => reference is AssetObject asset ? asset.identity.persistentId : Guid.Empty; - private void AssignAsset( PropertyDrawContext context, Type assetType, diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/EngineObjectReferencePropertyDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/EngineObjectReferencePropertyDrawer.cs index 7006565b7..1a1d05064 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/EngineObjectReferencePropertyDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/EngineObjectReferencePropertyDrawer.cs @@ -1,31 +1,24 @@ - - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - using Inno.Editor.Core; using Inno.Editor.Inspection; using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Runtime; using Inno.Scene; using Inno.Scene.Components; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [PropertyDrawer(typeof(GameObject), useForChildren: true, priority: 100)] [PropertyDrawer(typeof(GameComponent), useForChildren: true, priority: 100)] internal sealed class EngineObjectReferencePropertyDrawer : IPropertyDrawer { private const nuint C_SEARCH_BUFFER_SIZE = 256; - private readonly Dictionary m_searchByPath = new(StringComparer.Ordinal); private readonly RuntimeSession m_runtimeSession; - internal EngineObjectReferencePropertyDrawer(RuntimeSession runtimeSession) { m_runtimeSession = runtimeSession ?? throw new ArgumentNullException(nameof(runtimeSession)); @@ -42,51 +35,34 @@ public void Draw(PropertyDrawContext context) Type targetType = context.propertyType; EngineObject? selected = context.GetValue() as EngineObject; List candidates = CollectCandidates(targetType); - string preview = selected is null || selected.isDestroyed - ? "None" - : GetDisplayName(selected); - - bool open = EditorWidget.BeginBoundedCombo($"##{context.path}", preview); - _ = EditorDragDropRenderer.Target( - context.interactions.For( - InspectorInteractionIds.C_ENGINE_OBJECT_REFERENCE_AREA, - new EngineObjectReferenceDropTarget(targetType, context.SetValue))); - + string preview = selected is null || selected.isDestroyed ? "None" : GetDisplayName(selected); + bool open = EditorUI.current.widgets.BeginBoundedCombo($"##{context.path}", preview); + _ = EditorDragDropRenderer.Target(context.interactions.For(InspectorInteractionIds.C_ENGINE_OBJECT_REFERENCE_AREA, new EngineObjectReferenceDropTarget(targetType, value => context.SetValue(value)))); if (!open) return; - try { - string search = m_searchByPath.TryGetValue(context.path, out string? currentSearch) - ? currentSearch - : string.Empty; - _ = EditorWidget.SearchInput( - context.path, - "Search scene objects...", - ref search, - C_SEARCH_BUFFER_SIZE); + string search = m_searchByPath.TryGetValue(context.path, out string? currentSearch) ? currentSearch : string.Empty; + _ = EditorUI.current.widgets.SearchInput(context.path, "Search scene objects...", ref search, C_SEARCH_BUFFER_SIZE); m_searchByPath[context.path] = search; - - if (NativeImGui.Selectable("None", selected is null)) + if (Ui.Selectable("None", selected is null)) context.SetValue(null); - for (int i = 0; i < candidates.Count; i++) { EngineObject candidate = candidates[i]; string displayName = GetDisplayName(candidate); - if (!string.IsNullOrWhiteSpace(search) && - displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) + if (!string.IsNullOrWhiteSpace(search) && displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) { continue; } - if (NativeImGui.Selectable(displayName, ReferenceEquals(candidate, selected))) + if (Ui.Selectable(displayName, ReferenceEquals(candidate, selected))) context.SetValue(candidate); } } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } @@ -100,7 +76,6 @@ private List CollectCandidates(Type targetType) GameObject gameObject = objects[objectIndex]; if (targetType.IsInstanceOfType(gameObject)) candidates.Add(gameObject); - IReadOnlyList components = gameObject.GetComponents(); for (int componentIndex = 0; componentIndex < components.Count; componentIndex++) { @@ -108,6 +83,7 @@ private List CollectCandidates(Type targetType) candidates.Add(components[componentIndex]); } } + return candidates; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/SceneInspectionPropertyEditService.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/SceneInspectionPropertyEditService.cs index 16f5a223f..735f32075 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/SceneInspectionPropertyEditService.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/PropertyDrawing/SceneInspectionPropertyEditService.cs @@ -1,18 +1,15 @@ using System; - using Inno.Editor.Inspection; using Inno.Editor.Scene; using Inno.Scene; namespace Inno.Editor.Panel.Inspector; - /// /// Adapts generic inspection property edits to compact Scene history records. /// internal sealed class SceneInspectionPropertyEditService : IInspectionPropertyEditService { private readonly SceneEdits m_edits; - /// /// Creates the Scene property edit adapter. /// @@ -24,39 +21,17 @@ internal SceneInspectionPropertyEditService(SceneEdits edits) m_edits = edits ?? throw new ArgumentNullException(nameof(edits)); } - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the operation succeeds or its condition is satisfied; otherwise, . - /// + /// public bool ChangeProperty( object owner, string propertyName, Action mutation, - string historyName + string historyName, + string? mergeKey, + string? componentName ) { ArgumentNullException.ThrowIfNull(owner); - EngineObject sceneObject = owner as EngineObject ?? throw new ArgumentException( - $"Scene property edits require an '{typeof(EngineObject).FullName}' owner.", - nameof(owner)); - return m_edits.ChangeProperty( - sceneObject, - propertyName, - mutation, - historyName, - $"scene-property:{sceneObject.identity.persistentId:N}:{propertyName}"); + EngineObject sceneObject = owner as EngineObject ?? throw new ArgumentException($"Scene property edits require an '{typeof(EngineObject).FullName}' owner.", nameof(owner)); + return m_edits.ChangeProperty(sceneObject, propertyName, mutation, historyName, mergeKey); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Styling/InspectorTargetStyle.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Styling/InspectorTargetStyle.cs new file mode 100644 index 000000000..010d5e1ae --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Styling/InspectorTargetStyle.cs @@ -0,0 +1,17 @@ +using System.Numerics; +using Inno.Editor.ImGui; + +namespace Inno.Editor.Panel.Inspector; + +internal static class InspectorTargetStyle +{ + /// + /// Gets the subdued background of Tag labels in Inspector target headers. + /// + internal static Vector4 tagLabel { get; } = new(0.34f, 0.29f, 0.44f, EditorPalette.opacityOpaque); + + /// + /// Gets the subdued background of Layer labels in Inspector target headers. + /// + internal static Vector4 layerLabel { get; } = new(0.24f, 0.34f, 0.40f, EditorPalette.opacityOpaque); +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/ComponentEditorTarget.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/ComponentEditorTarget.cs deleted file mode 100644 index b42cd8730..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/ComponentEditorTarget.cs +++ /dev/null @@ -1,42 +0,0 @@ - -using System; - -using Inno.Scene; - -namespace Inno.Editor.Panel.Inspector; - -/// -/// Identifies a component together with its owning game object. -/// -internal sealed class ComponentEditorTarget -{ - /// - /// Creates an inspector action target that keeps a component paired with its owning game object. - /// - /// - /// The live game object that owns the component. - /// - /// - /// The component operated on by the inspector action. - /// - /// - /// Thrown when either argument is . - /// - internal ComponentEditorTarget( - GameObject gameObject, - GameComponent component - ) { - this.gameObject = gameObject ?? throw new ArgumentNullException(nameof(gameObject)); - this.component = component ?? throw new ArgumentNullException(nameof(component)); - } - - /// - /// Gets the owning game object. - /// - public GameObject gameObject { get; } - - /// - /// Gets the component. - /// - public GameComponent component { get; } -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectInspectionDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectInspectionDrawer.cs index db21f7c1f..d35ee1c92 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectInspectionDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectInspectionDrawer.cs @@ -1,8 +1,7 @@ - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Core.Serialization; using Inno.Core.Logging; using Inno.Editor.Core; @@ -10,22 +9,17 @@ using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Scene; using Inno.Editor.Settings; using Inno.Scene; using Inno.Scene.Components; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [InspectionDrawer(typeof(GameObject))] internal sealed class GameObjectInspectionDrawer : InspectionDrawer { private const nuint C_SEARCH_BUFFER_SIZE = 256; - private readonly InspectorCardControls m_cardControls; private readonly SceneEdits m_edits; private readonly SerializationRegistry m_serialization; @@ -33,7 +27,6 @@ internal sealed class GameObjectInspectionDrawer : InspectionDrawer private readonly GameObjectLayerSelector m_layerSelector; private readonly EditorSettings m_settings; private string m_componentSearch = string.Empty; - /// /// Creates a GameObject drawer backed by the current project classification settings. /// @@ -53,7 +46,7 @@ internal sealed class GameObjectInspectionDrawer : InspectionDrawer /// The application log router used by component card controls. /// /// - /// Thrown when , , or + /// Thrown when , , or /// is . /// internal GameObjectInspectionDrawer( @@ -65,12 +58,8 @@ LogRouter logs ) { m_edits = edits ?? throw new ArgumentNullException(nameof(edits)); ArgumentNullException.ThrowIfNull(classificationSettings); - m_tagSelector = new GameObjectTagSelector( - classificationSettings, - edits); - m_layerSelector = new GameObjectLayerSelector( - classificationSettings, - edits); + m_tagSelector = new GameObjectTagSelector(classificationSettings, edits); + m_layerSelector = new GameObjectLayerSelector(classificationSettings, edits); m_settings = settings ?? throw new ArgumentNullException(nameof(settings)); m_serialization = serialization ?? throw new ArgumentNullException(nameof(serialization)); m_cardControls = new InspectorCardControls(logs); @@ -79,9 +68,7 @@ LogRouter logs /// /// Gets the icon glyph used to represent this item in the editor. /// - public override string icon => m_settings - .Get("Editor/Appearance/Icons/GameObject") - .GetAsString("value", ImGuiIcon.Cube)!; + public override string icon => m_settings.Get("Editor/Appearance/Icons/GameObject").GetAsString("value", ImGuiIcon.Cube)!; /// /// Binds a caller-visible label to the current inspection target. @@ -98,9 +85,7 @@ LogRouter logs protected override (string name, Action? setter) BindName( InspectionDrawContext context, GameObject target - ) - => (target.name, name => m_edits.RenameGameObject(target, name)); - + ) => (target.name, name => m_edits.RenameGameObject(target, name)); /// /// Renders the header presentation for the current editor frame. /// @@ -115,26 +100,21 @@ protected override void DrawHeader( GameObject target ) { bool active = target.activeSelf; - if (EditorWidget.CompactCheckbox( - $"target_active_{target.identity.persistentId:N}", - ref active, - tooltip: "Enable or disable this GameObject.")) + if (EditorUI.current.widgets.CompactCheckbox($"target_active_{target.identity.persistentId:N}", ref active, tooltip: "Enable or disable this GameObject.")) { m_edits.SetGameObjectActive(target, active); } - NativeImGui.SameLine(0f, -1f); - NativeImGui.TextUnformatted("Active"); - NativeImGui.SameLine(0f, EditorWidget.style.inspectorHeaderSectionSpacing); - float available = NativeImGui.GetContentRegionAvail().X; - float tagLabelWidth = EditorWidget.GetLabelChipSize("Tag").X; - float layerLabelWidth = EditorWidget.GetLabelChipSize("Layer").X; - float controlWidth = MathF.Max( - 1f, - (available - tagLabelWidth - layerLabelWidth - - EditorWidget.style.inspectorHeaderSectionSpacing) * 0.5f); - m_tagSelector.Draw(target, controlWidth); - NativeImGui.SameLine(0f, EditorWidget.style.inspectorHeaderSectionSpacing); - m_layerSelector.Draw(target, controlWidth); + + Ui.SameLine(0f, -1f); + Ui.Text("Active"); + Ui.SameLine(0f, EditorUI.current.widgets.style.headerSectionSpacing); + float available = Ui.GetContentRegionAvailable().X; + float tagLabelWidth = EditorUI.current.widgets.GetLabelChipSize("Tag").X; + float layerLabelWidth = EditorUI.current.widgets.GetLabelChipSize("Layer").X; + float controlWidth = MathF.Max(1f, (available - tagLabelWidth - layerLabelWidth - EditorUI.current.widgets.style.headerSectionSpacing) * 0.5f); + m_tagSelector.Draw([target], context.interactions, controlWidth); + Ui.SameLine(0f, EditorUI.current.widgets.style.headerSectionSpacing); + m_layerSelector.Draw([target], context.interactions, controlWidth); } /// @@ -153,32 +133,28 @@ GameObject gameObject if (!gameObject.isRuntimeValid || !gameObject.scene.isLoaded) { _ = context.interactions.For(context.interactions.focusedArea).Select(); - NativeImGui.TextUnformatted("Selected GameObject is not available in the active scene."); + Ui.Text("Selected GameObject is not available in the active scene."); return; } if (!m_layerSelector.IsLayerDefined(gameObject.layer)) { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.error); - ImGuiWidget.WrappedText( - $"Layer slot {gameObject.layer.index} is not defined in the current project settings. " + - "Choose Default or restore that layer definition."); - NativeImGui.PopStyleColor(); - NativeImGui.Spacing(); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.error); + EditorUI.current.widgets.WrappedText($"Layer slot {gameObject.layer.index} is not defined in the current project settings. " + "Choose Default or restore that layer definition."); + Ui.PopStyleColor(); + Ui.Spacing(); } if (!m_tagSelector.IsTagDefined(gameObject.tag)) { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.error); - ImGuiWidget.WrappedText( - $"Tag '{gameObject.tag}' is not defined in the current Project Settings. " + - "Choose another tag or restore that tag definition."); - NativeImGui.PopStyleColor(); - NativeImGui.Spacing(); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.error); + EditorUI.current.widgets.WrappedText($"Tag '{gameObject.tag}' is not defined in the current Project Settings. " + "Choose another tag or restore that tag definition."); + Ui.PopStyleColor(); + Ui.Spacing(); } - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.ItemSpacing), EditorWidget.style.compactItemSpacing); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.compactFramePadding); + Ui.PushStyleVar((ImGuiStyleVar.ItemSpacing), EditorUI.current.widgets.style.compactItemSpacing); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.compactFramePadding); try { DrawComponents(context, gameObject); @@ -186,7 +162,7 @@ GameObject gameObject } finally { - NativeImGui.PopStyleVar(2); + Ui.PopStyleVar(2); } } @@ -200,127 +176,82 @@ GameObject gameObject GameComponent component = components[i]; Type componentType = component.GetType(); MissingGameComponent? missing = component as MissingGameComponent; - IReadOnlyList properties = missing is null - ? m_serialization.GetProperties(component) - : Array.Empty(); + IReadOnlyList properties = missing is null ? m_serialization.GetProperties(component) : Array.Empty(); string componentId = component.identity.persistentId.ToString("N"); GameBehavior? behavior = component as GameBehavior; - var editorTarget = new ComponentEditorTarget(gameObject, component); + var editorTarget = component; string title = missing?.missingTypeName ?? componentType.Name; bool canRemove = componentType != typeof(Transform); bool dimmed = behavior is { enabled: false }; Vector2 cardMinimum = default; Vector2 headerMaximum = default; - bool open = EditorWidget.CollapsingCard( - componentId, - title, - behavior is not null - ? () => - { - bool enabled = behavior.enabled; - if (EditorWidget.CompactCheckbox( - $"enabled_{componentId}", - ref enabled, - tooltip: "Enable or disable this component.")) - { - _ = m_edits.ChangeProperty( - behavior, - "enabled", - () => behavior.enabled = enabled, - enabled ? "Enable Component" : "Disable Component", - mergeKey: null); - } - } - : null, - () => m_cardControls.DrawComponent( - m_edits, - component, - canRemove, - () => context.interactions - .For( - InspectorInteractionIds.C_COMPONENT_AREA, - editorTarget) - .Enqueue(InspectorInteractionIds.C_REMOVE_COMPONENT)), - dimmed: dimmed, - trailingControlWidth: m_cardControls.GetWidth(canRemove), - drawContextMenu: () => + bool open = EditorUI.current.widgets.CollapsingCard(componentId, title, behavior is not null ? () => + { + bool enabled = behavior.enabled; + if (EditorUI.current.widgets.CompactCheckbox($"enabled_{componentId}", ref enabled, tooltip: "Enable or disable this component.")) { - cardMinimum = NativeImGui.GetItemRectMin(); - headerMaximum = NativeImGui.GetItemRectMax(); - _ = EditorMenuRenderer.ContextMenu( - $"##component_menu_{componentId}", - context.interactions.For(InspectorInteractionIds.C_COMPONENT_AREA, editorTarget)); - m_cardControls.DrawComponentDragSource(component, title, dimmed); - }); - + _ = m_edits.ChangeProperty(behavior, "enabled", () => behavior.enabled = enabled, enabled ? "Enable Component" : "Disable Component", mergeKey: null); + } + } : null, () => m_cardControls.DrawComponent( + m_edits, + component, + canRemove, + () => context.interactions.For(InspectorInteractionIds.C_COMPONENT_AREA, editorTarget).Enqueue(InspectorInteractionIds.C_REMOVE_COMPONENT) + ), dimmed: dimmed, trailingControlWidth: m_cardControls.GetWidth(canRemove), drawContextMenu: () => + { + cardMinimum = Ui.GetItemMinimum(); + headerMaximum = Ui.GetItemMaximum(); + _ = EditorUI.current.menus.ContextMenu($"##component_menu_{componentId}", context.interactions.For(InspectorInteractionIds.C_COMPONENT_AREA, editorTarget)); + m_cardControls.DrawComponentDragSource(component, title, dimmed); + }); Vector2 cardMaximum = headerMaximum; if (open) { - NativeImGui.Unindent(); - EditorWidget.CardBody( - componentId, - () => EditorWidget.SectionLayout(() => + Ui.Unindent(); + EditorUI.current.widgets.CardBody(componentId, () => EditorUI.current.widgets.SectionLayout(() => + { + if (missing is not null) { - if (missing is not null) - { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.error); - ImGuiWidget.WrappedText( - $"Missing component script ({missing.missingType.stableId:D}). " + - "Its serialized state is preserved and will recover automatically when the type returns."); - NativeImGui.PopStyleColor(); - return; - } - if (context.TryDrawInline(component)) - return; - if (properties.Count == 0) - { - InspectorTypeOrigin.Draw(componentType); - return; - } - for (int propertyIndex = 0; propertyIndex < properties.Count; propertyIndex++) - { - context.properties.Draw( - context.editorContext, - component, - $"gameObject.{gameObject.identity.persistentId:N}.{componentId}", - properties[propertyIndex]); - } - }), - dimmed: dimmed); - cardMaximum = NativeImGui.GetItemRectMax(); - NativeImGui.Indent(); - NativeImGui.TreePop(); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.error); + EditorUI.current.widgets.WrappedText($"Missing component script ({missing.missingType.stableId:D}). " + "Its serialized state is preserved and will recover automatically when the type returns."); + Ui.PopStyleColor(); + return; + } + + if (context.TryDrawInline(component)) + return; + if (properties.Count == 0) + { + InspectorTypeOrigin.Draw(componentType); + return; + } + + for (int propertyIndex = 0; propertyIndex < properties.Count; propertyIndex++) + { + context.properties.Draw(context.editorContext, component, $"gameObject.{gameObject.identity.persistentId:N}.{componentId}", properties[propertyIndex]); + } + }), dimmed: dimmed); + cardMaximum = Ui.GetItemMaximum(); + Ui.Indent(); + Ui.TreePop(); } - m_cardControls.DrawComponentDropTarget( - context.interactions, - m_edits, - component, - i, - cardMinimum, - cardMaximum); - NativeImGui.Dummy(new Vector2(0f, EditorWidget.style.inspectorCardSpacing)); + m_cardControls.DrawComponentDropTarget(context.interactions, m_edits, component, i, cardMinimum, cardMaximum); + Ui.Dummy(new Vector2(0f, EditorUI.current.widgets.style.cardSpacing)); } - } private void DrawAddComponent( InspectionDrawContext context, GameObject gameObject ) { - if (EditorWidget.CenteredButton( - "Add Component", - EditorWidget.style.inspectorAddButtonTopPadding)) + if (EditorUI.current.widgets.CenteredButton("Add Component", EditorUI.current.widgets.style.addButtonTopPadding)) { m_componentSearch = string.Empty; - NativeImGui.OpenPopup("##add_component_popup"); + Ui.OpenPopup("##add_component_popup"); } - if (!EditorWidget.BeginSearchPopup( - "##add_component_popup", - ref m_componentSearch, - "Search components...", - C_SEARCH_BUFFER_SIZE)) + if (!EditorUI.current.widgets.BeginSearchPopup("##add_component_popup", ref m_componentSearch, "Search components...", C_SEARCH_BUFFER_SIZE)) { return; } @@ -328,16 +259,12 @@ GameObject gameObject try { EditorInteraction interaction = context.interactions.For(InspectorInteractionIds.C_COMPONENT_AREA, gameObject); - if (EditorMenuRenderer.DrawSearchItems( - interaction, - interaction.BuildMenu().items, - m_componentSearch)) - NativeImGui.CloseCurrentPopup(); + if (EditorUI.current.menus.DrawSearchItems(interaction, interaction.BuildMenu().items, m_componentSearch)) + Ui.CloseCurrentPopup(); } finally { - EditorWidget.EndSearchPopup(); + EditorUI.current.widgets.EndSearchPopup(); } } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectTagSelector.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectTagSelector.cs index 762f83d9f..1478f8a5f 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectTagSelector.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameObjectTagSelector.cs @@ -1,16 +1,15 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - +using System.Linq; +using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Scene; using Inno.Scene; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - /// /// Draws the project-defined tag picker used by GameObject target headers. /// @@ -18,7 +17,6 @@ internal sealed class GameObjectTagSelector { private readonly SceneProjectSettingsModule m_settings; private readonly SceneEdits m_edits; - /// /// Creates a tag selector backed by the effective Project Settings snapshot. /// @@ -29,7 +27,7 @@ internal sealed class GameObjectTagSelector /// The Scene editing service used to record GameObject tag changes. /// /// - /// Thrown when or is . + /// Thrown when or is . /// internal GameObjectTagSelector( SceneProjectSettingsModule settings, @@ -42,48 +40,54 @@ SceneEdits edits /// /// Draws a compact tag selector for one live GameObject. /// - /// + /// /// The GameObject whose tag should be displayed and edited. /// + /// + /// The authoritative shared History owner. + /// /// /// The width reserved for the selector. /// internal void Draw( - GameObject target, + IReadOnlyList targets, + EditorInteractions interactions, float width ) { - ArgumentNullException.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(targets); + if (targets.Count == 0) + return; + GameObject target = targets[0]; + bool mixed = targets.Any(value => value.tag != target.tag); IReadOnlyList tags = m_settings.tagCatalog.GetTags(); - string preview = m_settings.tagCatalog.IsDefined(target.tag) - ? target.tag - : $"{target.tag} (Undefined)"; - EditorWidget.LabelChip("Tag", EditorPalette.inspectorTagLabel); - NativeImGui.SameLine(0f, 0f); + string preview = m_settings.tagCatalog.IsDefined(target.tag) ? target.tag : $"{target.tag} (Undefined)"; + if (mixed) + preview = "Mixed values"; + EditorUI.current.widgets.LabelChip("Tag", InspectorTargetStyle.tagLabel); + Ui.SameLine(0f, 0f); float selectorWidth = MathF.Max(1f, width); - if (!EditorWidget.BeginMenuSelector( - $"game_object_tag_{target.identity.persistentId:N}", - preview, - selectorWidth, - selectorWidth)) + if (!EditorUI.current.widgets.BeginMenuSelector($"game_object_tag_{target.identity.persistentId:N}", preview, selectorWidth, selectorWidth)) { return; } + try { for (int i = 0; i < tags.Count; i++) { string tag = tags[i]; - if (NativeImGui.Selectable( - tag, - string.Equals(target.tag, tag, StringComparison.Ordinal))) + if (Ui.Selectable(tag, string.Equals(target.tag, tag, StringComparison.Ordinal))) { - m_edits.SetGameObjectTag(target, tag); + using EditorHistoryTransaction transaction = interactions.history.BeginTransaction("Set Selection Tag"); + foreach (GameObject selected in targets) + m_edits.SetGameObjectTag(selected, tag); + transaction.Commit(); } } } finally { - EditorWidget.EndMenuSelector(); + EditorUI.current.widgets.EndMenuSelector(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameSceneInspectionDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameSceneInspectionDrawer.cs index b56475cf5..08f698ef5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameSceneInspectionDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameSceneInspectionDrawer.cs @@ -1,8 +1,7 @@ - +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Core.Serialization; using Inno.Core.Logging; using Inno.Editor.Core; @@ -10,27 +9,21 @@ using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Scene; using Inno.Editor.Settings; using Inno.Scene; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [InspectionDrawer(typeof(GameScene))] internal sealed class GameSceneInspectionDrawer : InspectionDrawer { private const nuint C_SEARCH_BUFFER_SIZE = 256; - private readonly InspectorCardControls m_cardControls; private readonly SceneEdits m_edits; private readonly EditorSettings m_settings; private readonly SerializationRegistry m_serialization; private string m_systemSearch = string.Empty; - /// /// Creates a Scene drawer backed by the Scene editing service. /// @@ -47,7 +40,7 @@ internal sealed class GameSceneInspectionDrawer : InspectionDrawer /// The application log router used by component card controls. /// /// - /// Thrown when or is . + /// Thrown when or is . /// internal GameSceneInspectionDrawer( SceneEdits edits, @@ -64,9 +57,7 @@ LogRouter logs /// /// Gets the icon glyph used to represent this item in the editor. /// - public override string icon => m_settings - .Get("Editor/Appearance/Icons/Scene") - .GetAsString("value", ImGuiIcon.Cubes)!; + public override string icon => m_settings.Get("Editor/Appearance/Icons/Scene").GetAsString("value", ImGuiIcon.Cubes)!; /// /// Binds a caller-visible label to the current inspection target. @@ -83,9 +74,7 @@ LogRouter logs protected override (string name, Action? setter) BindName( InspectionDrawContext context, GameScene target - ) - => (target.name, name => m_edits.RenameScene(target, name)); - + ) => (target.name, name => m_edits.RenameScene(target, name)); /// /// Renders the header presentation for the current editor frame. /// @@ -98,9 +87,7 @@ GameScene target protected override void DrawHeader( InspectionDrawContext context, GameScene target - ) - => NativeImGui.TextUnformatted(target.isLoaded ? "Loaded Scene" : "Scene"); - + ) => Ui.Text(target.isLoaded ? "Loaded Scene" : "Scene"); /// /// Renders the value presentation for the current editor frame. /// @@ -117,12 +104,12 @@ GameScene scene if (!scene.isLoaded || scene.isDestroyed) { _ = context.interactions.For(context.interactions.focusedArea).Select(); - NativeImGui.TextUnformatted("Selected scene is no longer loaded."); + Ui.Text("Selected scene is no longer loaded."); return; } - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.ItemSpacing), EditorWidget.style.compactItemSpacing); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.compactFramePadding); + Ui.PushStyleVar((ImGuiStyleVar.ItemSpacing), EditorUI.current.widgets.style.compactItemSpacing); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.compactFramePadding); try { DrawSystems(context, scene); @@ -130,7 +117,7 @@ GameScene scene } finally { - NativeImGui.PopStyleVar(2); + Ui.PopStyleVar(2); } } @@ -143,135 +130,88 @@ GameScene scene { GameSystem system = systems[i]; MissingGameSystem? missing = system as MissingGameSystem; - IReadOnlyList properties = missing is null - ? m_serialization.GetProperties(system) - : Array.Empty(); + IReadOnlyList properties = missing is null ? m_serialization.GetProperties(system) : Array.Empty(); string systemId = system.identity.persistentId.ToString("N"); - var editorTarget = new SystemEditorTarget(scene, system); + var editorTarget = system; string title = missing?.missingTypeName ?? system.GetType().Name; bool dimmed = !system.enabled; Vector2 cardMinimum = default; Vector2 headerMaximum = default; - bool open = EditorWidget.CollapsingCard( - systemId, - title, - missing is null ? () => - { - bool enabled = system.enabled; - if (EditorWidget.CompactCheckbox( - $"enabled_{systemId}", - ref enabled, - tooltip: "Enable or disable this system.")) - { - _ = m_edits.ChangeProperty( - system, - "enabled", - () => system.enabled = enabled, - enabled ? "Enable System" : "Disable System", - mergeKey: null); - } - } : null, - () => m_cardControls.DrawSystem( - m_edits, - scene, - system, - () => context.interactions - .For( - InspectorInteractionIds.C_SYSTEM_AREA, - editorTarget) - .Enqueue(InspectorInteractionIds.C_REMOVE_SYSTEM)), - dimmed: dimmed, - trailingControlWidth: m_cardControls.GetWidth(canRemove: true), - drawContextMenu: () => + bool open = EditorUI.current.widgets.CollapsingCard(systemId, title, missing is null ? () => + { + bool enabled = system.enabled; + if (EditorUI.current.widgets.CompactCheckbox($"enabled_{systemId}", ref enabled, tooltip: "Enable or disable this system.")) { - cardMinimum = NativeImGui.GetItemRectMin(); - headerMaximum = NativeImGui.GetItemRectMax(); - _ = EditorMenuRenderer.ContextMenu( - $"##system_menu_{systemId}", - context.interactions.For(InspectorInteractionIds.C_SYSTEM_AREA, editorTarget)); - m_cardControls.DrawSystemDragSource(system, title, dimmed); - }); + _ = m_edits.ChangeProperty(system, "enabled", () => system.enabled = enabled, enabled ? "Enable System" : "Disable System", mergeKey: null); + } + } : null, () => m_cardControls.DrawSystem( + m_edits, + scene, + system, + () => context.interactions.For(InspectorInteractionIds.C_SYSTEM_AREA, editorTarget).Enqueue(InspectorInteractionIds.C_REMOVE_SYSTEM) + ), dimmed: dimmed, trailingControlWidth: m_cardControls.GetWidth(canRemove: true), drawContextMenu: () => + { + cardMinimum = Ui.GetItemMinimum(); + headerMaximum = Ui.GetItemMaximum(); + _ = EditorUI.current.menus.ContextMenu($"##system_menu_{systemId}", context.interactions.For(InspectorInteractionIds.C_SYSTEM_AREA, editorTarget)); + m_cardControls.DrawSystemDragSource(system, title, dimmed); + }); Vector2 cardMaximum = headerMaximum; if (open) { - NativeImGui.Unindent(); - EditorWidget.CardBody( - systemId, - () => EditorWidget.SectionLayout(() => + Ui.Unindent(); + EditorUI.current.widgets.CardBody(systemId, () => EditorUI.current.widgets.SectionLayout(() => + { + if (missing is not null) + { + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.error); + EditorUI.current.widgets.WrappedText($"Missing system script ({missing.missingType.stableId:D}). " + "Its serialized state is preserved and will recover automatically when the type returns."); + Ui.PopStyleColor(); + return; + } + + if (properties.Count == 0) { - if (missing is not null) - { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.error); - ImGuiWidget.WrappedText( - $"Missing system script ({missing.missingType.stableId:D}). " + - "Its serialized state is preserved and will recover automatically when the type returns."); - NativeImGui.PopStyleColor(); - return; - } - if (properties.Count == 0) - { - InspectorTypeOrigin.Draw(system.GetType()); - return; - } - for (int propertyIndex = 0; propertyIndex < properties.Count; propertyIndex++) - { - context.properties.Draw( - context.editorContext, - system, - $"scene.{scene.identity.persistentId:N}.{systemId}", - properties[propertyIndex]); - } - }), - dimmed: dimmed); - cardMaximum = NativeImGui.GetItemRectMax(); - NativeImGui.Indent(); - NativeImGui.TreePop(); + InspectorTypeOrigin.Draw(system.GetType()); + return; + } + + for (int propertyIndex = 0; propertyIndex < properties.Count; propertyIndex++) + { + context.properties.Draw(context.editorContext, system, $"scene.{scene.identity.persistentId:N}.{systemId}", properties[propertyIndex]); + } + }), dimmed: dimmed); + cardMaximum = Ui.GetItemMaximum(); + Ui.Indent(); + Ui.TreePop(); } - m_cardControls.DrawSystemDropTarget( - context.interactions, - m_edits, - scene, - system, - i, - cardMinimum, - cardMaximum); - NativeImGui.Dummy(new Vector2(0f, EditorWidget.style.inspectorCardSpacing)); + m_cardControls.DrawSystemDropTarget(context.interactions, m_edits, scene, system, i, cardMinimum, cardMaximum); + Ui.Dummy(new Vector2(0f, EditorUI.current.widgets.style.cardSpacing)); } - } private void DrawAddSystem( InspectionDrawContext context, GameScene scene ) { - if (EditorWidget.CenteredButton( - "Add System", - EditorWidget.style.inspectorAddButtonTopPadding)) + if (EditorUI.current.widgets.CenteredButton("Add System", EditorUI.current.widgets.style.addButtonTopPadding)) { m_systemSearch = string.Empty; - NativeImGui.OpenPopup("##add_system_popup"); + Ui.OpenPopup("##add_system_popup"); } - if (!EditorWidget.BeginSearchPopup( - "##add_system_popup", - ref m_systemSearch, - "Search systems...", - C_SEARCH_BUFFER_SIZE)) - return; + if (!EditorUI.current.widgets.BeginSearchPopup("##add_system_popup", ref m_systemSearch, "Search systems...", C_SEARCH_BUFFER_SIZE)) + return; try { EditorInteraction interaction = context.interactions.For(InspectorInteractionIds.C_SYSTEM_AREA, scene); - if (EditorMenuRenderer.DrawSearchItems( - interaction, - interaction.BuildMenu().items, - m_systemSearch)) - NativeImGui.CloseCurrentPopup(); + if (EditorUI.current.menus.DrawSearchItems(interaction, interaction.BuildMenu().items, m_systemSearch)) + Ui.CloseCurrentPopup(); } finally { - EditorWidget.EndSearchPopup(); + EditorUI.current.widgets.EndSearchPopup(); } } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameTagsSetting.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameTagsSetting.cs index e8e2600bb..79ed086e7 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameTagsSetting.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/GameTagsSetting.cs @@ -1,36 +1,25 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Core.Settings; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Settings; using Inno.Scene; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [ProjectSettingPath("Project/Scene/Tags")] -internal sealed unsafe class GameTagsSetting : ProjectSettingEditor +internal sealed class GameTagsSetting : ProjectSettingEditor { private const int C_TAG_BUFFER_SIZE = 128; - private string m_error = string.Empty; private string m_newTag = string.Empty; - - /// - /// Gets the stable project-setting identity used for discovery and persistence. - /// /// /// Gets the presentation section that groups this setting. /// public override string section => "Definitions"; - /// /// Gets the user-facing explanation of this feature or setting. /// @@ -45,33 +34,22 @@ internal sealed unsafe class GameTagsSetting : ProjectSettingEditorItemSpacing.X; - float actionWidth = EditorWidget.GetCompactIconSize().X; - NativeImGui.SetNextItemWidth(MathF.Max( - 1f, - NativeImGui.GetContentRegionAvail().X - actionWidth - spacing)); - bool submit = EditorImGui.InputTextWithHint( - "##new_project_tag", - "New tag", - ref m_newTag, - C_TAG_BUFFER_SIZE, - ImGuiInputTextFlags.EnterReturnsTrue); - NativeImGui.SameLine(0f, spacing); - submit |= EditorWidget.ClickableIcon( - "add_project_tag", - ImGuiIcon.Plus, - "Add project tag"); + float spacing = Ui.GetLayout().itemSpacing.X; + float actionWidth = EditorUI.current.widgets.GetCompactIconSize().X; + Ui.SetNextItemWidth(MathF.Max(1f, Ui.GetContentRegionAvailable().X - actionWidth - spacing)); + bool submit = Ui.InputTextWithHint("##new_project_tag", "New tag", ref m_newTag, C_TAG_BUFFER_SIZE, ImGuiInputTextFlags.EnterReturnsTrue); + Ui.SameLine(0f, spacing); + submit |= EditorUI.current.widgets.ClickableIcon("add_project_tag", ImGuiIcon.Plus, "Add project tag"); if (!submit || string.IsNullOrWhiteSpace(m_newTag)) return; - try { if (!setting.Add(m_newTag)) @@ -89,51 +67,42 @@ private void DrawAddRow(GameTagCatalog setting) private static void DrawTags(GameTagCatalog setting) { IReadOnlyList tags = setting.GetTags(); - ImGuiTableFlags flags = ImGuiTableFlags.RowBg | - ImGuiTableFlags.BordersInnerH | - ImGuiTableFlags.BordersInnerV | - ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoSavedSettings; - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.CellPadding), ImGuiWidget.style.cellPadding); - if (!NativeImGui.BeginTable("##project_tag_definitions", 2, (int)(flags), default(System.Numerics.Vector2), 0f)) + ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerH | ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoSavedSettings; + Ui.PushStyleVar((ImGuiStyleVar.CellPadding), EditorUI.current.widgets.style.cellPadding); + if (!Ui.BeginTable("##project_tag_definitions", 2, flags, default(System.Numerics.Vector2), 0f)) { - NativeImGui.PopStyleVar(); + Ui.PopStyleVar(); return; } + try { - NativeImGui.TableSetupColumn( - "Tag", - (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize)); - NativeImGui.TableSetupColumn( - "Action", - (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), - 72f * ImGuiWidget.style.zoom); + Ui.TableSetupColumn("Tag", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize)); + Ui.TableSetupColumn("Action", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), 72f * EditorUI.current.widgets.style.zoom); DrawTagTableHeader(); for (int i = 0; i < tags.Count; i++) { string tag = tags[i]; bool isDefault = string.Equals(tag, GameObject.defaultTag, StringComparison.Ordinal); - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); - NativeImGui.AlignTextToFramePadding(); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); + Ui.AlignTextToFramePadding(); InsetPlainCell(); - NativeImGui.TextUnformatted(tag); - _ = NativeImGui.TableSetColumnIndex(1); + Ui.Text(tag); + _ = Ui.TableSetColumnIndex(1); InsetPlainCell(); if (isDefault) { - ImGuiWidget.ColoredText(EditorPalette.textDisabled, "Fixed"); + EditorUI.current.widgets.ColoredText(EditorPalette.textDisabled, "Fixed"); continue; } - if (EditorWidget.ClickableText( - $"remove_project_tag_{tag}", - "Remove", - new Vector2( - NativeImGui.CalcTextSize("Remove").X, - NativeImGui.GetFrameHeight()), - "Remove this definition. Existing scene assignments are preserved and become undefined.")) + + if (EditorUI.current.widgets.ClickableText( + $"remove_project_tag_{tag}", + "Remove", + new Vector2(Ui.MeasureText("Remove").X, Ui.GetFrameHeight()), + "Remove this definition. Existing scene assignments are preserved and become undefined." + )) { _ = setting.Remove(tag); } @@ -141,17 +110,17 @@ private static void DrawTags(GameTagCatalog setting) } finally { - NativeImGui.EndTable(); - NativeImGui.PopStyleVar(); + Ui.EndTable(); + Ui.PopStyleVar(); } } private static void DrawTagTableHeader() { - uint background = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionHeader); - NativeImGui.TableNextRow(); - NativeImGui.TableSetBgColor((int)(ImGuiTableBgTarget.RowBg0), background); - NativeImGui.TableSetBgColor((int)(ImGuiTableBgTarget.RowBg1), background); + uint background = Ui.ToPackedColor(EditorPalette.collectionHeader); + Ui.TableNextRow(); + Ui.TableSetBgColor((ImGuiTableBgTarget.RowBg0), background); + Ui.TableSetBgColor((ImGuiTableBgTarget.RowBg1), background); DrawHeaderCell(0, "Tag"); DrawHeaderCell(1, "Action"); } @@ -160,12 +129,10 @@ private static void DrawHeaderCell( int column, string label ) { - _ = NativeImGui.TableSetColumnIndex(column); + _ = Ui.TableSetColumnIndex(column); InsetPlainCell(); - NativeImGui.TextUnformatted(label); + Ui.Text(label); } - private static void InsetPlainCell() - => NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + NativeImGui.Raw.GetStyle()->FramePadding.X); + private static void InsetPlainCell() => Ui.SetCursorPosX(Ui.GetCursorPosX() + Ui.GetLayout().framePadding.X); } diff --git a/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineInspectionDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/PipelineInspectionDrawer.cs similarity index 62% rename from src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineInspectionDrawer.cs rename to src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/PipelineInspectionDrawer.cs index 658d51a3c..0ef4ddaa8 100644 --- a/src/composition/editor/features/Inno.Editor.Rendering/Pipelines/PipelineInspectionDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/PipelineInspectionDrawer.cs @@ -1,3 +1,6 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Rendering; using System; using System.Linq; using Inno.Assets; @@ -6,19 +9,18 @@ using Inno.Editor.Assets; using Inno.Editor.Inspection; using Inno.Rendering; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Assets; -namespace Inno.Editor.Rendering; - +namespace Inno.Editor.Panel.Inspector; [InspectionDrawer(typeof(RenderPipelineAsset))] internal sealed class PipelineInspectionDrawer(IInspectionIconProvider icons) : InspectionDrawer { /// /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + public override string icon => Inno.Editor.ImGui.ImGuiIcon.File; + /// /// Retrieves the current icon from authoritative state. /// @@ -31,13 +33,20 @@ internal sealed class PipelineInspectionDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - RenderPipelineAsset target -) - => context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(target.identity.persistentId, out AssetInfo? info) && info is not null - && documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry) ? icons.GetIcon(entry) : icon; + protected override string GetIcon( + InspectionDrawContext context, + RenderPipelineAsset target + ) => context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + target.identity.persistentId, + out AssetInfo? info + ) + && info is not null + && documents.assets.TryGetFileSystemEntry( + info.assetPath, + out AssetFileEntry entry + ) ? icons.GetIcon(entry) : icon; /// /// Binds a caller-visible label to the current inspection target. /// @@ -50,11 +59,10 @@ RenderPipelineAsset target /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - RenderPipelineAsset target -) - => (target.name, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + RenderPipelineAsset target + ) => (target.name, null); /// /// Renders the header presentation for the current editor frame. /// @@ -64,14 +72,20 @@ RenderPipelineAsset target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - RenderPipelineAsset target -) { - if (context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(target.identity.persistentId, out AssetInfo? info) && info is not null) + protected override void DrawHeader( + InspectionDrawContext context, + RenderPipelineAsset target + ) { + if (context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + target.identity.persistentId, + out AssetInfo? info + ) + && info is not null) PipelineInspector.DrawHeader(context, documents, documents.Open(info.assetPath)); } + /// /// Renders the value presentation for the current editor frame. /// @@ -81,12 +95,17 @@ RenderPipelineAsset target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - RenderPipelineAsset target -) { - if (context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(target.identity.persistentId, out AssetInfo? info) && info is not null) + protected override void Draw( + InspectionDrawContext context, + RenderPipelineAsset target + ) { + if (context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + target.identity.persistentId, + out AssetInfo? info + ) + && info is not null) PipelineInspector.Draw(context, documents, documents.Open(info.assetPath)); } } @@ -97,7 +116,8 @@ internal sealed class PipelineSourceDrawer(IInspectionIconProvider /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + public override string icon => Inno.Editor.ImGui.ImGuiIcon.File; + /// /// Retrieves the current icon from authoritative state. /// @@ -110,10 +130,10 @@ internal sealed class PipelineSourceDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - AssetFileEntry target -) => icons.GetIcon(target); + protected override string GetIcon( + InspectionDrawContext context, + AssetFileEntry target + ) => icons.GetIcon(target); /// /// Checks whether this drawer supports the selected Inspector target. /// @@ -123,8 +143,7 @@ AssetFileEntry target /// /// when the documented condition is satisfied; otherwise, . /// -protected override bool CanInspect(AssetFileEntry target) - => !target.isDirectory && target.extension.Equals(".irenderpipeline", StringComparison.OrdinalIgnoreCase); + protected override bool CanInspect(AssetFileEntry target) => !target.isDirectory && target.extension.Equals(".irenderpipeline", StringComparison.OrdinalIgnoreCase); /// /// Binds a caller-visible label to the current inspection target. /// @@ -137,11 +156,10 @@ protected override bool CanInspect(AssetFileEntry target) /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - AssetFileEntry target -) - => (target.nameWithoutExtension, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + AssetFileEntry target + ) => (target.nameWithoutExtension, null); /// /// Renders the header presentation for the current editor frame. /// @@ -151,13 +169,14 @@ AssetFileEntry target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - AssetFileEntry target -) { + protected override void DrawHeader( + InspectionDrawContext context, + AssetFileEntry target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) PipelineInspector.DrawHeader(context, documents, documents.Open(target.assetPath)); } + /// /// Renders the value presentation for the current editor frame. /// @@ -167,10 +186,10 @@ AssetFileEntry target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - AssetFileEntry target -) { + protected override void Draw( + InspectionDrawContext context, + AssetFileEntry target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) PipelineInspector.Draw(context, documents, documents.Open(target.assetPath)); } @@ -205,39 +224,42 @@ internal static void Draw( Guid id ) { AssetDraftDocuments.Draft draft = documents.drafts.GetDraft(id); - documents.drafts.TouchInspection(id); RenderPipelineAsset pipeline = documents.Read(id); var edits = new DraftEdits(documents, id, pipeline); - ImGuiApi.PushID(id.ToString("N")); + ImGuiApi.PushId(id.ToString("N")); try { - if (Widget.SectionHeader("Render Pipeline", "This source is the configuration authority. Unsaved settings do not change Scene, Game or Player content.")) + if (EditorUI.current.widgets.SectionHeader( + "Render Pipeline", + "This source is the configuration authority. Unsaved settings do not change Scene, Game or Player content." + )) { - Widget.Hint(draft.isDirty ? "Unsaved changes · rendering unchanged" : "Saved · import and activation are separate"); + EditorUI.current.widgets.Hint(draft.isDirty ? "Unsaved changes · rendering unchanged" : "Saved · import and activation are separate"); if (draft.error.Length != 0) - Widget.Hint(draft.error); + EditorUI.current.widgets.Hint(draft.error); if (draft.readOnly) - Widget.Hint("Installed Pipeline · copy into the project to edit"); - Widget.Hint("Pipeline: " + pipeline.pipelineTypeId); + EditorUI.current.widgets.Hint("Installed Pipeline · copy into the project to edit"); + EditorUI.current.widgets.Hint("Pipeline: " + pipeline.pipelineTypeId); DrawSettings("pipeline.settings", pipeline.pipelineState, value => pipeline.pipelineState = value); } - bool featuresOpen = Widget.SectionHeader("Features", "Ordered extension settings belong to this Pipeline. Each enabled feature participates in its render graph."); + bool featuresOpen = EditorUI.current.widgets.SectionHeader( + "Features", + "Ordered extension settings belong to this Pipeline. Each enabled feature participates in its render graph." + ); RenderFeatureConfiguration[] features = pipeline.features; for (int index = 0; featuresOpen && index < features.Length; index++) { int slot = index; - ImGuiApi.PushID(slot); + ImGuiApi.PushId(slot); try { RenderFeatureConfiguration feature = features[slot]; - context.properties.DrawValue(context.editorContext, draft, "pipeline.features." + slot + ".enabled", - feature.featureTypeId, typeof(bool), () => features[slot].enabled, - value => - { - features[slot].enabled = (bool)value!; - pipeline.features = features; - }, edits, draft.readOnly); + context.properties.DrawValue(context.editorContext, draft, "pipeline.features." + slot + ".enabled", feature.featureTypeId, typeof(bool), () => features[slot].enabled, value => + { + features[slot].enabled = (bool)value!; + pipeline.features = features; + }, edits, draft.readOnly); DrawSettings("pipeline.features." + slot, feature.state, value => { features[slot].state = value; @@ -253,6 +275,7 @@ Guid id documents.Replace(id, pipeline); break; } + if (slot + 1 < features.Length) { if (slot > 0) @@ -273,17 +296,16 @@ Guid id } finally { - ImGuiApi.PopID(); + ImGuiApi.PopId(); } } + if (featuresOpen && features.Length == 0) - Widget.Hint("No additional features"); - if (!ImGuiApi.IsAnyItemActive()) - documents.Commit(id); + EditorUI.current.widgets.Hint("No additional features"); } finally { - ImGuiApi.PopID(); + ImGuiApi.PopId(); } void DrawSettings( @@ -296,16 +318,18 @@ Action assign ISerializable? settings = documents.RestoreSettings(state); if (settings is null) { - Widget.Hint("No typed settings"); + EditorUI.current.widgets.Hint("No typed settings"); return; } + var settingsEdits = new SettingsEdits(edits, () => assign(new(documents.assets.CaptureProperties(settings)))); foreach (SerializedProperty property in documents.serialization.GetProperties(settings)) context.properties.DrawDraftProperty(context.editorContext, draft, settings, path, property, settingsEdits, draft.readOnly); } - catch (Exception error) when (PipelineDocuments.Recoverable(error)) + catch (Exception error) when ((error is System.IO.IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(error) is null) { - Widget.Hint("Settings unavailable · " + state.stableTypeId + " · " + error.Message + " · Stored properties and dependencies are retained."); + EditorUI.current.widgets.Hint("Settings unavailable · " + state.stableTypeId + " · " + error.Message + " · Stored properties and dependencies are retained."); } } } @@ -316,32 +340,17 @@ private sealed class DraftEdits( RenderPipelineAsset pipeline ) : IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) { + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) { mutation(); - documents.Replace(id, pipeline, !ImGuiApi.IsAnyItemActive()); + documents.Replace(id, pipeline, mergeKey); return true; } } @@ -351,34 +360,18 @@ private sealed class SettingsEdits( Action capture ) : IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) - => edits.ChangeProperty(owner, propertyName, () => - { - mutation(); - capture(); - }, historyName); + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) => edits.ChangeProperty(owner, propertyName, () => + { + mutation(); + capture(); + }, historyName, mergeKey, componentName); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SelectionInspectionDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SelectionInspectionDrawer.cs new file mode 100644 index 000000000..5d926093a --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SelectionInspectionDrawer.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Core.Identity; +using Inno.Core.Serialization; +using Inno.Editor.ImGui; +using Inno.Editor.Inspection; +using Inno.Editor.Interactions; +using Inno.Editor.Scene; +using Inno.Scene; + +namespace Inno.Editor.Panel.Inspector; +[InspectionDrawer(typeof(EditorSelectionSnapshot))] +internal sealed class SelectionInspectionDrawer( + SceneEdits edits, + SerializationRegistry serialization, + SceneProjectSettingsModule settings +) : InspectionDrawer +{ + private readonly GameObjectTagSelector m_tags = new(settings, edits); + private readonly GameObjectLayerSelector m_layers = new(settings, edits); + private string m_componentSearch = ""; + /// + public override string icon => ImGuiIcon.LayerGroup; + + /// + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) => ($"{target.identities.Count} Selected", null); + /// + protected override void Draw( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) { + var values = new List(target.identities.Count); + target.CopyTargets(context.interactions, values); + if (values.Count != target.identities.Count) + { + EditorUiSession.current.widgets.Hint("The selection includes unavailable or different content. Select compatible scene objects or assets to edit common values."); + return; + } + + if (values.Count == 0) + return; + int primaryIndex = values.FindIndex(value => value.identity.runtimeIdentity == target.primary); + if (primaryIndex > 0) + { + EngineObject primary = values[primaryIndex]; + values.RemoveAt(primaryIndex); + values.Insert(0, primary); + } + bool readOnly = values.Any(value => !edits.CanEdit(value)); + if (readOnly) + EditorUiSession.current.widgets.HelpBox("The selection includes a read-only owner. No batch changes will be applied.", ImGuiIcon.Lock, EditorPalette.warning); + EditorUiSession.current.widgets.Disabled(readOnly, () => + { + if (values.All(value => value is GameObject)) + DrawGameObjects(context, target, values.Cast().ToArray()); + else + DrawCommon(context, target, values, "selection"); + }); + } + + private void DrawGameObjects( + InspectionDrawContext context, + EditorSelectionSnapshot state, + IReadOnlyList targets + ) { + bool active = targets[0].activeSelf; + bool mixed = targets.Any(value => value.activeSelf != active); + EditorUiSession.current.widgets.MixedValue(mixed, () => + { + if (!EditorUiSession.current.widgets.CompactCheckbox("selection.active", ref active, tooltip: "Set the active state of every selected object.")) + return; + using EditorHistoryTransaction transaction = context.interactions.history.BeginTransaction("Set Selected Active"); + foreach (GameObject target in targets) + edits.SetGameObjectActive(target, active); + transaction.Commit(); + }); + EditorUiSession.current.widgets.Hint(mixed ? "Active \u00b7 Mixed values" : "Active"); + float width = MathF.Max(1, Inno.Editor.ImGui.ImGui.GetContentRegionAvailable().X); + m_tags.Draw(targets, context.interactions, width); + m_layers.Draw(targets, context.interactions, width); + IReadOnlyList primary = targets[0].GetComponents(); + var occurrences = new Dictionary(); + foreach (GameComponent component in primary) + { + Type type = component.GetType(); + int occurrence = occurrences.GetValueOrDefault(type); + occurrences[type] = occurrence + 1; + GameComponent? [] matches = targets.Select(value => value.GetComponents().Where(candidate => candidate.GetType() == type).Skip(occurrence).FirstOrDefault()).ToArray(); + if (matches.Any(value => value is null)) + continue; + if (EditorUiSession.current.widgets.SectionHeader($"{type.Name} ({occurrence + 1})", "Common component values. Changes apply atomically to the entire selection.")) + { + string id = $"selection.{type.FullName}.{occurrence}"; + DrawCommon(context, state, matches.Cast().ToArray(), id); + if (Inno.Editor.ImGui.ImGui.Button("Reset Selected##" + id)) + { + using EditorHistoryTransaction transaction = context.interactions.history.BeginTransaction("Reset Components"); + foreach (GameComponent match in matches.Cast()) + edits.ResetComponent(match); + transaction.Commit(); + } + + if (matches.All(value => value is not Inno.Scene.Components.Transform)) + { + Inno.Editor.ImGui.ImGui.SameLine(0, -1); + if (Inno.Editor.ImGui.ImGui.Button("Remove Selected##" + id)) + { + using EditorHistoryTransaction transaction = context.interactions.history.BeginTransaction("Remove Components"); + foreach (GameComponent match in matches.Cast()) + edits.RemoveComponent(match); + transaction.Commit(); + return; + } + } + } + } + + DrawAddComponents(context, targets[0]); + } + + private void DrawAddComponents( + InspectionDrawContext context, + GameObject primary + ) { + var widgets = EditorUiSession.current.widgets; + if (widgets.CenteredButton("Add Component to Selection", widgets.style.addButtonTopPadding)) + { + m_componentSearch = ""; + Inno.Editor.ImGui.ImGui.OpenPopup("selection.add-component"); + } + + if (!widgets.BeginSearchPopup("selection.add-component", ref m_componentSearch, "Search components...", 256)) + return; + try + { + EditorInteraction interaction = context.interactions.For(InspectorInteractionIds.C_COMPONENT_AREA, primary); + if (EditorUiSession.current.menus.DrawSearchItems(interaction, interaction.BuildMenu().items, m_componentSearch)) + Inno.Editor.ImGui.ImGui.CloseCurrentPopup(); + } + finally + { + widgets.EndSearchPopup(); + } + } + + private void DrawCommon( + InspectionDrawContext context, + EditorSelectionSnapshot state, + IReadOnlyList targets, + string path + ) { + if (targets.Any(value => value is not ISerializable)) + { + EditorUiSession.current.widgets.Hint("These objects expose no common serialized properties."); + return; + } + + IReadOnlyList[] properties = targets.Select(value => serialization.GetProperties((ISerializable)value)).ToArray(); + int common = 0; + foreach (SerializedProperty property in properties[0]) + { + if (properties.Any(values => !values.Any(value => value.name == property.name && value.propertyType == property.propertyType))) + continue; + var availability = properties.Select(( + values, + index + ) => context.properties.GetPropertyAvailability( + targets[index], values.Single(value => value.name == property.name && value.propertyType == property.propertyType))).ToArray(); + if (availability.Any(value => !value.isVisible)) + continue; + bool readOnly = availability.Any(value => value.isReadOnly); + common++; + bool mixed = !edits.HaveSamePropertyValues(targets, property.name); + EditorUiSession.current.widgets.MixedValue( + mixed, + () => context.properties.DrawDraftProperty(context.editorContext, state, targets[0], path, property, new SelectionEdits(edits, targets), readOnly) + ); + if (mixed) + EditorUiSession.current.widgets.Hint("Mixed values"); + } + + if (common == 0) + EditorUiSession.current.widgets.Hint("No common editable properties."); + } + + private sealed class SelectionEdits( + SceneEdits edits, + IReadOnlyList targets + ) : IInspectionPropertyEditService + { + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) => edits.ChangeProperties(targets, propertyName, mutation, historyName, mergeKey, componentName); + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SystemEditorTarget.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SystemEditorTarget.cs deleted file mode 100644 index e78339ceb..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/SystemEditorTarget.cs +++ /dev/null @@ -1,42 +0,0 @@ - -using System; - -using Inno.Scene; - -namespace Inno.Editor.Panel.Inspector; - -/// -/// Identifies a system together with its owning scene. -/// -internal sealed class SystemEditorTarget -{ - /// - /// Creates an inspector action target that keeps a system paired with its owning scene. - /// - /// - /// The loaded scene that owns the system. - /// - /// - /// The system operated on by the inspector action. - /// - /// - /// Thrown when either argument is . - /// - internal SystemEditorTarget( - GameScene scene, - GameSystem system - ) { - this.scene = scene ?? throw new ArgumentNullException(nameof(scene)); - this.system = system ?? throw new ArgumentNullException(nameof(system)); - } - - /// - /// Gets the owning scene. - /// - public GameScene scene { get; } - - /// - /// Gets the system. - /// - public GameSystem system { get; } -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/TransformInspectionDrawer.cs b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/TransformInspectionDrawer.cs index f868283fd..919a295e9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/TransformInspectionDrawer.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Inspector/Targets/TransformInspectionDrawer.cs @@ -1,20 +1,21 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; -using Inno.Adapter.Presentation.ImGui; using Inno.Core.Mathematics; using Inno.Editor.ImGui; using Inno.Editor.Inspection; +using Inno.Editor.Interactions; using Inno.Editor.Scene; using Inno.Scene.Components; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Inspector; - [InspectionDrawer(typeof(Transform))] -internal sealed unsafe class TransformInspectionDrawer(SceneEdits edits) : InspectionDrawer +internal sealed class TransformInspectionDrawer( + SceneEdits edits, + EditorInteractions interactions +) : InspectionDrawer { private bool m_world; - /// /// Gets the icon glyph used to represent this item in the editor. /// @@ -35,9 +36,7 @@ internal sealed unsafe class TransformInspectionDrawer(SceneEdits edits) : Inspe protected override (string name, Action? setter) BindName( InspectionDrawContext context, Transform target - ) - => ("Transform", null); - + ) => ("Transform", null); /// /// Renders the value presentation for the current editor frame. /// @@ -51,15 +50,11 @@ protected override void Draw( InspectionDrawContext context, Transform target ) { - if (!EditorWidget.SectionHeader( + if (!EditorUI.current.widgets.SectionHeader( m_world ? "World Space" : "Local Space", - m_world - ? "Position, rotation, and scale include the parent hierarchy. Edits are stored as local values and support Undo/Redo." - : "Position, rotation, and scale are relative to the parent. Rotation is expressed in degrees.", - () => _ = EditorWidget.CompactCheckbox( - "##transform_space", - ref m_world, - tooltip: m_world ? "Switch to Local Space." : "Switch to World Space."))) + m_world ? "Position, rotation, and scale include the parent hierarchy. Edits are stored as local values and support Undo/Redo." : "Position, rotation, and scale are relative to the parent. Rotation is expressed in degrees.", + () => _ = EditorUI.current.widgets.CompactCheckbox("##transform_space", ref m_world, tooltip: m_world ? "Switch to Local Space." : "Switch to World Space.") + )) { return; } @@ -76,24 +71,28 @@ Transform target invertible = false; } } + if (!invertible) - EditorWidget.HelpBox("World editing requires an invertible parent transform. Edit in Local Space or restore the parent's zero scale.", - ImGuiIcon.TriangleExclamation, EditorPalette.warning); - NativeImGui.BeginDisabled(!invertible); + EditorUI.current.widgets.HelpBox( + "World editing requires an invertible parent transform. Edit in Local Space or restore the parent's zero scale.", + ImGuiIcon.TriangleExclamation, + EditorPalette.warning + ); + Ui.BeginDisabled(!invertible); try { Vector3 position = m_world ? target.worldPosition : target.localPosition; Quaternion rotation = m_world ? target.worldRotation : target.localRotation; Vector3 euler = rotation.ToEulerAnglesZYXDegrees(); Vector3 scale = m_world ? target.worldScale : target.localScale; - DrawVector("Position", "Translation in world units.", position, value => Change(target, "localPosition", () => + DrawVector(EditPath(target, "localPosition"), "Position", "Translation in world units.", position, value => Change(target, "localPosition", () => { if (m_world) target.worldPosition = value; else target.localPosition = value; })); - DrawVector("Rotation", "Euler rotation in degrees.", euler, value => Change(target, "localRotation", () => + DrawVector(EditPath(target, "localRotation"), "Rotation", "Euler rotation in degrees.", euler, value => Change(target, "localRotation", () => { Quaternion next = Quaternion.FromEulerAnglesZYXDegrees(value); if (m_world) @@ -101,7 +100,7 @@ Transform target else target.localRotation = next; })); - DrawVector("Scale", "Scale along each axis. One preserves the original size.", scale, value => Change(target, "localScale", () => + DrawVector(EditPath(target, "localScale"), "Scale", "Scale along each axis. One preserves the original size.", scale, value => Change(target, "localScale", () => { if (m_world) target.worldScale = value; @@ -111,7 +110,7 @@ Transform target } finally { - NativeImGui.EndDisabled(); + Ui.EndDisabled(); } } @@ -119,26 +118,42 @@ private void Change( Transform target, string property, Action mutation - ) - => edits.ChangeProperty(target, property, mutation, "Change Transform " + property, - $"transform:{target.identity.persistentId:N}:{property}:{m_world}"); + ) { + string path = EditPath(target, property); + EditorUI.current.widgets.BeginPropertyEdit(interactions.history, path, "Change Transform " + property); + try + { + _ = edits.ChangeProperty(target, property, mutation, "Change Transform " + property, EditorUI.current.widgets.editMergeKey); + } + catch + { + EditorUI.current.widgets.CancelEdits(); + throw; + } + } + private string EditPath( + Transform target, + string property + ) => $"transform:{target.identity.persistentId:N}:{property}:{m_world}"; private static void DrawVector( + string path, string label, string tooltip, Vector3 value, Action apply ) { - EditorWidget.PropertyRow("transform." + label, label, () => + EditorUI.current.widgets.PropertyRow("transform." + label, label, () => { - float width = MathF.Max(1f, (NativeImGui.GetContentRegionAvail().X - NativeImGui.Raw.GetStyle()->ItemSpacing.X * 2f) / 3f); - bool changed = EditorWidget.AxisDragFloat(label, "X", ref value.x, width); - NativeImGui.SameLine(0f, -1f); - changed |= EditorWidget.AxisDragFloat(label, "Y", ref value.y, width); - NativeImGui.SameLine(0f, -1f); - changed |= EditorWidget.AxisDragFloat(label, "Z", ref value.z, width); + float width = MathF.Max(1f, (Ui.GetContentRegionAvailable().X - Ui.GetLayout().itemSpacing.X * 2f) / 3f); + bool changed = EditorUI.current.widgets.AxisDragFloat(label, "X", ref value.x, width); + Ui.SameLine(0f, -1f); + changed |= EditorUI.current.widgets.AxisDragFloat(label, "Y", ref value.y, width); + Ui.SameLine(0f, -1f); + changed |= EditorUI.current.widgets.AxisDragFloat(label, "Z", ref value.z, width); if (changed) apply(value); }, tooltip: tooltip); + EditorUI.current.widgets.CompletePropertyEdit(path); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryCopyTarget.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryCopyTarget.cs index d70a38f5f..684f70cdf 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryCopyTarget.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryCopyTarget.cs @@ -1,37 +1,29 @@ using System; using System.Text; - using Inno.Editor.Diagnostics; namespace Inno.Editor.Panel.Logging; - -internal sealed class ConsoleEntryCopyTarget +internal readonly struct ConsoleEntryCopyTarget { + private readonly EditorConsoleOccurrence m_entry; + private readonly int m_repeatCount; internal ConsoleEntryCopyTarget( EditorConsoleOccurrence entry, int repeatCount ) { - message = entry.displayMessage; - fullText = FormatFullText(entry, repeatCount); + m_entry = entry; + m_repeatCount = repeatCount; } - internal string message { get; } - internal string fullText { get; } + internal string message => m_entry.displayMessage; + internal string fullText => FormatFullText(m_entry, m_repeatCount); private static string FormatFullText( EditorConsoleOccurrence entry, int repeatCount ) { var builder = new StringBuilder(); - builder.Append('[') - .Append(entry.time.ToString("yyyy-MM-dd HH:mm:ss.fff")) - .Append("] [") - .Append(entry.kind) - .Append("] [") - .Append(entry.level) - .Append("] [") - .Append(entry.category) - .Append(']'); + builder.Append('[').Append(entry.time.ToString("yyyy-MM-dd HH:mm:ss.fff")).Append("] [").Append(entry.kind).Append("] [").Append(entry.level).Append("] [").Append(entry.category).Append(']'); builder.Append(' ').Append(entry.displayMessage); if (repeatCount > 1) builder.Append(" (x").Append(repeatCount).Append(')'); @@ -47,6 +39,7 @@ int repeatCount builder.Append(')'); } } + return builder.ToString(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryImGuiIdentity.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryImGuiIdentity.cs index fe0cf7550..1f4c557ba 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryImGuiIdentity.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleEntryImGuiIdentity.cs @@ -1,16 +1,15 @@ -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - internal static class ConsoleEntryImGuiIdentity { internal static void Push(string identity) { - NativeImGui.PushID(identity); + Ui.PushId(identity); } internal static void Pop() { - NativeImGui.PopID(); + Ui.PopId(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.Layout.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.Layout.cs new file mode 100644 index 000000000..fdeced606 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.Layout.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Ui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; + +namespace Inno.Editor.Panel.Logging; + +internal sealed partial class ConsolePanel +{ + private sealed class EntryMeasurement( + ConsoleView.Row row, + bool open, + float height + ) { + internal readonly ConsoleView.Row row = row; + internal readonly bool open = open; + internal float height = height; + internal int remainingMeasurements = open ? 2 : 0; + } + + private Dictionary m_measurements = new(StringComparer.Ordinal); + private EntryMeasurement[] m_layout = []; + private readonly List m_pendingMeasurements = []; + private IReadOnlyList? m_layoutRows; + private float[] m_offsets = [0f]; + private (float width, float frameHeight, float padding, float spacing, float zoom)? m_layoutShape; + private int m_openRevision; + private int m_layoutOpenRevision = -1; + private bool m_scrollFocusedRow; + private readonly Dictionary m_cardSlots = new(StringComparer.Ordinal); + private readonly List m_cardWindowNames = []; + private readonly Stack m_freeCardWindows = []; + private readonly List m_retiredCardKeys = []; + private ulong m_cardFrame; + + private sealed class CardWindowSlot(int index) + { + internal readonly int index = index; + internal ulong lastFrame; + } + + private string AcquireCardWindow(string key) + { + if (!m_cardSlots.TryGetValue(key, out CardWindowSlot? slot)) + { + if (!m_freeCardWindows.TryPop(out int index)) + { + index = m_cardWindowNames.Count; + m_cardWindowNames.Add("##ConsoleEntryCard_" + index); + } + slot = new CardWindowSlot(index); + m_cardSlots.Add(key, slot); + } + slot.lastFrame = m_cardFrame; + return m_cardWindowNames[slot.index]; + } + + private void RetireCardWindows() + { + m_retiredCardKeys.Clear(); + foreach (var entry in m_cardSlots) + if (entry.Value.lastFrame != m_cardFrame) + m_retiredCardKeys.Add(entry.Key); + foreach (string key in m_retiredCardKeys) + { + m_freeCardWindows.Push(m_cardSlots[key].index); + m_cardSlots.Remove(key); + } + m_retiredCardKeys.Clear(); + } + + private void ToggleEntry(string key) + { + if (!m_openEntries.Remove(key)) + m_openEntries.Add(key); + m_openRevision++; + } + + private void ClearLayout() + { + m_measurements.Clear(); + m_layoutRows = null; + m_layout = []; + m_pendingMeasurements.Clear(); + m_offsets = [0f]; + m_layoutShape = null; + m_scrollFocusedRow = false; + m_cardSlots.Clear(); + m_freeCardWindows.Clear(); + for (int index = m_cardWindowNames.Count - 1; index >= 0; index--) + m_freeCardWindows.Push(index); + } + + private void RefreshLayout() + { + var layout = Ui.GetLayout(); + var shape = (width: Ui.GetContentRegionAvailable().X, frameHeight: Ui.GetFrameHeight(), padding: layout.framePadding.Y, + spacing: layout.itemSpacing.Y, zoom: EditorUI.current.widgets.style.zoom); + if (ReferenceEquals(m_layoutRows, m_view.rows) && m_layoutShape == shape && m_layoutOpenRevision == m_openRevision) + return; + bool sameShape = m_layoutShape == shape; + float collapsedHeight = MathF.Truncate(shape.frameHeight + shape.padding * 2f); + var measurements = new Dictionary(m_view.rows.Count, StringComparer.Ordinal); + var entries = new EntryMeasurement[m_view.rows.Count]; + for (int index = 0; index < entries.Length; index++) + { + ConsoleView.Row row = m_view.rows[index]; + bool open = m_openEntries.Contains(row.key); + EntryMeasurement measurement = sameShape && m_measurements.TryGetValue(row.key, out EntryMeasurement? previous) + && ReferenceEquals(previous.row, row) && previous.open == open + ? previous : new(row, open, collapsedHeight); + entries[index] = measurement; + measurements.Add(row.key, measurement); + } + m_measurements = measurements; + m_layout = entries; + m_pendingMeasurements.Clear(); + for (int index = 0; index < entries.Length; index++) + if (entries[index].remainingMeasurements > 0) + m_pendingMeasurements.Add(index); + m_offsets = new float[entries.Length + 1]; + m_layoutRows = m_view.rows; + m_layoutShape = shape; + m_layoutOpenRevision = m_openRevision; + RefreshOffsets(0); + } + + private void RefreshOffsets(int start) + { + float spacing = Ui.GetLayout().itemSpacing.Y; + for (int index = start; index < m_layout.Length; index++) + m_offsets[index + 1] = m_offsets[index] + m_layout[index].height + spacing; + } + + private void DrawVisibleEntries() + { + var clip = Ui.GetClipBounds(); + if (clip.maximum.Y <= clip.minimum.Y || clip.maximum.X <= clip.minimum.X) + return; + RefreshLayout(); + m_cardFrame++; + Vector2 origin = Ui.GetCursorScreenPosition(); + int first = FindLayoutRow(clip.minimum.Y - origin.Y); + int last = FindLayoutRow(clip.maximum.Y - origin.Y); + int focused = m_view.FindIndex(m_focusedKey); + int changedFrom = m_layout.Length; + float adjustment = 0f; + int visibleIndex = first; + int pendingIndex = 0; + bool focusPending = focused >= 0; + while (visibleIndex <= last || pendingIndex < m_pendingMeasurements.Count || focusPending) + { + int index = Math.Min(visibleIndex <= last ? visibleIndex : int.MaxValue, + pendingIndex < m_pendingMeasurements.Count ? m_pendingMeasurements[pendingIndex] : int.MaxValue); + if (focusPending) + index = Math.Min(index, focused); + if (index == visibleIndex) + visibleIndex++; + if (pendingIndex < m_pendingMeasurements.Count && index == m_pendingMeasurements[pendingIndex]) + pendingIndex++; + if (index == focused) + focusPending = false; + EntryMeasurement entry = m_layout[index]; + bool measure = entry.remainingMeasurements > 0; + Ui.SetCursorScreenPosition(origin + new Vector2(0f, m_offsets[index] + adjustment)); + float height = DrawConsoleEntry(entry.row, measure); + if (!float.IsFinite(height) || height <= 0f) + throw new InvalidOperationException("Console row measurement did not produce a finite positive height."); + if (measure) + entry.remainingMeasurements--; + if (height != entry.height) + { + adjustment += height - entry.height; + entry.height = height; + changedFrom = Math.Min(changedFrom, index); + } + if (index == focused && m_scrollFocusedRow) + { + float top = origin.Y + m_offsets[index] + adjustment; + if (top < clip.minimum.Y) + Ui.SetScrollHereY(0f); + else if (top + height > clip.maximum.Y) + Ui.SetScrollHereY(1f); + } + } + for (int index = m_pendingMeasurements.Count - 1; index >= 0; index--) + if (m_layout[m_pendingMeasurements[index]].remainingMeasurements == 0) + m_pendingMeasurements.RemoveAt(index); + if (changedFrom < m_layout.Length) + RefreshOffsets(changedFrom); + m_scrollFocusedRow = false; + // A slot is reusable only after all rows have finished submitting this frame. + RetireCardWindows(); + Ui.SetCursorScreenPosition(origin); + Ui.Dummy(new(1f, MathF.Max(1f, m_offsets[^1] - Ui.GetLayout().itemSpacing.Y))); + } + + private int FindLayoutRow(float position) + { + int index = Array.BinarySearch(m_offsets, position); + return Math.Clamp(index < 0 ? ~index - 1 : index, 0, m_layout.Length - 1); + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.cs index 309cbf27f..df1122689 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanel.cs @@ -1,46 +1,48 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; - using Inno.Core.Logging; using Inno.Editor.Core; using Inno.Editor.Diagnostics; using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - /// /// Displays append-only logs and current diagnostics in the unified editor Console. /// [EditorPanel("diagnostics.console", "Console", order: 400, menuPath: "Diagnostics")] -internal sealed unsafe class ConsolePanel : EditorPanel +internal sealed partial class ConsolePanel : EditorPanel { - #region State +#region State private readonly ConsolePanelContent m_content = new(); private readonly HashSet m_filterLevels = Enum.GetValues().ToHashSet(); private readonly LogLevel[] m_levels = Enum.GetValues(); private readonly HashSet m_openEntries = new(StringComparer.Ordinal); - + private readonly ConsoleView m_view = new(); + private string m_query = string.Empty; + private string? m_focusedKey; + private string? m_selectedKey; + private string? m_contextKey; + private bool m_requestContextMenu; + private bool m_keyboardHandled; + private object? m_focusedCopyTarget; private bool m_collapse = true; private long m_lastRevision = -1; private bool m_requestScrollToBottom = true; private readonly IEditorConsole m_console; private readonly EditorInteractions m_interactions; - #endregion - +#endregion /// /// Lets the Console entries region own vertical scrolling. /// public override bool allowScrolling => false; - #region Lifecycle +#region Lifecycle /// /// Creates the panel. /// @@ -51,7 +53,7 @@ internal sealed unsafe class ConsolePanel : EditorPanel /// The editor interaction entry point used by contextual entry commands. /// /// - /// Thrown when or is . + /// Thrown when or is . /// internal ConsolePanel( IEditorConsole console, @@ -83,6 +85,17 @@ protected override void Restore(EditorState state) m_collapse = state.Get("collapse", true); } + /// + protected override void OnDetach(EditorContext context) + { + ClearFocus(); + m_view.Clear(); + m_openEntries.Clear(); + ClearLayout(); + m_lastRevision = -1; + m_requestScrollToBottom = true; + } + /// /// Draws this feature using the current editor presentation context. /// @@ -91,49 +104,53 @@ protected override void Restore(EditorState state) /// protected override void OnDraw(EditorContext context) { - NativeImGui.BeginChild( - "ConsoleChild", - Vector2.Zero, - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); - DrawToolbar(); - NativeImGui.Separator(); - DrawConsoleRegion(); - NativeImGui.EndChild(); + Ui.BeginChild("ConsoleChild", Vector2.Zero, (ImGuiChildFlags.None), (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); + try + { + DrawToolbar(); + Ui.Separator(); + DrawConsoleRegion(); + } + finally + { + Ui.EndChild(); + } } - #endregion - #region Toolbar +#endregion +#region Toolbar + private static ConsoleStyle consoleStyle => new(EditorUI.current.widgets.style); + private void DrawToolbar() { - bool collapseChanged = NativeImGui.Checkbox("Collapse", ref m_collapse); + if (EditorUI.current.widgets.SearchInput("console", "Search messages, sources and files", ref m_query, 1024)) + m_requestScrollToBottom = false; + bool collapseChanged = Ui.Checkbox("Collapse", ref m_collapse); if (collapseChanged) { m_requestScrollToBottom = true; } - NativeImGui.SameLine(0f, -1f); + Ui.SameLine(0f, -1f); bool filterChanged = DrawFilterCombo(); if (filterChanged) { m_requestScrollToBottom = true; } - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.Button("Clear")) + Ui.SameLine(0f, -1f); + if (Ui.Button("Clear")) { m_console.Clear(); m_openEntries.Clear(); + ClearFocus(); m_requestScrollToBottom = true; } } private bool DrawFilterCombo() { - if (!EditorWidget.BeginBoundedCombo( - "##ConsoleFilter", - "Filter", - ImGuiComboFlags.WidthFitPreview)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##ConsoleFilter", "Filter", ImGuiComboFlags.WidthFitPreview)) { return false; } @@ -143,7 +160,7 @@ private bool DrawFilterCombo() { LogLevel level = m_levels[i]; bool selected = m_filterLevels.Contains(level); - if (!NativeImGui.Checkbox(level.ToString(), ref selected)) + if (!Ui.Checkbox(level.ToString(), ref selected)) { continue; } @@ -160,168 +177,187 @@ private bool DrawFilterCombo() changed = true; } - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); return changed; } - #endregion - - #region Console Region +#endregion +#region Console Region private void DrawConsoleRegion() { - NativeImGui.BeginChild("ConsoleRegion", Vector2.Zero, 0, 0); - - EditorConsoleSnapshot snapshot = m_console.Capture(); - RemoveStaleOpenEntries(snapshot); - bool hasNewEntries = m_lastRevision >= 0 && snapshot.revision != m_lastRevision; - m_lastRevision = snapshot.revision; - - bool scrollAtBottom = NativeImGui.GetScrollY() >= - NativeImGui.GetScrollMaxY() - EditorWidget.style.logAutoScrollTolerance; - bool shouldScrollToBottom = m_requestScrollToBottom || (hasNewEntries && scrollAtBottom); - DrawEntries(snapshot); - - if (shouldScrollToBottom) + Ui.BeginChild("ConsoleRegion", Vector2.Zero, 0, 0); + try { - NativeImGui.SetScrollHereY(1f); - m_requestScrollToBottom = false; + EditorConsoleSnapshot snapshot = m_console.Capture(); + if (m_lastRevision != snapshot.revision) + RemoveStaleOpenEntries(snapshot); + bool viewChanged = m_view.Update(snapshot, m_query, m_filterLevels, m_collapse); + if (viewChanged) + { + int focused = m_view.FindIndex(m_focusedKey); + if (focused < 0) + { + ClearFocus(); + } + else if (m_interactions.focusedArea == LoggingInteractionIds.C_ENTRY_AREA) + FocusRow(m_view.rows[focused], select: false); + } + bool hasNewEntries = m_lastRevision >= 0 && snapshot.revision != m_lastRevision; + m_lastRevision = snapshot.revision; + bool scrollAtBottom = Ui.GetScrollY() >= Ui.GetScrollMaxY() - consoleStyle.autoScrollTolerance; + bool shouldScrollToBottom = m_requestScrollToBottom || (hasNewEntries && scrollAtBottom); + HandleKeyboardNavigation(); + if (m_keyboardHandled) + m_requestScrollToBottom = false; + DrawEntries(snapshot); + DrawEntryContextMenu(); + if (shouldScrollToBottom && !m_keyboardHandled) + { + Ui.SetScrollHereY(1f); + m_requestScrollToBottom = false; + } + } + finally + { + Ui.EndChild(); } + } - NativeImGui.EndChild(); +#endregion +#region Entries + private void ClearFocus() + { + m_focusedKey = null; + m_selectedKey = null; + m_focusedCopyTarget = null; + m_contextKey = null; + m_requestContextMenu = false; + if (m_interactions.focusedArea == LoggingInteractionIds.C_ENTRY_AREA) + m_interactions.For(LoggingInteractionIds.C_ENTRY_AREA).Focus(); } - #endregion - #region Entries private void DrawEntries(EditorConsoleSnapshot snapshot) { if (snapshot.occurrences.Count == 0) { + ClearLayout(); m_content.DrawDisabledHint("No logs or diagnostics yet."); return; } - int visibleCount = 0; - if (m_collapse) - { - for (int i = 0; i < snapshot.groups.Count; i++) - { - EditorConsoleGroup group = snapshot.groups[i]; - if (!m_filterLevels.Contains(group.latest.level)) - continue; - visibleCount++; - DrawConsoleEntry( - group.latest, - group.occurrences, - group.count, - $"group/{group.identity}"); - } - } - else + if (m_view.rows.Count == 0) { - for (int i = 0; i < snapshot.occurrences.Count; i++) - { - EditorConsoleOccurrence occurrence = snapshot.occurrences[i]; - if (!m_filterLevels.Contains(occurrence.level)) - continue; - visibleCount++; - DrawConsoleEntry( - occurrence, - [occurrence], - 1, - $"occurrence/{occurrence.sequence}"); - } + ClearLayout(); + m_content.DrawDisabledHint("No entries match the current search and filters."); + return; } - - if (visibleCount == 0) - m_content.DrawDisabledHint("All logs are filtered out."); + DrawVisibleEntries(); } - #endregion - #region Entry Card - private void DrawConsoleEntry( - EditorConsoleOccurrence entry, - IReadOnlyList occurrences, - int repeatCount, - string identity +#endregion +#region Entry Card + private float DrawConsoleEntry( + ConsoleView.Row row, + bool measure ) { - (Vector4 levelColor, string levelIcon) = m_content.GetLevelVisual(entry.level); + (Vector4 levelColor, string levelIcon) = m_content.GetLevelVisual(row.latest.level); + return DrawEntryCard(row, levelColor, levelIcon, measure); + } - ConsoleEntryImGuiIdentity.Push(identity); - bool open = m_openEntries.Contains(identity); - if (!open) + private float DrawEntryCard( + ConsoleView.Row row, + Vector4 levelColor, + string levelIcon, + bool measure + ) { + bool open = m_openEntries.Contains(row.key); + Vector4 cardBg = open ? m_content.GetExpandedBgColor(levelColor) : m_content.GetCollapsedBgColor(); + Vector4 cardBorder = row.key == m_selectedKey ? EditorPalette.contentAccent + : open ? ConsoleStyle.GetExpandedBorder(levelColor) : m_content.GetCollapsedBorderColor(); + Ui.PushStyleColor((ImGuiCol.FrameBg), cardBg); + Ui.PushStyleColor((ImGuiCol.Border), cardBorder); + Ui.PushStyleVar((ImGuiStyleVar.FrameRounding), Ui.GetLayout().frameRounding); + Ui.PushStyleVar((ImGuiStyleVar.FrameBorderSize), EditorUI.current.widgets.style.borderSize); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.framePadding); + try { - Vector4 collapsedCardBg = m_content.GetCollapsedBgColor(); - Vector4 collapsedCardBorder = m_content.GetCollapsedBorderColor(); - NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), collapsedCardBg); - NativeImGui.PushStyleColor((int)(ImGuiCol.Border), collapsedCardBorder); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameRounding), NativeImGui.Raw.GetStyle()->FrameRounding); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameBorderSize), EditorWidget.style.borderSize); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.framePadding); - - ImGuiChildFlags collapsedChildFlags = ImGuiChildFlags.FrameStyle; - ImGuiWindowFlags collapsedChildWindowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; - float collapsedHeight = NativeImGui.GetFrameHeight() + NativeImGui.Raw.GetStyle()->FramePadding.Y * 2f; - if (NativeImGui.BeginChild("##ConsoleEntryCard", new Vector2(0f, collapsedHeight), (int)(collapsedChildFlags), (int)(collapsedChildWindowFlags))) + ImGuiChildFlags childFlags = ImGuiChildFlags.FrameStyle | (open ? ImGuiChildFlags.AutoResizeY : ImGuiChildFlags.None); + if (open && measure) + childFlags |= ImGuiChildFlags.AlwaysAutoResize; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; + float height = open ? 0f : Ui.GetFrameHeight() + Ui.GetLayout().framePadding.Y * 2f; + bool visible = Ui.BeginChild(AcquireCardWindow(row.key), new Vector2(0f, height), childFlags, windowFlags); + try { - DrawHeader(entry, repeatCount, levelColor, levelIcon, false, collapsedCardBg, out bool toggled); - if (toggled) - m_openEntries.Add(identity); + if (visible) + { + ConsoleEntryImGuiIdentity.Push(row.key); + try + { + DrawHeader(row.latest, row.occurrences.Count, levelColor, levelIcon, open, cardBg, out bool toggled); + if (toggled) + ToggleEntry(row.key); + if (open) + DrawOccurrences(row.occurrences, cardBg); + } + finally + { + ConsoleEntryImGuiIdentity.Pop(); + } + } } - - NativeImGui.EndChild(); - DrawEntryContextMenu(entry, repeatCount); - NativeImGui.PopStyleVar(3); - NativeImGui.PopStyleColor(2); - ConsoleEntryImGuiIdentity.Pop(); - return; + finally + { + Ui.EndChild(); + } + float measuredHeight = Ui.GetItemMaximum().Y - Ui.GetItemMinimum().Y; + if (Ui.IsItemClicked(ImGuiMouseButton.Left) || Ui.IsItemClicked(ImGuiMouseButton.Right)) + FocusRow(row, select: true); + if (Ui.IsItemHovered() && Ui.IsMouseReleased(ImGuiMouseButton.Right)) + { + m_contextKey = row.key; + m_requestContextMenu = true; + } + return measuredHeight; } - - Vector4 cardBg = m_content.GetExpandedBgColor(levelColor); - Vector4 cardBorder = EditorPalette.GetLogExpandedBorder(levelColor); - NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), cardBg); - NativeImGui.PushStyleColor((int)(ImGuiCol.Border), cardBorder); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameRounding), NativeImGui.Raw.GetStyle()->FrameRounding); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameBorderSize), EditorWidget.style.borderSize); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.framePadding); - - ImGuiChildFlags childFlags = ImGuiChildFlags.FrameStyle | ImGuiChildFlags.AutoResizeY; - ImGuiWindowFlags childWindowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; - if (NativeImGui.BeginChild("##ConsoleEntryCard", new Vector2(0f, 0f), (int)(childFlags), (int)(childWindowFlags))) + finally { - DrawHeader(entry, repeatCount, levelColor, levelIcon, true, cardBg, out bool toggled); - if (toggled) - m_openEntries.Remove(identity); + Ui.PopStyleVar(3); + Ui.PopStyleColor(2); + } + } - NativeImGui.PushStyleColor( - (int)(ImGuiCol.Separator), - EditorPalette.GetLogSeparator(cardBg)); - try + private static void DrawOccurrences( + IReadOnlyList occurrences, + Vector4 cardBackground + ) { + Ui.PushStyleColor(ImGuiCol.Separator, ConsoleStyle.GetSeparator(cardBackground)); + try + { + Ui.Separator(); + for (int i = 0; i < occurrences.Count; i++) { - NativeImGui.Separator(); - for (int i = 0; i < occurrences.Count; i++) + if (i > 0) + Ui.Separator(); + Ui.PushId(unchecked((int)occurrences[i].sequence)); + try { - if (i > 0) - NativeImGui.Separator(); - NativeImGui.PushID(unchecked((int)occurrences[i].sequence)); DrawDetail(occurrences[i]); - NativeImGui.PopID(); } - } - finally - { - NativeImGui.PopStyleColor(); + finally + { + Ui.PopId(); + } } } - - NativeImGui.EndChild(); - DrawEntryContextMenu(entry, repeatCount); - NativeImGui.PopStyleVar(3); - NativeImGui.PopStyleColor(2); - ConsoleEntryImGuiIdentity.Pop(); + finally + { + Ui.PopStyleColor(); + } } - #endregion - #region Entry Header / Detail +#endregion +#region Entry Header / Detail private void DrawHeader( EditorConsoleOccurrence entry, int repeatCount, @@ -332,85 +368,83 @@ private void DrawHeader( out bool toggled ) { toggled = false; - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - + EditorUiLayout style = Ui.GetLayout(); string content = isOpen ? entry.displayMessage : m_content.GetFirstLine(entry.displayMessage); string repeatText = repeatCount > 1 ? $" (x{repeatCount})" : string.Empty; string prefix = $"{levelIcon} [{entry.level}] "; string toggleText = isOpen ? "▼" : "▶"; - - float toggleW = NativeImGui.CalcTextSize(toggleText).X + EditorWidget.style.logDisclosurePadding.X * 2f; - float prefixW = NativeImGui.CalcTextSize(prefix).X + toggleW + style->ItemInnerSpacing.X; - float suffixW = NativeImGui.CalcTextSize(repeatText).X; + float toggleW = Ui.MeasureText(toggleText).X + consoleStyle.disclosurePadding.X * 2f; + float prefixW = Ui.MeasureText(prefix).X + toggleW + style.itemInnerSpacing.X; + float suffixW = Ui.MeasureText(repeatText).X; float suffixColW = MathF.Max(1f, suffixW); - float tableOuterW = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); + float tableOuterW = MathF.Max(1f, Ui.GetContentRegionAvailable().X); float contentW = MathF.Max(1f, tableOuterW - prefixW - suffixColW); - if (!isOpen) { content = m_content.FitTextWithEllipsis(m_content.GetFirstLine(entry.displayMessage), contentW); } Vector2 tableOuterSize = new(tableOuterW, 0f); - ImGuiTableFlags flags = - ImGuiTableFlags.SizingFixedFit | - ImGuiTableFlags.NoHostExtendX | - ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoPadInnerX | - ImGuiTableFlags.NoSavedSettings; - - if (NativeImGui.BeginTable("##HeaderTable", 3, (int)(flags), tableOuterSize)) + ImGuiTableFlags flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX | ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoPadInnerX | ImGuiTableFlags.NoSavedSettings; + if (Ui.BeginTable("##HeaderTable", 3, flags, tableOuterSize)) { - NativeImGui.TableSetupColumn("##prefix", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), prefixW); - NativeImGui.TableSetupColumn("##content", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), contentW); - NativeImGui.TableSetupColumn("##suffix", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), suffixColW); - - NativeImGui.TableNextRow(); - if (isOpen) + try { - uint headerBgU32 = NativeImGui.ColorConvertFloat4ToU32(headerBgColor); - NativeImGui.TableSetBgColor((int)(ImGuiTableBgTarget.RowBg0), headerBgU32); - } + Ui.TableSetupColumn("##prefix", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), prefixW); + Ui.TableSetupColumn("##content", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), contentW); + Ui.TableSetupColumn("##suffix", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), suffixColW); + Ui.TableNextRow(); + if (isOpen) + { + uint headerBgU32 = Ui.ToPackedColor(headerBgColor); + Ui.TableSetBgColor((ImGuiTableBgTarget.RowBg0), headerBgU32); + } - _ = NativeImGui.TableSetColumnIndex(0); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorWidget.style.logDisclosurePadding); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameBorderSize), 0f); - NativeImGui.PushStyleColor((int)(ImGuiCol.Button), EditorPalette.logToggle); - NativeImGui.PushStyleColor((int)(ImGuiCol.ButtonHovered), EditorPalette.logToggleHovered); - NativeImGui.PushStyleColor((int)(ImGuiCol.ButtonActive), EditorPalette.logToggleActive); - if (NativeImGui.SmallButton($"{toggleText}##HeaderToggle")) - { - toggled = true; - } - NativeImGui.PopStyleColor(3); - NativeImGui.PopStyleVar(2); - NativeImGui.SameLine(0f, -1f); - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), levelColor); - NativeImGui.TextUnformatted(prefix); - NativeImGui.PopStyleColor(); - - _ = NativeImGui.TableSetColumnIndex(1); - if (isOpen) - { - NativeImGui.PushTextWrapPos(0f); - NativeImGui.TextUnformatted(content); - NativeImGui.PopTextWrapPos(); - } - else - { - NativeImGui.TextUnformatted(content); - } + _ = Ui.TableSetColumnIndex(0); + Ui.PushStyleVar((ImGuiStyleVar.FramePadding), consoleStyle.disclosurePadding); + Ui.PushStyleVar((ImGuiStyleVar.FrameBorderSize), 0f); + Ui.PushStyleColor((ImGuiCol.Button), ConsoleStyle.toggle); + Ui.PushStyleColor((ImGuiCol.ButtonHovered), ConsoleStyle.toggleHovered); + Ui.PushStyleColor((ImGuiCol.ButtonActive), ConsoleStyle.toggleActive); + try + { + if (Ui.SmallButton($"{toggleText}##HeaderToggle") && !m_keyboardHandled) + { + toggled = true; + } + + } + finally + { + Ui.PopStyleColor(3); + Ui.PopStyleVar(2); + } + Ui.SameLine(0f, -1f); + EditorUI.current.widgets.ColoredText(levelColor, prefix); + _ = Ui.TableSetColumnIndex(1); + if (isOpen) + { + EditorUI.current.widgets.WrappedText(content); + } + else + { + Ui.Text(content); + } + + _ = Ui.TableSetColumnIndex(2); + if (!string.IsNullOrEmpty(repeatText)) + { + float remainW = Ui.GetContentRegionAvailable().X; + float suffixOffsetX = MathF.Max(0f, remainW - suffixW); + Ui.SetCursorPosX(Ui.GetCursorPosX() + suffixOffsetX); + Ui.Text(repeatText); + } - _ = NativeImGui.TableSetColumnIndex(2); - if (!string.IsNullOrEmpty(repeatText)) + } + finally { - float remainW = NativeImGui.GetContentRegionAvail().X; - float suffixOffsetX = MathF.Max(0f, remainW - suffixW); - NativeImGui.SetCursorPosX(NativeImGui.GetCursorPosX() + suffixOffsetX); - NativeImGui.TextUnformatted(repeatText); + Ui.EndTable(); } - - NativeImGui.EndTable(); } } @@ -424,16 +458,11 @@ private static void DrawDetail(EditorConsoleOccurrence entry) } string fileText = string.IsNullOrWhiteSpace(entry.file) ? "-" : entry.file; - string fileWithLineText = entry.line > 0 - ? entry.column > 0 - ? $"{fileText}:{entry.line}:{entry.column}" - : $"{fileText}:{entry.line}" - : fileText; - + string fileWithLineText = entry.line > 0 ? entry.column > 0 ? $"{fileText}:{entry.line}:{entry.column}" : $"{fileText}:{entry.line}" : fileText; // These cards may be culled between frames; do not rely on a previous frame's auto-fit width. - float labelWidth = NativeImGui.CalcTextSize("Session:").X; - float availableWidth = NativeImGui.GetContentRegionAvail().X; - if (availableWidth < labelWidth + NativeImGui.CalcTextSize("Diagnostic").X + NativeImGui.Raw.GetStyle()->CellPadding.X * 4) + float labelWidth = Ui.MeasureText("Session:").X; + float availableWidth = Ui.GetContentRegionAvailable().X; + if (availableWidth < labelWidth + Ui.MeasureText("Diagnostic").X + Ui.GetLayout().cellPadding.X * 4) { DrawStackedField("Kind:", entry.kind.ToString()); DrawStackedField("File:", fileWithLineText); @@ -442,38 +471,95 @@ private static void DrawDetail(EditorConsoleOccurrence entry) DrawStackedField("Session:", entry.sessionId.ToString()); DrawStackedField("Time:", timeText); } - else if (NativeImGui.BeginTable("##ConsoleEntryDetails", 2, (int)(ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings), - new Vector2(availableWidth, 0))) + else if (Ui.BeginTable("##ConsoleEntryDetails", 2, (ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings), new Vector2(availableWidth, 0))) { - NativeImGui.TableSetupColumn("##label", (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), labelWidth); - NativeImGui.TableSetupColumn("##value", (int)(ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), 1f); - DrawDetailFieldRow("Kind:", entry.kind.ToString()); - DrawDetailFieldRow("File:", fileWithLineText); - DrawDetailFieldRow("Source:", sourceText); - if (entry.sessionId.isAssigned) - DrawDetailFieldRow("Session:", entry.sessionId.ToString()); - DrawDetailFieldRow("Time:", timeText); - NativeImGui.EndTable(); + try + { + Ui.TableSetupColumn("##label", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), labelWidth); + Ui.TableSetupColumn("##value", (ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.NoResize), 1f); + DrawDetailFieldRow("Kind:", entry.kind.ToString()); + DrawDetailFieldRow("File:", fileWithLineText); + DrawDetailFieldRow("Source:", sourceText); + if (entry.sessionId.isAssigned) + DrawDetailFieldRow("Session:", entry.sessionId.ToString()); + DrawDetailFieldRow("Time:", timeText); + } + finally + { + Ui.EndTable(); + } } if (!string.IsNullOrWhiteSpace(entry.stackTrace)) { - NativeImGui.Separator(); - NativeImGui.PushTextWrapPos(0f); - NativeImGui.TextUnformatted(entry.stackTrace); - NativeImGui.PopTextWrapPos(); + Ui.Separator(); + EditorUI.current.widgets.WrappedText(entry.stackTrace); } } - private void DrawEntryContextMenu( - EditorConsoleOccurrence entry, - int repeatCount + private void HandleKeyboardNavigation() + { + int current = m_view.FindIndex(m_focusedKey); + int pageSize = Math.Max(1, (int)(Ui.GetContentRegionAvailable().Y / Math.Max(1f, Ui.GetFrameHeight()))); + ListNavigationResult navigation = EditorUI.current.widgets.NavigateList(m_interactions, m_view.rows.Count, current, pageSize); + m_keyboardHandled = navigation.activate || navigation.selectionRequested || navigation.focusChanged; + m_scrollFocusedRow = navigation.focusChanged || navigation.activate; + if (navigation.focusedIndex >= 0 && (navigation.focusChanged || navigation.selectionRequested || navigation.activate)) + { + ConsoleView.Row row = m_view.rows[navigation.focusedIndex]; + FocusRow(row, navigation.selectionRequested || navigation.activate); + if (navigation.activate) + { + ToggleEntry(row.key); + } + } + if (navigation.cancel) + ClearFocus(); + } + + private void FocusRow( + ConsoleView.Row row, + bool select ) { - _ = EditorMenuRenderer.ContextMenu( - "##ConsoleEntryContextMenu", - m_interactions.For( - LoggingInteractionIds.C_ENTRY_AREA, - new ConsoleEntryCopyTarget(entry, repeatCount))); + m_focusedKey = row.key; + if (select) + m_selectedKey = row.key; + // The view owns this neutral boxed payload while Core keeps only a weak action target. + m_focusedCopyTarget = row.copyTarget; + m_interactions.For(LoggingInteractionIds.C_ENTRY_AREA, m_focusedCopyTarget).Focus(); + } + + private void DrawEntryContextMenu() + { + const string popupId = "##ConsoleEntryContextMenu"; + if (m_contextKey is null) + return; + int index = m_view.FindIndex(m_contextKey); + if (index < 0) + { + if (Ui.BeginPopup(popupId)) + { + try + { + Ui.CloseCurrentPopup(); + } + finally + { + Ui.EndPopup(); + } + } + m_contextKey = null; + m_requestContextMenu = false; + return; + } + if (m_requestContextMenu) + { + Ui.OpenPopup(popupId); + m_requestContextMenu = false; + } + if (!EditorUI.current.menus.ContextMenu(popupId, + m_interactions.For(LoggingInteractionIds.C_ENTRY_AREA, m_view.rows[index].copyTarget))) + m_contextKey = null; } private void RemoveStaleOpenEntries(EditorConsoleSnapshot snapshot) @@ -492,24 +578,19 @@ private static void DrawDetailFieldRow( string label, string value ) { - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); - _ = NativeImGui.TableSetColumnIndex(1); - NativeImGui.PushTextWrapPos(0f); - NativeImGui.TextUnformatted(value); - NativeImGui.PopTextWrapPos(); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); + _ = Ui.TableSetColumnIndex(1); + EditorUI.current.widgets.WrappedText(value); } private static void DrawStackedField( string label, string value ) { - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); - NativeImGui.PushTextWrapPos(0f); - NativeImGui.TextUnformatted(value); - NativeImGui.PopTextWrapPos(); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); + EditorUI.current.widgets.WrappedText(value); } - #endregion - +#endregion } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanelContent.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanelContent.cs index e4be43156..33604dca3 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanelContent.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsolePanelContent.cs @@ -1,14 +1,10 @@ using System; using System.Numerics; - using Inno.Core.Logging; using Inno.Editor.ImGui; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - internal sealed class ConsolePanelContent { private const string C_ICON_BUG = ImGuiIcon.Bug; @@ -17,33 +13,28 @@ internal sealed class ConsolePanelContent private const string C_ICON_ERROR = ImGuiIcon.CircleXmark; private const string C_ICON_FATAL = ImGuiIcon.SkullCrossbones; private const string C_ICON_DEFAULT = ImGuiIcon.FileLines; - internal void DrawDisabledHint(string text) { - NativeImGui.BeginDisabled(true); - NativeImGui.TextUnformatted(text); - NativeImGui.EndDisabled(); + Ui.BeginDisabled(true); + Ui.Text(text); + Ui.EndDisabled(); } internal (Vector4 color, string icon) GetLevelVisual(LogLevel level) { return level switch { - LogLevel.Debug => (EditorPalette.logDebug, C_ICON_BUG), - LogLevel.Info => (EditorPalette.logInfo, C_ICON_INFO), - LogLevel.Warn => (EditorPalette.logWarning, C_ICON_WARN), - LogLevel.Error => (EditorPalette.logError, C_ICON_ERROR), - LogLevel.Fatal => (EditorPalette.logFatal, C_ICON_FATAL), - _ => (EditorPalette.text, C_ICON_DEFAULT) - }; + LogLevel.Debug => (ConsoleStyle.debug, C_ICON_BUG), + LogLevel.Info => (ConsoleStyle.info, C_ICON_INFO), + LogLevel.Warn => (ConsoleStyle.warning, C_ICON_WARN), + LogLevel.Error => (ConsoleStyle.error, C_ICON_ERROR), + LogLevel.Fatal => (ConsoleStyle.fatal, C_ICON_FATAL), + _ => (EditorPalette.text, C_ICON_DEFAULT)}; } - internal Vector4 GetCollapsedBgColor() => EditorPalette.logCollapsedCard; - - internal Vector4 GetCollapsedBorderColor() => EditorPalette.logCollapsedBorder; - - internal Vector4 GetExpandedBgColor(Vector4 levelColor) => EditorPalette.GetLogExpandedCard(levelColor); - + internal Vector4 GetCollapsedBgColor() => ConsoleStyle.collapsedCard; + internal Vector4 GetCollapsedBorderColor() => ConsoleStyle.collapsedBorder; + internal Vector4 GetExpandedBgColor(Vector4 levelColor) => ConsoleStyle.GetExpandedCard(levelColor); internal string GetFirstLine(string text) { if (string.IsNullOrEmpty(text)) @@ -53,6 +44,7 @@ internal string GetFirstLine(string text) if (text[i] is '\n' or '\r') return text[..i]; } + return text; } @@ -65,23 +57,23 @@ float maxWidth return string.Empty; if (maxWidth <= 1f) return c_ellipsis; - if (NativeImGui.CalcTextSize(text).X <= maxWidth) + if (Ui.MeasureText(text).X <= maxWidth) return text; - float ellipsisWidth = NativeImGui.CalcTextSize(c_ellipsis).X; + float ellipsisWidth = Ui.MeasureText(c_ellipsis).X; if (ellipsisWidth >= maxWidth) return c_ellipsis; - float maxTextWidth = maxWidth - ellipsisWidth; int low = 0; int high = text.Length; while (low < high) { int middle = (low + high + 1) / 2; - if (NativeImGui.CalcTextSize(text[..middle]).X <= maxTextWidth) + if (Ui.MeasureText(text[..middle]).X <= maxTextWidth) low = middle; else high = middle - 1; } + return low <= 0 ? c_ellipsis : text[..low] + c_ellipsis; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleView.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleView.cs new file mode 100644 index 000000000..5a17c1422 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/ConsoleView.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Core.Logging; +using Inno.Editor.Diagnostics; + +namespace Inno.Editor.Panel.Logging; + +internal sealed class ConsoleView +{ + private long m_revision = -1; + private string m_query = string.Empty; + private ulong m_levels; + private bool m_collapsed; + private readonly Dictionary m_indices = new(StringComparer.Ordinal); + + internal IReadOnlyList rows { get; private set; } = Array.Empty(); + + internal bool Update( + EditorConsoleSnapshot snapshot, + string query, + IReadOnlySet levels, + bool collapsed + ) { + ulong mask = 0; + foreach (LogLevel level in levels) + mask |= 1UL << (int)level; + if (m_revision == snapshot.revision && m_levels == mask && m_collapsed == collapsed + && string.Equals(m_query, query, StringComparison.Ordinal)) + return false; + var filtered = new List(); + if (collapsed) + { + foreach (EditorConsoleGroup group in snapshot.groups) + { + var matches = new List(); + foreach (EditorConsoleOccurrence occurrence in group.occurrences) + { + if (Matches(occurrence, query, levels)) + matches.Add(occurrence); + } + if (matches.Count > 0) + filtered.Add(new Row("group/" + group.identity, matches[^1], matches.AsReadOnly())); + } + } + else + { + foreach (EditorConsoleOccurrence occurrence in snapshot.occurrences) + { + if (Matches(occurrence, query, levels)) + filtered.Add(new Row("occurrence/" + occurrence.sequence, occurrence, Array.AsReadOnly(new[] { occurrence }))); + } + } + if (collapsed) + { + filtered.Sort(static ( + first, + second + ) => first.latest.sequence.CompareTo(second.latest.sequence)); + } + for (int index = 0; index < filtered.Count; index++) + { + Row candidate = filtered[index]; + if (m_indices.TryGetValue(candidate.key, out int previousIndex)) + { + Row previous = rows[previousIndex]; + if (ReferenceEquals(previous.latest, candidate.latest) && previous.occurrences.SequenceEqual(candidate.occurrences)) + filtered[index] = previous; + } + } + m_indices.Clear(); + for (int index = 0; index < filtered.Count; index++) + m_indices.Add(filtered[index].key, index); + rows = filtered.AsReadOnly(); + m_revision = snapshot.revision; + m_query = query; + m_levels = mask; + m_collapsed = collapsed; + return true; + } + + internal int FindIndex(string? key) => key is not null && m_indices.TryGetValue(key, out int index) ? index : -1; + + internal void Clear() + { + rows = Array.Empty(); + m_indices.Clear(); + m_revision = -1; + m_query = string.Empty; + m_levels = 0; + m_collapsed = false; + } + + private static bool Matches( + EditorConsoleOccurrence occurrence, + string query, + IReadOnlySet levels + ) => levels.Contains(occurrence.level) + && (query.Length == 0 + || occurrence.message.Contains(query, StringComparison.OrdinalIgnoreCase) + || occurrence.source.Contains(query, StringComparison.OrdinalIgnoreCase) + || occurrence.file.Contains(query, StringComparison.OrdinalIgnoreCase) + || occurrence.code.Contains(query, StringComparison.OrdinalIgnoreCase) + || occurrence.category.Contains(query, StringComparison.OrdinalIgnoreCase) + || occurrence.stackTrace.Contains(query, StringComparison.OrdinalIgnoreCase)); + + internal sealed class Row + { + internal Row( + string key, + EditorConsoleOccurrence latest, + IReadOnlyList occurrences + ) { + this.key = key; + this.latest = latest; + this.occurrences = occurrences; + copyTarget = new ConsoleEntryCopyTarget(latest, occurrences.Count); + } + + internal string key { get; } + internal EditorConsoleOccurrence latest { get; } + internal IReadOnlyList occurrences { get; } + internal object copyTarget { get; } + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleDetailsCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleDetailsCommand.cs index f6b61c262..7fa3f4ac3 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleDetailsCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleDetailsCommand.cs @@ -1,10 +1,11 @@ using Inno.Editor.Interactions; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Inno.Core.Input; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - [EditorAction(LoggingInteractionIds.C_COPY_DETAILS, LoggingInteractionIds.C_ENTRY_AREA)] [EditorMenu(LoggingInteractionIds.C_ENTRY_AREA, "Copy Full Entry", order: 110)] +[EditorShortcut(LoggingInteractionIds.C_ENTRY_AREA, KeyCode.C, KeyModifier.Shift, primary: true)] internal sealed class CopyConsoleDetailsCommand : EditorAction { /// @@ -13,6 +14,5 @@ internal sealed class CopyConsoleDetailsCommand : EditorAction /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) - => NativeImGui.SetClipboardText(context.target.fullText); + protected override void Execute(EditorActionContext context) => Ui.SetClipboardText(context.target.fullText); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleMessageCommand.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleMessageCommand.cs index b2238b061..3352f1666 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleMessageCommand.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Console/CopyConsoleMessageCommand.cs @@ -1,10 +1,11 @@ using Inno.Editor.Interactions; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Inno.Core.Input; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - [EditorAction(LoggingInteractionIds.C_COPY_MESSAGE, LoggingInteractionIds.C_ENTRY_AREA)] [EditorMenu(LoggingInteractionIds.C_ENTRY_AREA, "Copy Message", order: 100)] +[EditorShortcut(LoggingInteractionIds.C_ENTRY_AREA, KeyCode.C, primary: true)] internal sealed class CopyConsoleMessageCommand : EditorAction { /// @@ -13,6 +14,5 @@ internal sealed class CopyConsoleMessageCommand : EditorAction /// The operation scope that provides state, services, and ownership boundaries. /// - protected override void Execute(EditorActionContext context) - => NativeImGui.SetClipboardText(context.target.message); + protected override void Execute(EditorActionContext context) => Ui.SetClipboardText(context.target.message); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj index bd58d3070..64e593ed5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Inno.Editor.Panel.Logging.csproj @@ -4,7 +4,6 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor @@ -12,31 +11,13 @@ - - - - - - + + + + - - + + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Logging/LoggingModule.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Logging/LoggingModule.cs index b2281ee74..852bfebe8 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Logging/LoggingModule.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Logging/LoggingModule.cs @@ -1,5 +1,4 @@ using System; - using Inno.Core.Diagnostics; using Inno.Core.Logging; using Inno.Editor.Core; @@ -8,13 +7,12 @@ using Inno.Editor.Settings; namespace Inno.Editor.Panel.Logging; - [EditorModule("diagnostics-logging", order: int.MinValue)] internal sealed class LoggingModule : EditorModule, IEditorConsole { + private IDisposable? m_settingsSubscription; private readonly EditorConsole m_console; private readonly EditorSettings m_settings; - internal LoggingModule( LogRouter logRouter, DiagnosticHub diagnosticHub, @@ -34,7 +32,7 @@ EditorSettings settings protected override void OnStart(EditorContext context) { ApplySettings(m_settings); - m_settings.changed += ApplySettings; + m_settingsSubscription = m_settings.ListenChanges(ApplySettings); m_console.Start(); } @@ -46,32 +44,24 @@ protected override void OnStart(EditorContext context) /// protected override void OnStop(EditorContext context) { - m_settings.changed -= ApplySettings; + m_settingsSubscription?.Dispose(); m_console.Dispose(); } /// /// Releases resources retained by this feature after it has stopped. /// - protected override void OnDispose() => m_console.Dispose(); - - private void ApplySettings(EditorSettings settings) => m_console.clearOnPlay = ClearConsoleOnPlaySetting.Read(settings); - - int IEditorConsole.capacity + protected override void OnDispose() { - get => m_console.capacity; - set => m_console.capacity = value; + m_settingsSubscription?.Dispose(); + m_console.Dispose(); } - bool IEditorConsole.clearOnPlay - { - get => m_console.clearOnPlay; - set => m_console.clearOnPlay = value; - } + private void ApplySettings(EditorSettings settings) => m_console.clearOnPlay = ClearConsoleOnPlaySetting.Read(settings); + int IEditorConsole.capacity { get => m_console.capacity; set => m_console.capacity = value; } - EditorConsoleSnapshot IEditorConsole.Capture() - => m_console.Capture(); + bool IEditorConsole.clearOnPlay { get => m_console.clearOnPlay; set => m_console.clearOnPlay = value; } - void IEditorConsole.Clear() - => m_console.Clear(); + EditorConsoleSnapshot IEditorConsole.Capture() => m_console.Capture(); + void IEditorConsole.Clear() => m_console.Clear(); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Settings/ConsoleSettings.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Settings/ConsoleSettings.cs index 8e3222eec..ef1b31961 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Settings/ConsoleSettings.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Settings/ConsoleSettings.cs @@ -1,8 +1,7 @@ using Inno.Editor.Settings; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Logging; - [EditorSettingPath("Editor/Diagnostics")] internal sealed class DiagnosticsSettingsPage : EditorSetting { @@ -25,7 +24,6 @@ internal sealed class ConsoleSettingsPage : EditorSetting internal sealed class ClearConsoleOnPlaySetting : EditorSetting { internal const string C_PATH = "Editor/Diagnostics/Console/Clear on Play"; - /// /// Gets a new value initialized to the canonical Console retention policy. /// @@ -43,12 +41,10 @@ public override EditorSettingObject defaultValue /// Gets the presentation section that groups this setting. /// public override string section => "Play Mode"; - /// /// Gets the user-facing explanation of this feature or setting. /// - public override string description - => "Clear ordinary Console logs when a new Play Mode request begins while retaining active diagnostics."; + public override string description => "Clear ordinary Console logs when a new Play Mode request begins while retaining active diagnostics."; /// /// Draws the staged Console retention preference. @@ -59,7 +55,7 @@ public override string description protected override void OnDraw(EditorSettingObject setting) { bool value = setting.GetAsBoolean("value", true); - if (NativeImGui.Checkbox("Clear ordinary logs when entering Play Mode", ref value)) + if (Ui.Checkbox("Clear ordinary logs when entering Play Mode", ref value)) setting.SetAsBoolean("value", value); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Logging/Styling/ConsoleStyle.cs b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Styling/ConsoleStyle.cs new file mode 100644 index 000000000..ef94e75e6 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Logging/Styling/ConsoleStyle.cs @@ -0,0 +1,114 @@ +using System.Numerics; +using Inno.Editor.ImGui; + +namespace Inno.Editor.Panel.Logging; + +internal readonly struct ConsoleStyle +{ + private readonly float m_zoom; + + internal ConsoleStyle(EditorStyleMetrics metrics) => m_zoom = metrics.zoom; + + /// + /// Gets debug log color. + /// + internal static Vector4 debug { get; } = new(0.80f, 0.90f, 0.85f, EditorPalette.opacityOpaque); + + /// + /// Gets informational log color. + /// + internal static Vector4 info { get; } = new(0.20f, 1f, 0.20f, EditorPalette.opacityOpaque); + + /// + /// Gets warning log color. + /// + internal static Vector4 warning { get; } = new(1f, 1f, 0.20f, EditorPalette.opacityOpaque); + + /// + /// Gets error log color. + /// + internal static Vector4 error { get; } = new(1f, 0.20f, 0.20f, EditorPalette.opacityOpaque); + + /// + /// Gets fatal log color. + /// + internal static Vector4 fatal { get; } = new(1f, 0.20f, 1f, EditorPalette.opacityOpaque); + + /// + /// Gets collapsed log card background. + /// + internal static Vector4 collapsedCard { get; } = new(0.61960787f, 0.5764706f, 0.76862746f, EditorPalette.opacityMuted); + + /// + /// Gets collapsed log card border. + /// + internal static Vector4 collapsedBorder { get; } = new(0.32f, 0.34f, 0.37f, EditorPalette.opacityMedium); + + /// + /// Gets the base expanded log card background. + /// + internal static Vector4 expandedBase { get; } = new(0.10f, 0.10f, 0.10f, EditorPalette.opacityOpaque); + + /// + /// Gets the base expanded log card border. + /// + internal static Vector4 expandedBorderBase { get; } = new(0.24f, 0.24f, 0.24f, EditorPalette.opacityOpaque); + + /// + /// Gets log header button background. + /// + internal static Vector4 toggle { get; } = new(0.61960787f, 0.5764706f, 0.76862746f, EditorPalette.opacityNone); + + /// + /// Gets hovered log header button background. + /// + internal static Vector4 toggleHovered { get; } = new(0.7372549f, 0.69411767f, 0.8862745f, EditorPalette.opacityMedium); + + /// + /// Gets active log header button background. + /// + internal static Vector4 toggleActive { get; } = new(0.8156863f, 0.77254903f, 0.9647059f, EditorPalette.opacityMedium); + + /// + /// Gets the expanded log card background derived from a severity color. + /// + /// + /// The palette color representing the log severity. + /// + /// + /// The standard expanded-card base blended with the severity color. + /// + internal static Vector4 GetExpandedCard(Vector4 severityColor) => EditorPalette.Lerp(expandedBase, severityColor, 0.12f); + + /// + /// Gets the expanded log card border derived from a severity color. + /// + /// + /// The palette color representing the log severity. + /// + /// + /// The standard expanded-border base blended with the severity color. + /// + internal static Vector4 GetExpandedBorder(Vector4 severityColor) => EditorPalette.Lerp(expandedBorderBase, severityColor, 0.20f); + + /// + /// Gets the separator color used inside an expanded log card. + /// + /// + /// The resolved background color of the expanded card. + /// + /// + /// The card color blended toward the shared text color. + /// + internal static Vector4 GetSeparator(Vector4 cardColor) => EditorPalette.Lerp(cardColor, EditorPalette.text, 0.12f); + + /// + /// Gets the distance from the bottom treated as auto-scroll. + /// + internal float autoScrollTolerance => m_zoom; + + /// + /// Gets log disclosure padding. + /// + internal Vector2 disclosurePadding => new Vector2(6f, 1f) * m_zoom; +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/Inno.Editor.Panel.SceneView.csproj b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/Inno.Editor.Panel.SceneView.csproj index 8cca2b8d4..7e0763a1a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/Inno.Editor.Panel.SceneView.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/Inno.Editor.Panel.SceneView.csproj @@ -7,45 +7,21 @@ true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - true - - - + - + - - - + + + - - - + + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewBackgroundSetting.cs b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewBackgroundSetting.cs index add1c4479..a69226a30 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewBackgroundSetting.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewBackgroundSetting.cs @@ -1,28 +1,22 @@ using System.Numerics; - using Inno.Editor.ImGui; using Inno.Editor.Settings; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.SceneView; - [EditorSettingPath(C_PATH)] internal sealed class SceneViewBackgroundSetting : EditorSetting { internal const string C_PATH = "Editor/Appearance/Viewports/Scene Background"; private static readonly float[] S_DEFAULT = [0.11f, 0.12f, 0.14f, EditorPalette.opacityOpaque]; - /// /// Gets a new value initialized to this setting's canonical default state. /// public override EditorSettingObject defaultValue => CreateDefault(); - /// /// Gets the presentation section that groups this setting. /// public override string section => "Viewport Backgrounds"; - /// /// Gets the user-facing explanation of this feature or setting. /// @@ -37,13 +31,12 @@ internal sealed class SceneViewBackgroundSetting : EditorSetting protected override void OnDraw(EditorSettingObject setting) { Vector4 value = ReadVector(setting); - NativeImGui.SetNextItemWidth(-1f); - if (EditorImGui.ColorEditLinear4("##scene_view_background", ref value)) + Ui.SetNextItemWidth(-1f); + if (Ui.ColorEditLinear4("##scene_view_background", ref value)) setting.SetAsSingleArray("value", [value.X, value.Y, value.Z, value.W]); } internal static Vector4 Read(EditorSettings settings) => ReadVector(settings.Get(C_PATH)); - private static EditorSettingObject CreateDefault() { var result = new EditorSettingObject(); @@ -54,8 +47,6 @@ private static EditorSettingObject CreateDefault() private static Vector4 ReadVector(EditorSettingObject setting) { float[] values = setting.GetAsSingleArray("value", S_DEFAULT); - return values.Length == 4 - ? new Vector4(values[0], values[1], values[2], values[3]) - : new Vector4(S_DEFAULT[0], S_DEFAULT[1], S_DEFAULT[2], S_DEFAULT[3]); + return values.Length == 4 ? new Vector4(values[0], values[1], values[2], values[3]) : new Vector4(S_DEFAULT[0], S_DEFAULT[1], S_DEFAULT[2], S_DEFAULT[3]); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewPanel.cs index bd0657f2b..8417a698b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.SceneView/SceneViewPanel.cs @@ -1,12 +1,10 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using Inno.References; using Inno.Core.Identity; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; -using BGCS.Runtime; - using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; @@ -17,33 +15,28 @@ using Inno.Editor.Settings; using Inno.Scene; using Inno.Scene.Components; -using Inno.Native.ImGui; -using ImGuizmoMode = Inno.Native.ImGuizmo.ImGuizmoMode; -using ImGuizmoOperation = Inno.Native.ImGuizmo.ImGuizmoOperation; -using Inno.Adapter.Presentation.ImGui; +using ImGuizmoMode = Inno.Editor.ImGui.ImGuiWidget.TransformGizmoSpace; +using ImGuizmoOperation = Inno.Editor.ImGui.ImGuiWidget.TransformGizmoOperation; using Inno.Rendering; -using NativeImGui = Inno.Native.ImGui.ImGui; -using NativeImGuizmo = Inno.Native.ImGuizmo.ImGuizmo; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using Ui = Inno.Editor.ImGui.ImGui; using EngineMatrix = Inno.Core.Mathematics.Matrix; using EngineQuaternion = Inno.Core.Mathematics.Quaternion; using EngineVector3 = Inno.Core.Mathematics.Vector3; namespace Inno.Editor.Panel.SceneView; - /// /// Presents the active Plugin provider and host-owned transform manipulation for the Scene viewport. /// [EditorPanel("rendering.scene-view", "Scene", order: 210, menuPath: "Viewports")] -internal sealed unsafe class SceneViewPanel : EditorPanel +internal sealed class SceneViewPanel : EditorPanel { + private IDisposable? m_settingsSubscription; private const string C_VIEWPORT_ID = "scene-view"; private const string C_GIZMO_CLUSTER_POPUP = "##scene-gizmo-cluster"; private const int C_MANIPULATION_TOOL_COUNT = 4; - private const float C_IMGUIZMO_AXIS_LIMIT_DEFAULT = 0.0025f; + private const string C_TRANSFORM_EDIT = "scene-view.transform"; private static readonly EditorViewportKindId S_KIND = new("inno.editor.viewport.scene"); private static readonly Vector2 S_UNAVAILABLE_PADDING = new(48f, 32f); - private readonly EditorRenderingModule m_rendering; private readonly EditorInteractions m_interactions; private readonly SceneEdits m_sceneEdits; @@ -56,10 +49,9 @@ internal sealed unsafe class SceneViewPanel : EditorPanel private ImGuiMouseButton m_navigationDragButton; private ImGuizmoOperation m_operation = ImGuizmoOperation.Translate; private ImGuizmoMode m_mode = ImGuizmoMode.World; - private Transform? m_gestureTarget; - private TransformSnapshot m_gestureBefore; + private RuntimeIdentity? m_gestureTarget; + private RuntimeIdentity[] m_gestureTargets = []; private Identity[] m_popupGizmoOwners = []; - internal SceneViewPanel( EditorRenderingModule rendering, EditorInteractions interactions, @@ -80,7 +72,6 @@ EditorSettings settings /// Gets whether use window padding is enabled for this implementation. /// public override bool useWindowPadding => false; - /// /// Gets whether allow scrolling is enabled for this implementation. /// @@ -96,43 +87,29 @@ protected override void OnDraw(EditorContext context) { using IDisposable presentationScope = m_playModeLoop.EnterPresentationScope(); _ = context; - if (m_gestureTarget is not null && !NativeImGui.IsMouseDown((int)ImGuiMouseButton.Left)) + if (m_gestureTarget is not null && !Ui.IsMouseButtonDown(ImGuiMouseButton.Left)) CommitGesture(); - - Vector2 available = NativeImGui.GetContentRegionAvail(); + Vector2 available = Ui.GetContentRegionAvailable(); if (available.X <= 0f || available.Y <= 0f) return; int width = Math.Max(1, (int)MathF.Floor(available.X)); int height = Math.Max(1, (int)MathF.Floor(available.Y)); - m_rendering.SetPresentation( - C_VIEWPORT_ID, - new EditorViewportPresentation(ToEngineColor(m_backgroundColor))); + m_rendering.SetPresentation(C_VIEWPORT_ID, new EditorViewportPresentation(ToEngineColor(m_backgroundColor))); m_rendering.SetContentScope(C_VIEWPORT_ID, CreateContentScope()); - Vector2 minimum = NativeImGui.GetCursorScreenPos(); + Vector2 minimum = Ui.GetCursorScreenPosition(); Vector2 maximum = minimum + new Vector2(width, height); ManipulationToolbarLayout toolbar = CreateManipulationToolbarLayout(minimum, maximum); - bool hovered = NativeImGui.IsWindowHovered((int)ImGuiHoveredFlags.RootAndChildWindows) - && NativeImGui.IsMouseHoveringRect(minimum, maximum); + bool hovered = Ui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows) && Ui.IsMouseHoveringRect(minimum, maximum); bool toolbarHovered = IsManipulationToolbarHovered(toolbar); - _ = m_rendering.TryConfigureNavigation( - S_KIND, - C_VIEWPORT_ID, - width, - height, - out EditorViewportNavigationProfile navigationProfile); - bool navigationOwnsPointer = HandleNavigation( - navigationProfile, - hovered && !toolbarHovered, - minimum, - maximum); + _ = m_rendering.TryConfigureNavigation(S_KIND, C_VIEWPORT_ID, width, height, out EditorViewportNavigationProfile navigationProfile); + bool navigationOwnsPointer = HandleNavigation(navigationProfile, hovered && !toolbarHovered, minimum, maximum); if (!m_rendering.TrySubmit(S_KIND, C_VIEWPORT_ID, width, height, out EditorViewportOutput output)) { - DrawUnavailable( - available, - m_rendering.GetCompositionError(C_VIEWPORT_ID) ?? "No rendering model contributes to Scene View."); + DrawUnavailable(available, m_rendering.GetCompositionError(C_VIEWPORT_ID) ?? "No rendering model contributes to Scene View."); _ = DrawManipulationToolbar(toolbar); return; } + if (!output.isReady) { DrawUnavailable(available, "Preparing Scene View GPU target..."); @@ -141,40 +118,34 @@ protected override void OnDraw(EditorContext context) } m_rendering.Draw(output, new Vector2(width, height)); - minimum = NativeImGui.GetItemRectMin(); - maximum = NativeImGui.GetItemRectMax(); - bool viewportClicked = NativeImGui.IsItemClicked((int)ImGuiMouseButton.Left); + minimum = Ui.GetItemMinimum(); + maximum = Ui.GetItemMaximum(); + bool viewportClicked = Ui.IsItemClicked(ImGuiMouseButton.Left); toolbar = CreateManipulationToolbarLayout(minimum, maximum); toolbarHovered = IsManipulationToolbarHovered(toolbar); - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); + EditorDrawList drawList = Ui.GetDrawList(); IReadOnlyList<(Vector2 center, EditorGizmoIcon icon)> icons; bool toolbarOwnsPointer; bool gizmoOwnsPointer; - NativeImGui.Raw.ChannelsSplit(drawList, 3); + drawList.SplitChannels(3); try { - NativeImGui.Raw.ChannelsSetCurrent(drawList, 0); + drawList.SetChannel(0); icons = DrawGizmos(minimum, maximum); - NativeImGui.Raw.ChannelsSetCurrent(drawList, 2); + drawList.SetChannel(2); toolbarOwnsPointer = DrawManipulationToolbar(toolbar); - NativeImGui.Raw.ChannelsSetCurrent(drawList, 1); - gizmoOwnsPointer = !navigationOwnsPointer - && DrawTransformGizmo(minimum, maximum, hovered && !toolbarHovered); + drawList.SetChannel(1); + gizmoOwnsPointer = !navigationOwnsPointer && DrawTransformGizmo(minimum, maximum, hovered && !toolbarHovered); } finally { - NativeImGui.Raw.ChannelsMerge(drawList); + drawList.MergeChannels(); } - bool iconOwnsPointer = SelectGizmoIcons(icons, - viewportClicked && !toolbarOwnsPointer && !gizmoOwnsPointer && !navigationOwnsPointer); - if (!viewportClicked - || toolbarOwnsPointer - || gizmoOwnsPointer - || iconOwnsPointer - || navigationOwnsPointer - || NativeImGui.Raw.GetIO()->KeyAlt != 0) + + bool iconOwnsPointer = SelectGizmoIcons(icons, viewportClicked && !toolbarOwnsPointer && !gizmoOwnsPointer && !navigationOwnsPointer); + if (!viewportClicked || toolbarOwnsPointer || gizmoOwnsPointer || iconOwnsPointer || navigationOwnsPointer || Ui.GetInput().alt) return; - Vector2 mouse = NativeImGui.GetMousePos(); + Vector2 mouse = Ui.GetMousePos(); float x = (mouse.X - minimum.X) / Math.Max(1f, maximum.X - minimum.X); float y = (mouse.Y - minimum.Y) / Math.Max(1f, maximum.Y - minimum.Y); m_rendering.HandlePointer(S_KIND, C_VIEWPORT_ID, width, height, x, y, button: 0); @@ -190,7 +161,7 @@ protected override void OnAttach(EditorContext context) { _ = context; ApplySettings(m_settings); - m_settings.changed += ApplySettings; + m_settingsSubscription = m_settings.ListenChanges(ApplySettings); } /// @@ -202,7 +173,7 @@ protected override void OnAttach(EditorContext context) protected override void OnDetach(EditorContext context) { _ = context; - m_settings.changed -= ApplySettings; + m_settingsSubscription?.Dispose(); m_planarNavigation.Cancel(); m_navigationDrag = NavigationDrag.None; CommitGesture(); @@ -251,22 +222,15 @@ protected override void Restore(EditorState state) { if (!state.Get("navigation.initialized", false)) return; - var position = new EngineVector3( - state.Get("navigation.position.x", 0f), - state.Get("navigation.position.y", 0f), - state.Get("navigation.position.z", 0f)); + var position = new EngineVector3(state.Get("navigation.position.x", 0f), state.Get("navigation.position.y", 0f), state.Get("navigation.position.z", 0f)); var rotation = new EngineQuaternion( state.Get("navigation.rotation.x", 0f), state.Get("navigation.rotation.y", 0f), state.Get("navigation.rotation.z", 0f), - state.Get("navigation.rotation.w", 1f)); - var pivot = new EngineVector3( - state.Get("navigation.pivot.x", 0f), - state.Get("navigation.pivot.y", 0f), - state.Get("navigation.pivot.z", 0f)); - EditorViewportProjection projection = (EditorViewportProjection)state.Get( - "navigation.projection", - (int)EditorViewportProjection.Orthographic); + state.Get("navigation.rotation.w", 1f) + ); + var pivot = new EngineVector3(state.Get("navigation.pivot.x", 0f), state.Get("navigation.pivot.y", 0f), state.Get("navigation.pivot.z", 0f)); + EditorViewportProjection projection = (EditorViewportProjection)state.Get("navigation.projection", (int)EditorViewportProjection.Orthographic); try { EditorViewportNavigationState navigation = m_rendering.GetNavigationState(C_VIEWPORT_ID); @@ -277,118 +241,76 @@ protected override void Restore(EditorState state) rotation, state.Get("navigation.fieldOfView", 60f), state.Get("navigation.nearClip", 0.01f), - state.Get("navigation.farClip", 1000f)); + state.Get("navigation.farClip", 1000f) + ); } else { - navigation.ConfigureOrthographic( - position, - rotation, - state.Get("navigation.orthographicSize", 5f)); + navigation.ConfigureOrthographic(position, rotation, state.Get("navigation.orthographicSize", 5f)); } + navigation.pivot = pivot; navigation.focusDistance = state.Get("navigation.focusDistance", 10f); navigation.movementSpeed = state.Get("navigation.movementSpeed", 5f); - navigation.mode = (EditorViewportNavigationMode)state.Get( - "navigation.mode", - (int)EditorViewportNavigationMode.Planar); + navigation.mode = (EditorViewportNavigationMode)state.Get("navigation.mode", (int)EditorViewportNavigationMode.Planar); } catch (ArgumentException) { - // Malformed workspace camera state is ignored and the active provider supplies a fresh default. + // Malformed workspace camera state is ignored and the active provider supplies a fresh default. } } private void ApplySettings(EditorSettings settings) => m_backgroundColor = SceneViewBackgroundSetting.Read(settings); - private ContentReadScope CreateContentScope() => m_scenePresentation.Capture(); - private IReadOnlyList<(Vector2 center, EditorGizmoIcon icon)> DrawGizmos( Vector2 minimum, Vector2 maximum ) { - if (!m_rendering.TryGetManipulationSpace(C_VIEWPORT_ID, - out EditorViewportManipulationSpace space)) - return []; + if (!m_rendering.TryGetManipulationSpace(C_VIEWPORT_ID, out EditorViewportManipulationSpace space)) + return[]; int width = Math.Max(1, (int)(maximum.X - minimum.X)); int height = Math.Max(1, (int)(maximum.Y - minimum.Y)); EditorGizmoFrame frame = m_rendering.CollectGizmos(C_VIEWPORT_ID, width, height); EngineMatrix worldToClip = space.projectionMatrix * space.viewMatrix; - var draw = NativeImGui.Raw.GetWindowDrawList(); - uint white = NativeImGui.ColorConvertFloat4ToU32(Vector4.One); - uint outline = NativeImGui.ColorConvertFloat4ToU32(new Vector4(0f, 0f, 0f, 0.75f)); + EditorDrawList draw = Ui.GetDrawList(); + uint white = Ui.ToPackedColor(Vector4.One); + uint outline = Ui.ToPackedColor(new Vector4(0f, 0f, 0f, 0.75f)); foreach (EditorGizmoLine line in frame.lines) { if (!TryProject(line.start, out Vector2 start) || !TryProject(line.end, out Vector2 end)) continue; - EditorImGui.DrawLine( - start + Vector2.One, - end + Vector2.One, - outline, - 3.5f - ); - EditorImGui.DrawLine( - start, - end, - white, - 2.5f - ); + Ui.DrawLine(start + Vector2.One, end + Vector2.One, outline, 3.5f); + Ui.DrawLine(start, end, white, 2.5f); } - float iconSize = 24f * EditorWidget.style.zoom; + + float iconSize = 24f * EditorUI.current.widgets.style.zoom; var displayIcons = new List<(Vector2 center, EditorGizmoIcon icon)>(); - ImFont* font = NativeImGui.Raw.GetFont(); foreach (EditorGizmoIcon icon in frame.icons) { if (!TryProject(icon.position, out Vector2 center)) continue; displayIcons.Add((center, icon)); string glyph = GizmoIconGlyph(icon.iconId); - using NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(glyph, allowsNull: false); - ImVec2C measured = default; - NativeImGui.Raw.CalcTextSizeA(font, iconSize, float.MaxValue, 0f, (sbyte*)nativeText.pointer, null, null, &measured); - Vector2 size = new(measured.X, measured.Y); + float fontScale = iconSize / Ui.GetFontSize(); + Vector2 size = Ui.MeasureText(glyph, fontScale); Vector2 origin = center - size * 0.5f; - ImVec2C outlinePosition = new() { X = origin.X + 1f, Y = origin.Y + 1f }; - ImVec2C textPosition = new() { X = origin.X, Y = origin.Y }; - NativeImGui.Raw.AddText( - draw, - font, - iconSize, - &outlinePosition, - outline, - (sbyte*)nativeText.pointer, - null, - 0f, - null - ); - NativeImGui.Raw.AddText( - draw, - font, - iconSize, - &textPosition, - white, - (sbyte*)nativeText.pointer, - null, - 0f, - null - ); + draw.Text(origin + Vector2.One, outline, glyph, fontScale); + draw.Text(origin, white, glyph, fontScale); } - return displayIcons; + return displayIcons; bool TryProject( EngineVector3 world, out Vector2 screen ) { - Inno.Core.Mathematics.Vector4 clip = Inno.Core.Mathematics.Vector4.Transform( - new Inno.Core.Mathematics.Vector4(world.x, world.y, world.z, 1f), worldToClip); + Inno.Core.Mathematics.Vector4 clip = Inno.Core.Mathematics.Vector4.Transform(new Inno.Core.Mathematics.Vector4(world.x, world.y, world.z, 1f), worldToClip); if (clip.w <= 0.000001f) { screen = default; return false; } - screen = minimum + new Vector2( - (clip.x / clip.w + 1f) * width * 0.5f, - (1f - clip.y / clip.w) * height * 0.5f); + + screen = minimum + new Vector2((clip.x / clip.w + 1f) * width * 0.5f, (1f - clip.y / clip.w) * height * 0.5f); return float.IsFinite(screen.X) && float.IsFinite(screen.Y); } } @@ -400,13 +322,9 @@ bool maySelect bool iconClicked = false; if (maySelect) { - Vector2 mouse = NativeImGui.GetMousePos(); - float radius = 20f * EditorWidget.style.zoom; - Identity[] owners = displayIcons - .Where(icon => Vector2.DistanceSquared(mouse, icon.center) <= radius * radius) - .Select(static icon => icon.icon.owner) - .DistinctBy(static owner => owner.persistentId) - .ToArray(); + Vector2 mouse = Ui.GetMousePos(); + float radius = 20f * EditorUI.current.widgets.style.zoom; + Identity[] owners = displayIcons.Where(icon => Vector2.DistanceSquared(mouse, icon.center) <= radius * radius).Select(static icon => icon.icon.owner).DistinctBy(static owner => owner.persistentId).ToArray(); if (owners.Length == 1 && owners[0].Resolve() is { isDestroyed: false } owner) { m_interactions.SetSelection(owner); @@ -415,17 +333,18 @@ bool maySelect else if (owners.Length > 1) { m_popupGizmoOwners = owners; - NativeImGui.OpenPopup(C_GIZMO_CLUSTER_POPUP); + Ui.OpenPopup(C_GIZMO_CLUSTER_POPUP); iconClicked = true; } } + DrawGizmoClusterPopup(); return iconClicked; } private void DrawGizmoClusterPopup() { - if (!NativeImGui.BeginPopup(C_GIZMO_CLUSTER_POPUP)) + if (!Ui.BeginPopup(C_GIZMO_CLUSTER_POPUP)) return; foreach (Identity identity in m_popupGizmoOwners) { @@ -433,24 +352,23 @@ private void DrawGizmoClusterPopup() if (owner is null || owner.isDestroyed) continue; string label = $"{owner.name}##{identity.runtimeIdentity}"; - if (!NativeImGui.MenuItem(label, string.Empty, false, true)) + if (!Ui.MenuItem(label, string.Empty, false, true)) continue; m_interactions.SetSelection(owner); - NativeImGui.CloseCurrentPopup(); + Ui.CloseCurrentPopup(); break; } - NativeImGui.EndPopup(); - } - private static string GizmoIconGlyph(string iconId) - => iconId switch - { - "camera" => ImGuiIcon.Camera, - "light" => ImGuiIcon.Lightbulb, - "canvas" => ImGuiIcon.VectorSquare, - _ => ImGuiIcon.CircleQuestion - }; + Ui.EndPopup(); + } + private static string GizmoIconGlyph(string iconId) => iconId switch + { + "camera" => ImGuiIcon.Camera, + "light" => ImGuiIcon.Lightbulb, + "canvas" => ImGuiIcon.VectorSquare, + _ => ImGuiIcon.CircleQuestion + }; private bool HandleNavigation( EditorViewportNavigationProfile profile, bool hovered, @@ -458,8 +376,7 @@ private bool HandleNavigation( Vector2 maximum ) { EditorViewportNavigationState navigation = m_rendering.GetNavigationState(C_VIEWPORT_ID); - if (!navigation.isInitialized - || profile.capabilities == EditorViewportNavigationCapabilities.None) + if (!navigation.isInitialized || profile.capabilities == EditorViewportNavigationCapabilities.None) { m_navigationDrag = NavigationDrag.None; m_planarNavigation.Cancel(); @@ -468,20 +385,24 @@ Vector2 maximum if (!SupportsMode(profile, navigation.mode)) navigation.mode = profile.defaultMode; - Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); - if (m_navigationDrag != NavigationDrag.None - && !NativeImGui.IsMouseDown((int)m_navigationDragButton)) + EditorUiInput io = Ui.GetInput(); + if (m_navigationDrag != NavigationDrag.None && !Ui.IsMouseButtonDown(m_navigationDragButton)) { m_navigationDrag = NavigationDrag.None; } - bool panning = m_planarNavigation.Update(hovered && m_navigationDrag == NavigationDrag.None - && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Pan), - NativeImGui.IsMouseClicked((int)ImGuiMouseButton.Left), NativeImGui.IsMouseClicked((int)ImGuiMouseButton.Middle), - NativeImGui.IsMouseDown((int)ImGuiMouseButton.Left), NativeImGui.IsMouseDown((int)ImGuiMouseButton.Middle), io->KeyAlt != 0, - allowAltPrimary: !profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Orbit)); + + bool panning = m_planarNavigation.Update( + hovered && m_navigationDrag == NavigationDrag.None && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Pan), + Ui.IsMouseClicked(ImGuiMouseButton.Left), + Ui.IsMouseClicked(ImGuiMouseButton.Middle), + Ui.IsMouseButtonDown(ImGuiMouseButton.Left), + Ui.IsMouseButtonDown(ImGuiMouseButton.Middle), + io.alt, + allowAltPrimary: !profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Orbit) + ); if (hovered && !panning && m_navigationDrag == NavigationDrag.None) { - if (io->KeyAlt != 0 && NativeImGui.IsMouseClicked((int)ImGuiMouseButton.Left)) + if (io.alt && Ui.IsMouseClicked(ImGuiMouseButton.Left)) { if (profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Orbit)) { @@ -489,20 +410,20 @@ Vector2 maximum m_navigationDragButton = ImGuiMouseButton.Left; } } - else if (NativeImGui.IsMouseClicked((int)ImGuiMouseButton.Right) - && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Fly)) + else if (Ui.IsMouseClicked(ImGuiMouseButton.Right) && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Fly)) { m_navigationDrag = NavigationDrag.Fly; m_navigationDragButton = ImGuiMouseButton.Right; } } - if (io->WantTextInput == 0 && NativeImGui.IsKeyPressed(ImGuiKey.Escape, repeat: false)) + if (!io.wantsText && Ui.IsKeyPressed(ImGuiKey.Escape, repeat: false)) { m_planarNavigation.Cancel(); m_navigationDrag = NavigationDrag.None; panning = false; } + bool ownsPointer = panning || m_navigationDrag != NavigationDrag.None; if (!hovered && !ownsPointer) return false; @@ -511,26 +432,23 @@ Vector2 maximum float aspect = width / height; if (panning) { - Pan(navigation, new Vector2(io->MouseDelta.X, io->MouseDelta.Y), height); - NativeImGui.SetMouseCursor((int)ImGuiMouseCursor.ResizeAll); + Pan(navigation, new Vector2(io.mouseDelta.X, io.mouseDelta.Y), height); + Ui.SetMouseCursor(ImGuiMouseCursor.ResizeAll); } + switch (m_navigationDrag) { case NavigationDrag.Orbit: - Orbit(navigation, profile, new Vector2(io->MouseDelta.X, io->MouseDelta.Y)); + Orbit(navigation, profile, new Vector2(io.mouseDelta.X, io.mouseDelta.Y)); break; case NavigationDrag.Fly: Fly(navigation, profile, io); break; } - if (hovered - && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Zoom) - && io->MouseWheel != 0f) - Zoom(navigation, profile, io->MouseWheel, minimum, width, height, aspect); - if (hovered - && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.FrameSelection) - && NativeImGui.IsKeyPressed(ImGuiKey.F, repeat: false)) + if (hovered && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Zoom) && io.wheel != 0f) + Zoom(navigation, profile, io.wheel, minimum, width, height, aspect); + if (hovered && profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.FrameSelection) && Ui.IsKeyPressed(ImGuiKey.F, repeat: false)) { EditorViewportFocusBounds? bounds = profile.focusBounds; if (bounds is null && TryGetSelectedTransform(out Transform? selected)) @@ -538,6 +456,7 @@ Vector2 maximum if (bounds is EditorViewportFocusBounds focus) Frame(navigation, profile, focus); } + return ownsPointer; } @@ -546,15 +465,11 @@ private static void Pan( Vector2 mouseDelta, float height ) { - float verticalSpan = navigation.projection == EditorViewportProjection.Orthographic - ? navigation.orthographicSize * 2f - : 2f * navigation.focusDistance - * MathF.Tan(navigation.fieldOfView * MathF.PI / 360f); + float verticalSpan = navigation.projection == EditorViewportProjection.Orthographic ? navigation.orthographicSize * 2f : 2f * navigation.focusDistance * MathF.Tan(navigation.fieldOfView * MathF.PI / 360f); float unitsPerPixel = verticalSpan / height; EngineVector3 right = EngineVector3.Transform(EngineVector3.RIGHT, navigation.rotation); EngineVector3 up = EngineVector3.Transform(EngineVector3.UP, navigation.rotation); - EngineVector3 delta = right * (-mouseDelta.X * unitsPerPixel) - + up * (mouseDelta.Y * unitsPerPixel); + EngineVector3 delta = right * (-mouseDelta.X * unitsPerPixel) + up * (mouseDelta.Y * unitsPerPixel); navigation.position += delta; navigation.pivot += delta; } @@ -566,16 +481,10 @@ Vector2 mouseDelta ) { EngineVector3 worldUp = GetWorldUp(profile); float sensitivity = GetPositive(profile.rotationSensitivity, 0.005f); - EngineQuaternion yaw = EngineQuaternion.CreateFromAxisAngle( - worldUp, - -mouseDelta.X * sensitivity); + EngineQuaternion yaw = EngineQuaternion.CreateFromAxisAngle(worldUp, -mouseDelta.X * sensitivity); EngineQuaternion yawed = (yaw * navigation.rotation).normalized; EngineVector3 right = EngineVector3.Transform(EngineVector3.RIGHT, yawed).normalized; - navigation.rotation = ApplyConstrainedPitch( - yawed, - right, - worldUp, - -mouseDelta.Y * sensitivity); + navigation.rotation = ApplyConstrainedPitch(yawed, right, worldUp, -mouseDelta.Y * sensitivity); navigation.mode = EditorViewportNavigationMode.Orbit; EngineVector3 forward = EngineVector3.Transform(EngineVector3.FORWARD, navigation.rotation); navigation.position = navigation.pivot - forward * navigation.focusDistance; @@ -584,46 +493,34 @@ Vector2 mouseDelta private static void Fly( EditorViewportNavigationState navigation, EditorViewportNavigationProfile profile, - Inno.Native.ImGui.ImGuiIO* io + EditorUiInput io ) { float sensitivity = GetPositive(profile.rotationSensitivity, 0.005f); EngineVector3 worldUp = GetWorldUp(profile); - EngineQuaternion yaw = EngineQuaternion.CreateFromAxisAngle( - worldUp, - -io->MouseDelta.X * sensitivity); + EngineQuaternion yaw = EngineQuaternion.CreateFromAxisAngle(worldUp, -io.mouseDelta.X * sensitivity); EngineQuaternion yawed = (yaw * navigation.rotation).normalized; EngineVector3 right = EngineVector3.Transform(EngineVector3.RIGHT, yawed).normalized; - navigation.rotation = ApplyConstrainedPitch( - yawed, - right, - worldUp, - -io->MouseDelta.Y * sensitivity); + navigation.rotation = ApplyConstrainedPitch(yawed, right, worldUp, -io.mouseDelta.Y * sensitivity); navigation.mode = EditorViewportNavigationMode.Fly; - EngineVector3 movement = EngineVector3.ZERO; EngineVector3 forward = EngineVector3.Transform(EngineVector3.FORWARD, navigation.rotation); right = EngineVector3.Transform(EngineVector3.RIGHT, navigation.rotation); - if (NativeImGui.IsKeyDown(ImGuiKey.W)) + if (Ui.IsKeyDown(ImGuiKey.W)) movement += forward; - if (NativeImGui.IsKeyDown(ImGuiKey.S)) + if (Ui.IsKeyDown(ImGuiKey.S)) movement -= forward; - if (NativeImGui.IsKeyDown(ImGuiKey.D)) + if (Ui.IsKeyDown(ImGuiKey.D)) movement += right; - if (NativeImGui.IsKeyDown(ImGuiKey.A)) + if (Ui.IsKeyDown(ImGuiKey.A)) movement -= right; - if (NativeImGui.IsKeyDown(ImGuiKey.E)) + if (Ui.IsKeyDown(ImGuiKey.E)) movement += worldUp; - if (NativeImGui.IsKeyDown(ImGuiKey.Q)) + if (Ui.IsKeyDown(ImGuiKey.Q)) movement -= worldUp; if (movement.LengthSquared() > 0.000001f) { - float multiplier = io->KeyShift != 0 - ? GetPositive(profile.fastMovementMultiplier, 4f) - : 1f; - navigation.position += movement.normalized - * navigation.movementSpeed - * multiplier - * MathF.Max(0f, io->DeltaTime); + float multiplier = io.shift ? GetPositive(profile.fastMovementMultiplier, 4f) : 1f; + navigation.position += movement.normalized * navigation.movementSpeed * multiplier * MathF.Max(0f, io.deltaTime); navigation.pivot = navigation.position + forward * navigation.focusDistance; } } @@ -640,30 +537,15 @@ float aspect float zoomSensitivity = GetPositive(profile.zoomSensitivity, 0.16f); if (navigation.projection == EditorViewportProjection.Orthographic) { - Vector2 mouse = NativeImGui.GetMousePos(); + Vector2 mouse = Ui.GetMousePos(); float normalizedX = Math.Clamp((mouse.X - minimum.X) / width, 0f, 1f); float normalizedY = Math.Clamp((mouse.Y - minimum.Y) / height, 0f, 1f); float previousSize = navigation.orthographicSize; - EngineVector3 previousOffset = GetViewportOffset( - normalizedX, - normalizedY, - previousSize, - aspect, - navigation.rotation); + EngineVector3 previousOffset = GetViewportOffset(normalizedX, normalizedY, previousSize, aspect, navigation.rotation); float minimumSize = GetPositive(profile.minimumOrthographicSize, 0.001f); - float maximumSize = MathF.Max( - minimumSize, - GetPositive(profile.maximumOrthographicSize, 100000f)); - float nextSize = Math.Clamp( - previousSize / EditorPlanarNavigation.WheelFactor(wheel, zoomSensitivity), - minimumSize, - maximumSize); - EngineVector3 nextOffset = GetViewportOffset( - normalizedX, - normalizedY, - nextSize, - aspect, - navigation.rotation); + float maximumSize = MathF.Max(minimumSize, GetPositive(profile.maximumOrthographicSize, 100000f)); + float nextSize = Math.Clamp(previousSize / EditorPlanarNavigation.WheelFactor(wheel, zoomSensitivity), minimumSize, maximumSize); + EngineVector3 nextOffset = GetViewportOffset(normalizedX, normalizedY, nextSize, aspect, navigation.rotation); EngineVector3 delta = previousOffset - nextOffset; navigation.position += delta; navigation.pivot += delta; @@ -672,13 +554,8 @@ float aspect } float minimumDistance = GetPositive(profile.minimumFocusDistance, 0.01f); - float maximumDistance = MathF.Max( - minimumDistance, - GetPositive(profile.maximumFocusDistance, 1000000f)); - navigation.focusDistance = Math.Clamp( - navigation.focusDistance / EditorPlanarNavigation.WheelFactor(wheel, zoomSensitivity), - minimumDistance, - maximumDistance); + float maximumDistance = MathF.Max(minimumDistance, GetPositive(profile.maximumFocusDistance, 1000000f)); + navigation.focusDistance = Math.Clamp(navigation.focusDistance / EditorPlanarNavigation.WheelFactor(wheel, zoomSensitivity), minimumDistance, maximumDistance); EngineVector3 direction = EngineVector3.Transform(EngineVector3.FORWARD, navigation.rotation); navigation.position = navigation.pivot - direction * navigation.focusDistance; } @@ -695,9 +572,7 @@ EditorViewportFocusBounds focus if (navigation.projection == EditorViewportProjection.Orthographic) { float minimumSize = GetPositive(profile.minimumOrthographicSize, 0.001f); - float maximumSize = MathF.Max( - minimumSize, - GetPositive(profile.maximumOrthographicSize, 100000f)); + float maximumSize = MathF.Max(minimumSize, GetPositive(profile.maximumOrthographicSize, 100000f)); navigation.orthographicSize = Math.Clamp(radius * padding, minimumSize, maximumSize); navigation.focusDistance = MathF.Max(navigation.focusDistance, radius * 2f); } @@ -705,27 +580,21 @@ EditorViewportFocusBounds focus { float halfFov = navigation.fieldOfView * MathF.PI / 360f; float distance = radius * padding / MathF.Max(0.001f, MathF.Tan(halfFov)); - navigation.focusDistance = Math.Clamp( - distance, - GetPositive(profile.minimumFocusDistance, 0.01f), - GetPositive(profile.maximumFocusDistance, 1000000f)); + navigation.focusDistance = Math.Clamp(distance, GetPositive(profile.minimumFocusDistance, 0.01f), GetPositive(profile.maximumFocusDistance, 1000000f)); } + navigation.position = focus.center - forward * navigation.focusDistance; } private static bool SupportsMode( EditorViewportNavigationProfile profile, EditorViewportNavigationMode mode - ) - => mode switch - { - EditorViewportNavigationMode.Orbit => - profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Orbit), - EditorViewportNavigationMode.Fly => - profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Fly), - _ => (profile.capabilities & EditorViewportNavigationCapabilities.Planar) != 0 - }; - + ) => mode switch + { + EditorViewportNavigationMode.Orbit => profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Orbit), + EditorViewportNavigationMode.Fly => profile.capabilities.HasFlag(EditorViewportNavigationCapabilities.Fly), + _ => (profile.capabilities & EditorViewportNavigationCapabilities.Planar) != 0 + }; private static EngineQuaternion ApplyConstrainedPitch( EngineQuaternion yawed, EngineVector3 right, @@ -735,21 +604,14 @@ float angle EngineQuaternion pitch = EngineQuaternion.CreateFromAxisAngle(right, angle); EngineQuaternion candidate = (pitch * yawed).normalized; EngineVector3 forward = EngineVector3.Transform(EngineVector3.FORWARD, candidate).normalized; - return MathF.Abs(EngineVector3.Dot(forward, worldUp)) < 0.999f - ? candidate - : yawed; + return MathF.Abs(EngineVector3.Dot(forward, worldUp)) < 0.999f ? candidate : yawed; } - private static EngineVector3 GetWorldUp(EditorViewportNavigationProfile profile) - => profile.worldUp.LengthSquared() > 0.000001f - ? profile.worldUp.normalized - : EngineVector3.UP; - + private static EngineVector3 GetWorldUp(EditorViewportNavigationProfile profile) => profile.worldUp.LengthSquared() > 0.000001f ? profile.worldUp.normalized : EngineVector3.UP; private static float GetPositive( float value, float fallback ) => float.IsFinite(value) && value > 0f ? value : fallback; - private static EngineVector3 GetViewportOffset( float normalizedX, float normalizedY, @@ -757,10 +619,7 @@ private static EngineVector3 GetViewportOffset( float aspect, EngineQuaternion rotation ) { - var local = new EngineVector3( - (normalizedX * 2f - 1f) * halfHeight * aspect, - (1f - normalizedY * 2f) * halfHeight, - 0f); + var local = new EngineVector3((normalizedX * 2f - 1f) * halfHeight * aspect, (1f - normalizedY * 2f) * halfHeight, 0f); return EngineVector3.Transform(local, rotation); } @@ -768,134 +627,90 @@ private void DrawUnavailable( Vector2 size, string message ) { - Vector2 minimum = NativeImGui.GetCursorScreenPos(); + Vector2 minimum = Ui.GetCursorScreenPosition(); Vector2 maximum = minimum + size; - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - EditorImGui.DrawFilledRectangle( - minimum, - maximum, - NativeImGui.ColorConvertFloat4ToU32(m_backgroundColor), - 0 - ); - NativeImGui.PushStyleColor((int)ImGuiCol.Text, EditorPalette.textDisabled); + EditorDrawList drawList = Ui.GetDrawList(); + Ui.DrawFilledRectangle(minimum, maximum, Ui.ToPackedColor(m_backgroundColor), 0); + Ui.PushStyleColor(ImGuiCol.Text, EditorPalette.textDisabled); try { - EditorWidget.CenteredWrappedText(message, size, S_UNAVAILABLE_PADDING); + EditorUI.current.widgets.CenteredWrappedText(message, size, S_UNAVAILABLE_PADDING); } finally { - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } } private static Inno.Core.Mathematics.Color ToEngineColor(Vector4 value) => new(value.X, value.Y, value.Z, value.W); - private bool DrawManipulationToolbar(ManipulationToolbarLayout layout) { - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - Vector4 background = EditorPalette.inspectorTargetHeader; + EditorDrawList drawList = Ui.GetDrawList(); + Vector4 background = EditorPalette.targetHeader; background.W = EditorPalette.opacityMedium; - EditorImGui.DrawFilledRectangle( - layout.minimum, - layout.maximum, - NativeImGui.ColorConvertFloat4ToU32(background), - EditorWidget.style.frameRounding - ); - + Ui.DrawFilledRectangle(layout.minimum, layout.maximum, Ui.ToPackedColor(background), EditorUI.current.widgets.style.frameRounding); float itemX = layout.minimum.X + layout.padding; float itemY = layout.minimum.Y + layout.padding; try { - if (DrawManipulationTool( - "move", - ImGuiIcon.UpDownLeftRight, - "Move", - new Vector2(itemX, itemY), - layout.itemSize, - m_operation == ImGuizmoOperation.Translate)) + if (DrawManipulationTool("move", ImGuiIcon.UpDownLeftRight, "Move", new Vector2(itemX, itemY), layout.itemSize, m_operation == ImGuizmoOperation.Translate)) { m_operation = ImGuizmoOperation.Translate; } - itemY += layout.itemSize.Y + layout.spacing; - if (DrawManipulationTool( - "rotate", - ImGuiIcon.Rotate, - "Rotate", - new Vector2(itemX, itemY), - layout.itemSize, - m_operation == ImGuizmoOperation.Rotate)) + itemY += layout.itemSize.Y + layout.spacing; + if (DrawManipulationTool("rotate", ImGuiIcon.Rotate, "Rotate", new Vector2(itemX, itemY), layout.itemSize, m_operation == ImGuizmoOperation.Rotate)) { m_operation = ImGuizmoOperation.Rotate; } - itemY += layout.itemSize.Y + layout.spacing; - if (DrawManipulationTool( - "scale", - ImGuiIcon.UpRightAndDownLeftFromCenter, - "Scale", - new Vector2(itemX, itemY), - layout.itemSize, - m_operation == ImGuizmoOperation.Scale)) + itemY += layout.itemSize.Y + layout.spacing; + if (DrawManipulationTool("scale", ImGuiIcon.UpRightAndDownLeftFromCenter, "Scale", new Vector2(itemX, itemY), layout.itemSize, m_operation == ImGuizmoOperation.Scale)) { m_operation = ImGuizmoOperation.Scale; } - itemY += layout.itemSize.Y + layout.sectionSpacing; + itemY += layout.itemSize.Y + layout.sectionSpacing; float separatorY = itemY - layout.sectionSpacing * 0.5f; - EditorImGui.DrawLine( + Ui.DrawLine( new Vector2(layout.minimum.X + layout.padding, separatorY), new Vector2(layout.maximum.X - layout.padding, separatorY), - NativeImGui.ColorConvertFloat4ToU32(Vector4.One), - EditorWidget.style.borderSize + Ui.ToPackedColor(Vector4.One), + EditorUI.current.widgets.style.borderSize ); - bool supportsCoordinateSpace = m_operation != ImGuizmoOperation.Scale; - string modeIcon = supportsCoordinateSpace && m_mode == ImGuizmoMode.World - ? ImGuiIcon.Globe - : ImGuiIcon.Cube; - string modeTooltip = !supportsCoordinateSpace - ? "Coordinate Space: Local (Scale is always local)" - : m_mode == ImGuizmoMode.Local - ? "Coordinate Space: Local (axes follow the selected transform)" - : "Coordinate Space: World (axes follow the world)"; + string modeIcon = supportsCoordinateSpace && m_mode == ImGuizmoMode.World ? ImGuiIcon.Globe : ImGuiIcon.Cube; + string modeTooltip = !supportsCoordinateSpace ? "Coordinate Space: Local (Scale is always local)" : m_mode == ImGuizmoMode.Local ? "Coordinate Space: Local (axes follow the selected transform)" : "Coordinate Space: World (axes follow the world)"; if (!supportsCoordinateSpace) - NativeImGui.BeginDisabled(true); + Ui.BeginDisabled(true); bool toggleCoordinateSpace; try { - toggleCoordinateSpace = DrawManipulationTool( - "coordinate_space", - modeIcon, - modeTooltip, - new Vector2(itemX, itemY), - layout.itemSize, - selected: false); + toggleCoordinateSpace = DrawManipulationTool("coordinate_space", modeIcon, modeTooltip, new Vector2(itemX, itemY), layout.itemSize, selected: false); } finally { if (!supportsCoordinateSpace) - NativeImGui.EndDisabled(); + Ui.EndDisabled(); } - if (!supportsCoordinateSpace && - NativeImGui.IsItemHovered((int)ImGuiHoveredFlags.AllowWhenDisabled) && - EditorWidget.BeginMenuTooltip()) + + if (!supportsCoordinateSpace && Ui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && EditorUI.current.widgets.BeginMenuTooltip()) { - NativeImGui.TextUnformatted(modeTooltip); - EditorWidget.EndMenuTooltip(); + Ui.Text(modeTooltip); + EditorUI.current.widgets.EndMenuTooltip(); } + if (toggleCoordinateSpace && supportsCoordinateSpace) { - m_mode = m_mode == ImGuizmoMode.Local - ? ImGuizmoMode.World - : ImGuizmoMode.Local; + m_mode = m_mode == ImGuizmoMode.Local ? ImGuizmoMode.World : ImGuizmoMode.Local; } } finally { - NativeImGui.SetCursorScreenPos(layout.viewportMaximum); - NativeImGui.Dummy(Vector2.Zero); + Ui.SetCursorScreenPosition(layout.viewportMaximum); + Ui.Dummy(Vector2.Zero); } + return IsManipulationToolbarHovered(layout); } @@ -907,36 +722,28 @@ private static bool DrawManipulationTool( Vector2 size, bool selected ) { - NativeImGui.SetCursorScreenPos(position); + Ui.SetCursorScreenPosition(position); if (selected) - NativeImGui.PushStyleColor((int)ImGuiCol.Text, Vector4.One); + Ui.PushStyleColor(ImGuiCol.Text, Vector4.One); bool pressed; try { - pressed = EditorWidget.ClickableText( - $"scene_transform_{id}", - icon, - size, - tooltip); + pressed = EditorUI.current.widgets.ClickableText($"scene_transform_{id}", icon, size, tooltip); } finally { if (selected) - NativeImGui.PopStyleColor(); + Ui.PopStyleColor(); } if (selected) { - Vector2 minimum = NativeImGui.GetItemRectMin(); - Vector2 maximum = NativeImGui.GetItemRectMax(); - float thickness = EditorWidget.style.interactionOverlayThickness; - EditorImGui.DrawFilledRectangle( - minimum, - new Vector2(minimum.X + thickness, maximum.Y), - NativeImGui.ColorConvertFloat4ToU32(Vector4.One), - 0 - ); + Vector2 minimum = Ui.GetItemMinimum(); + Vector2 maximum = Ui.GetItemMaximum(); + float thickness = EditorUI.current.widgets.style.interactionOverlayThickness; + Ui.DrawFilledRectangle(minimum, new Vector2(minimum.X + thickness, maximum.Y), Ui.ToPackedColor(Vector4.One), 0); } + return pressed; } @@ -944,18 +751,15 @@ private static ManipulationToolbarLayout CreateManipulationToolbarLayout( Vector2 viewportMinimum, Vector2 viewportMaximum ) { - float zoom = EditorWidget.style.zoom; + float zoom = EditorUI.current.widgets.style.zoom; float inset = 10f * zoom; float padding = 4f * zoom; float spacing = 2f * zoom; float sectionSpacing = 8f * zoom; - float itemExtent = MathF.Max(NativeImGui.GetFrameHeight(), 26f * zoom); + float itemExtent = MathF.Max(Ui.GetFrameHeight(), 26f * zoom); Vector2 itemSize = new(itemExtent); float width = itemExtent + padding * 2f; - float height = padding * 2f - + itemExtent * C_MANIPULATION_TOOL_COUNT - + spacing * 2f - + sectionSpacing; + float height = padding * 2f + itemExtent * C_MANIPULATION_TOOL_COUNT + spacing * 2f + sectionSpacing; Vector2 minimum = viewportMinimum + new Vector2(inset); Vector2 maximum = minimum + new Vector2(width, height); if (maximum.X > viewportMaximum.X - inset) @@ -964,136 +768,156 @@ Vector2 viewportMaximum minimum.X -= offset; maximum.X -= offset; } + if (maximum.Y > viewportMaximum.Y - inset) { float offset = maximum.Y - (viewportMaximum.Y - inset); minimum.Y -= offset; maximum.Y -= offset; } - return new ManipulationToolbarLayout( - minimum, - maximum, - viewportMaximum, - itemSize, - padding, - spacing, - sectionSpacing); - } - private static bool IsManipulationToolbarHovered(ManipulationToolbarLayout layout) - => NativeImGui.IsWindowHovered((int)ImGuiHoveredFlags.RootAndChildWindows) - && NativeImGui.IsMouseHoveringRect(layout.minimum, layout.maximum); + return new ManipulationToolbarLayout(minimum, maximum, viewportMaximum, itemSize, padding, spacing, sectionSpacing); + } - private unsafe bool DrawTransformGizmo( + private static bool IsManipulationToolbarHovered(ManipulationToolbarLayout layout) => Ui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows) + && Ui.IsMouseHoveringRect( + layout.minimum, + layout.maximum + ); + private bool DrawTransformGizmo( Vector2 minimum, Vector2 maximum, bool canInteract ) { - if (!m_rendering.TryGetManipulationSpace( - C_VIEWPORT_ID, - out EditorViewportManipulationSpace manipulationSpace) - || !TryGetSelectedTransform(out Transform? selected)) + if (!m_rendering.TryGetManipulationSpace(C_VIEWPORT_ID, out EditorViewportManipulationSpace manipulationSpace) || !TryGetSelectedTransform(out Transform? selected)) { return false; } - Transform target = m_gestureTarget ?? selected!; + if (m_gestureTarget is not null && !EditorUI.current.widgets.IsEditing(C_TRANSFORM_EDIT)) + { + m_gestureTarget = null; + m_gestureTargets = []; + } + + Transform target = m_gestureTarget is RuntimeIdentity identity + && m_interactions.TryResolveIdentity( + identity, + out IdentityObject? resolved + ) + && resolved is Transform active ? active : selected!; EngineMatrix world = target.localToWorldMatrix; - float* view = stackalloc float[16]; - float* projection = stackalloc float[16]; - float* model = stackalloc float[16]; - WriteColumnMajor(manipulationSpace.viewMatrix, view); - WriteColumnMajor(manipulationSpace.projectionMatrix, projection); - WriteColumnMajor(world, model); - - NativeImGuizmo.BeginFrame(); - NativeImGuizmo.Raw.SetDrawlist( - (Inno.Native.ImGuizmo.ImDrawList*)(nint)NativeImGui.Raw.GetWindowDrawList()); - NativeImGuizmo.SetRect( - minimum.X, - minimum.Y, - MathF.Max(1f, maximum.X - minimum.X), - MathF.Max(1f, maximum.Y - minimum.Y)); - NativeImGuizmo.SetOrthographic(manipulationSpace.isOrthographic); - NativeImGuizmo.SetGizmoSizeClipSpace(0.21f); - var gizmoStyle = NativeImGuizmo.Raw.GetStyle(); - gizmoStyle->TranslationLineThickness = 3.5f; - gizmoStyle->TranslationLineArrowSize = 9f; - gizmoStyle->RotationLineThickness = 3f; - gizmoStyle->RotationOuterLineThickness = 4f; - gizmoStyle->ScaleLineThickness = 3.5f; - gizmoStyle->ScaleLineCircleSize = 9f; - gizmoStyle->CenterCircleSize = 8f; - ImGuizmoMode effectiveMode = m_operation == ImGuizmoOperation.Scale - ? ImGuizmoMode.Local - : m_mode; ImGuizmoOperation operation = SelectManipulationOperation(manipulationSpace.plane, m_operation); - bool planarTranslation = manipulationSpace.plane != EditorViewportManipulationPlane.Spatial - && m_operation == ImGuizmoOperation.Translate; - bool changed; - bool isUsing; - bool isOver; - try - { - NativeImGuizmo.Enable(canInteract || m_gestureTarget is not null); - if (planarTranslation) - { - NativeImGuizmo.SetAxisMask( - manipulationSpace.plane == EditorViewportManipulationPlane.YZ, - manipulationSpace.plane == EditorViewportManipulationPlane.XZ, - manipulationSpace.plane == EditorViewportManipulationPlane.XY); - NativeImGuizmo.SetAxisLimit(float.PositiveInfinity); + bool planarTranslation = manipulationSpace.plane != EditorViewportManipulationPlane.Spatial && m_operation == ImGuizmoOperation.Translate; + TransformGizmoResult result = EditorUI.current.widgets.TransformGizmo( + world, + new TransformGizmoOptions { + minimum = minimum, + maximum = maximum, + view = manipulationSpace.viewMatrix, + projection = manipulationSpace.projectionMatrix, + orthographic = manipulationSpace.isOrthographic, + enabled = canInteract || m_gestureTarget is not null, + operation = operation, + space = m_operation == ImGuizmoOperation.Scale ? ImGuizmoMode.Local : m_mode, + maskX = planarTranslation && manipulationSpace.plane == EditorViewportManipulationPlane.YZ, + maskY = planarTranslation && manipulationSpace.plane == EditorViewportManipulationPlane.XZ, + maskZ = planarTranslation && manipulationSpace.plane == EditorViewportManipulationPlane.XY } - changed = NativeImGuizmo.Raw.Manipulate(view, projection, operation, effectiveMode, model, - null, null, null, null) != 0; - isUsing = NativeImGuizmo.IsUsing(); - isOver = NativeImGuizmo.IsOver(operation); + ); + if (Ui.IsKeyPressed(ImGuiKey.Escape, false) && m_gestureTarget is not null) + { + EditorUI.current.widgets.CancelEdits(); + m_gestureTarget = null; + m_gestureTargets = []; + return true; } - finally + + if (result.isUsing) { - NativeImGuizmo.Enable(true); - if (planarTranslation) + if (m_gestureTarget is null) { - NativeImGuizmo.SetAxisMask(false, false, false); - NativeImGuizmo.SetAxisLimit(C_IMGUIZMO_AXIS_LIMIT_DEFAULT); + m_gestureTarget = target.identity.runtimeIdentity; + m_gestureTargets = SelectedTransforms().Select(value => value.identity.runtimeIdentity!.Value).ToArray(); } + + EditorUI.current.widgets.BeginEdit(m_interactions.history, C_TRANSFORM_EDIT, "Transform Selection"); } - if (isUsing && m_gestureTarget is null) - { - m_gestureTarget = target; - m_gestureBefore = TransformSnapshot.Capture(target); - } - if (changed && TryReadManipulatedTransform( - ReadColumnMajor(model), target, m_operation, - out Inno.Core.Mathematics.Vector3 position, - out EngineQuaternion rotation, - out Inno.Core.Mathematics.Vector3 scale) - && IsUsable(position, rotation, scale)) + + if (result.changed && m_gestureTargets.Length != 0) { - target.SetWorldTransform(position, rotation, scale); + try + { + EngineMatrix delta = result.matrix * EngineMatrix.Invert(world); + foreach (RuntimeIdentity current in m_gestureTargets) + { + if (!m_interactions.TryResolveIdentity(current, out IdentityObject? value) || value is not Transform transform) + throw new InvalidOperationException("A transform gesture target is no longer available."); + EngineMatrix next = delta * transform.localToWorldMatrix; + if (!TryReadManipulatedTransform(next, transform, m_operation, out var position, out var rotation, out var scale) || !IsUsable(position, rotation, scale)) + throw new InvalidOperationException("The manipulated transform is not invertible or finite."); + _ = m_sceneEdits.ChangeProperty( + transform, + nameof(Transform.localPosition), + () => transform.SetWorldTransform(position, rotation, scale), + "Transform Selection", + EditorUI.current.widgets.editMergeKey + ); + } + } + catch + { + EditorUI.current.widgets.CancelEdits(); + m_gestureTarget = null; + m_gestureTargets = []; + throw; + } } - if (!isUsing && m_gestureTarget is not null) + + if (!result.isUsing && m_gestureTarget is not null) CommitGesture(); - return isUsing || isOver; + return result.isUsing || result.isOver; + } + + private Transform[] SelectedTransforms() + { + var selected = new List(); + m_interactions.selection.CopyTargets(selected); + Transform[] transforms = selected.Select(value => value switch + { + GameObject gameObject => gameObject.transform, + GameComponent component => component.transform, + _ => null + }).OfType().Distinct().ToArray(); + var set = transforms.ToHashSet(); + return transforms.Where(value => !HasSelectedAncestor(value, set)).ToArray(); + } + + private static bool HasSelectedAncestor( + Transform transform, + ISet selected + ) { + for (Transform? parent = transform.parent; parent is not null; parent = parent.parent) + if (selected.Contains(parent)) + return true; + return false; } private static ImGuizmoOperation SelectManipulationOperation( EditorViewportManipulationPlane plane, ImGuizmoOperation operation - ) - => operation switch + ) => operation switch + { + ImGuizmoOperation.Rotate => ImGuizmoOperation.Rotate, + ImGuizmoOperation.Scale => plane switch { - ImGuizmoOperation.Rotate => ImGuizmoOperation.Rotate, - ImGuizmoOperation.Scale => plane switch - { - EditorViewportManipulationPlane.XY => ImGuizmoOperation.ScaleX | ImGuizmoOperation.ScaleY, - EditorViewportManipulationPlane.XZ => ImGuizmoOperation.ScaleX | ImGuizmoOperation.ScaleZ, - EditorViewportManipulationPlane.YZ => ImGuizmoOperation.ScaleY | ImGuizmoOperation.ScaleZ, - _ => operation - }, + EditorViewportManipulationPlane.XY => ImGuizmoOperation.ScaleX | ImGuizmoOperation.ScaleY, + EditorViewportManipulationPlane.XZ => ImGuizmoOperation.ScaleX | ImGuizmoOperation.ScaleZ, + EditorViewportManipulationPlane.YZ => ImGuizmoOperation.ScaleY | ImGuizmoOperation.ScaleZ, _ => operation - }; - + }, + _ => operation + }; private static bool TryReadManipulatedTransform( EngineMatrix matrix, Transform target, @@ -1107,28 +931,37 @@ out Inno.Core.Mathematics.Vector3 scale scale = target.worldScale; if (operation == ImGuizmoOperation.Translate) return true; - if (operation == ImGuizmoOperation.Scale) { EngineMatrix original = target.localToWorldMatrix; scale = new Inno.Core.Mathematics.Vector3( - scale.x * SignedColumnRatio(original.m11, original.m21, original.m31, - matrix.m11, matrix.m21, matrix.m31), - scale.y * SignedColumnRatio(original.m12, original.m22, original.m32, - matrix.m12, matrix.m22, matrix.m32), - scale.z * SignedColumnRatio(original.m13, original.m23, original.m33, - matrix.m13, matrix.m23, matrix.m33)); + scale.x * SignedColumnRatio(original.m11, original.m21, original.m31, matrix.m11, matrix.m21, matrix.m31), + scale.y * SignedColumnRatio(original.m12, original.m22, original.m32, matrix.m12, matrix.m22, matrix.m32), + scale.z * SignedColumnRatio(original.m13, original.m23, original.m33, matrix.m13, matrix.m23, matrix.m33) + ); return true; } - if (MathF.Abs(scale.x) <= 0.00001f || MathF.Abs(scale.y) <= 0.00001f - || MathF.Abs(scale.z) <= 0.00001f) + if (MathF.Abs(scale.x) <= 0.00001f || MathF.Abs(scale.y) <= 0.00001f || MathF.Abs(scale.z) <= 0.00001f) return false; EngineMatrix normalized = new( - matrix.m11 / scale.x, matrix.m12 / scale.y, matrix.m13 / scale.z, 0f, - matrix.m21 / scale.x, matrix.m22 / scale.y, matrix.m23 / scale.z, 0f, - matrix.m31 / scale.x, matrix.m32 / scale.y, matrix.m33 / scale.z, 0f, - 0f, 0f, 0f, 1f); + matrix.m11 / scale.x, + matrix.m12 / scale.y, + matrix.m13 / scale.z, + 0f, + matrix.m21 / scale.x, + matrix.m22 / scale.y, + matrix.m23 / scale.z, + 0f, + matrix.m31 / scale.x, + matrix.m32 / scale.y, + matrix.m33 / scale.z, + 0f, + 0f, + 0f, + 0f, + 1f + ); rotation = EngineQuaternion.FromRotationMatrix(normalized).normalized; return true; } @@ -1142,9 +975,7 @@ private static float SignedColumnRatio( float changedZ ) { float squaredLength = originalX * originalX + originalY * originalY + originalZ * originalZ; - return squaredLength <= 0.0000000001f - ? 1f - : (originalX * changedX + originalY * changedY + originalZ * changedZ) / squaredLength; + return squaredLength <= 0.0000000001f ? 1f : (originalX * changedX + originalY * changedY + originalZ * changedZ) / squaredLength; } private bool TryGetSelectedTransform(out Transform? transform) @@ -1161,88 +992,31 @@ private bool TryGetSelectedTransform(out Transform? transform) private void CommitGesture() { - Transform? target = m_gestureTarget; - if (target is null) + if (m_gestureTarget is null) return; - TransformSnapshot before = m_gestureBefore; + if (EditorUI.current.widgets.IsEditing(C_TRANSFORM_EDIT)) + EditorUI.current.widgets.CommitEdits(); m_gestureTarget = null; - if (target.isDestroyed) - return; - TransformSnapshot after = TransformSnapshot.Capture(target); - before.ApplyLocal(target); - string name = m_operation switch - { - ImGuizmoOperation.Rotate => "Rotate GameObject", - ImGuizmoOperation.Scale => "Scale GameObject", - _ => "Move GameObject" - }; - using EditorHistoryTransaction transaction = m_interactions.history.BeginTransaction(name); - _ = m_sceneEdits.ChangeProperty( - target, - nameof(Transform.localPosition), - () => target.localPosition = after.position, - name); - _ = m_sceneEdits.ChangeProperty( - target, - nameof(Transform.localRotation), - () => target.localRotation = after.rotation, - name); - _ = m_sceneEdits.ChangeProperty( - target, - nameof(Transform.localScale), - () => target.localScale = after.scale, - name); - transaction.Commit(); + m_gestureTargets = []; } private static bool IsUsable( Inno.Core.Mathematics.Vector3 position, EngineQuaternion rotation, Inno.Core.Mathematics.Vector3 scale - ) - => float.IsFinite(position.x) - && float.IsFinite(position.y) - && float.IsFinite(position.z) - && float.IsFinite(rotation.x) - && float.IsFinite(rotation.y) - && float.IsFinite(rotation.z) - && float.IsFinite(rotation.w) - && float.IsFinite(scale.x) - && float.IsFinite(scale.y) - && float.IsFinite(scale.z) - && MathF.Abs(scale.x) > 0.00001f - && MathF.Abs(scale.y) > 0.00001f - && MathF.Abs(scale.z) > 0.00001f; - - private static unsafe void WriteColumnMajor( - EngineMatrix matrix, - float* destination - ) { - destination[0] = matrix.m11; - destination[1] = matrix.m21; - destination[2] = matrix.m31; - destination[3] = matrix.m41; - destination[4] = matrix.m12; - destination[5] = matrix.m22; - destination[6] = matrix.m32; - destination[7] = matrix.m42; - destination[8] = matrix.m13; - destination[9] = matrix.m23; - destination[10] = matrix.m33; - destination[11] = matrix.m43; - destination[12] = matrix.m14; - destination[13] = matrix.m24; - destination[14] = matrix.m34; - destination[15] = matrix.m44; - } - - private static unsafe EngineMatrix ReadColumnMajor(float* source) - => new( - source[0], source[4], source[8], source[12], - source[1], source[5], source[9], source[13], - source[2], source[6], source[10], source[14], - source[3], source[7], source[11], source[15]); - + ) => float.IsFinite(position.x) + && float.IsFinite(position.y) + && float.IsFinite(position.z) + && float.IsFinite(rotation.x) + && float.IsFinite(rotation.y) + && float.IsFinite(rotation.z) + && float.IsFinite(rotation.w) + && float.IsFinite(scale.x) + && float.IsFinite(scale.y) + && float.IsFinite(scale.z) + && MathF.Abs(scale.x) > 0.00001f + && MathF.Abs(scale.y) > 0.00001f + && MathF.Abs(scale.z) > 0.00001f; private readonly record struct ManipulationToolbarLayout( Vector2 minimum, Vector2 maximum, @@ -1252,23 +1026,6 @@ private readonly record struct ManipulationToolbarLayout( float spacing, float sectionSpacing ); - - private readonly record struct TransformSnapshot( - Inno.Core.Mathematics.Vector3 position, - EngineQuaternion rotation, - Inno.Core.Mathematics.Vector3 scale - ) { - internal static TransformSnapshot Capture(Transform transform) - => new(transform.localPosition, transform.localRotation, transform.localScale); - - internal void ApplyLocal(Transform transform) - { - transform.localPosition = position; - transform.localRotation = rotation; - transform.localScale = scale; - } - } - private enum NavigationDrag { None, diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Inno.Editor.Panel.Settings.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Inno.Editor.Panel.Settings.csproj index e86f51928..21f6e44c2 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Inno.Editor.Panel.Settings.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Inno.Editor.Panel.Settings.csproj @@ -4,34 +4,20 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor + - - - - - - + + + + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/BuildSettingsPresentation.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/BuildSettingsPresentation.cs index 6ff38a6f5..d5647db0a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/BuildSettingsPresentation.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/BuildSettingsPresentation.cs @@ -1,15 +1,12 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - using Inno.Build; using Inno.Build.Managed; -using EditorImGui = Inno.Editor.ImGui.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Settings; - internal enum BuildSettingsKey { GameProductName, @@ -28,7 +25,6 @@ internal enum BuildSettingsKey internal sealed class BuildSettingsField { private const int C_TEXT_CAPACITY = 4096; - internal BuildSettingsField( BuildSettingsKey key, string path, @@ -53,60 +49,37 @@ string description internal bool Draw( BuildSettings settings, BuildPipeline buildPipeline - ) - => key switch - { - BuildSettingsKey.GameProductName => DrawTextValue( - settings.gameProductName, - value => settings.gameProductName = value), - BuildSettingsKey.GamePersistentDataPath => DrawTextValue( - settings.gamePersistentDataPath, - value => settings.gamePersistentDataPath = value), - BuildSettingsKey.GameStartupScene => DrawTextValue( - settings.gameStartupScene, - value => settings.gameStartupScene = value), - BuildSettingsKey.GameWindowWidth => DrawPositiveInt( - settings.gameWindowWidth, - value => settings.gameWindowWidth = value), - BuildSettingsKey.GameWindowHeight => DrawPositiveInt( - settings.gameWindowHeight, - value => settings.gameWindowHeight = value), - BuildSettingsKey.GameTarget => DrawTarget(settings, buildPipeline), - BuildSettingsKey.GameManagedDeployment => DrawManagedDeployment(settings, buildPipeline), - BuildSettingsKey.GameOutputDirectory => DrawTextValue( - settings.gameOutputDirectory, - value => settings.gameOutputDirectory = value), - BuildSettingsKey.PluginDisplayName => DrawTextValue( - settings.pluginDisplayName, - value => settings.pluginDisplayName = value), - BuildSettingsKey.PluginOutputPath => DrawTextValue( - settings.pluginOutputPath, - value => settings.pluginOutputPath = value), - BuildSettingsKey.IncludePluginDependencies => DrawIncludeDependencies(settings), - _ => throw new InvalidOperationException($"Unknown Build Settings field '{key}'.") - }; - + ) => key switch + { + BuildSettingsKey.GameProductName => DrawTextValue(settings.gameProductName, value => settings.gameProductName = value), + BuildSettingsKey.GamePersistentDataPath => DrawTextValue(settings.gamePersistentDataPath, value => settings.gamePersistentDataPath = value), + BuildSettingsKey.GameStartupScene => DrawTextValue(settings.gameStartupScene, value => settings.gameStartupScene = value), + BuildSettingsKey.GameWindowWidth => DrawPositiveInt(settings.gameWindowWidth, value => settings.gameWindowWidth = value), + BuildSettingsKey.GameWindowHeight => DrawPositiveInt(settings.gameWindowHeight, value => settings.gameWindowHeight = value), + BuildSettingsKey.GameTarget => DrawTarget(settings, buildPipeline), + BuildSettingsKey.GameManagedDeployment => DrawManagedDeployment(settings, buildPipeline), + BuildSettingsKey.GameOutputDirectory => DrawTextValue(settings.gameOutputDirectory, value => settings.gameOutputDirectory = value), + BuildSettingsKey.PluginDisplayName => DrawTextValue(settings.pluginDisplayName, value => settings.pluginDisplayName = value), + BuildSettingsKey.PluginOutputPath => DrawTextValue(settings.pluginOutputPath, value => settings.pluginOutputPath = value), + BuildSettingsKey.IncludePluginDependencies => DrawIncludeDependencies(settings), + _ => throw new InvalidOperationException($"Unknown Build Settings field '{key}'.")}; internal bool IsDefault( BuildSettings settings, BuildSettings defaults - ) - => key switch - { - BuildSettingsKey.GameProductName => settings.gameProductName == defaults.gameProductName, - BuildSettingsKey.GamePersistentDataPath => settings.gamePersistentDataPath == defaults.gamePersistentDataPath, - BuildSettingsKey.GameStartupScene => settings.gameStartupScene == defaults.gameStartupScene, - BuildSettingsKey.GameWindowWidth => settings.gameWindowWidth == defaults.gameWindowWidth, - BuildSettingsKey.GameWindowHeight => settings.gameWindowHeight == defaults.gameWindowHeight, - BuildSettingsKey.GameTarget => settings.gameTarget == defaults.gameTarget, - BuildSettingsKey.GameManagedDeployment => settings.gameManagedDeployment == defaults.gameManagedDeployment, - BuildSettingsKey.GameOutputDirectory => settings.gameOutputDirectory == defaults.gameOutputDirectory, - BuildSettingsKey.PluginDisplayName => settings.pluginDisplayName == defaults.pluginDisplayName, - BuildSettingsKey.PluginOutputPath => settings.pluginOutputPath == defaults.pluginOutputPath, - BuildSettingsKey.IncludePluginDependencies => - settings.includePluginDependencies == defaults.includePluginDependencies, - _ => throw new InvalidOperationException($"Unknown Build Settings field '{key}'.") - }; - + ) => key switch + { + BuildSettingsKey.GameProductName => settings.gameProductName == defaults.gameProductName, + BuildSettingsKey.GamePersistentDataPath => settings.gamePersistentDataPath == defaults.gamePersistentDataPath, + BuildSettingsKey.GameStartupScene => settings.gameStartupScene == defaults.gameStartupScene, + BuildSettingsKey.GameWindowWidth => settings.gameWindowWidth == defaults.gameWindowWidth, + BuildSettingsKey.GameWindowHeight => settings.gameWindowHeight == defaults.gameWindowHeight, + BuildSettingsKey.GameTarget => settings.gameTarget == defaults.gameTarget, + BuildSettingsKey.GameManagedDeployment => settings.gameManagedDeployment == defaults.gameManagedDeployment, + BuildSettingsKey.GameOutputDirectory => settings.gameOutputDirectory == defaults.gameOutputDirectory, + BuildSettingsKey.PluginDisplayName => settings.pluginDisplayName == defaults.pluginDisplayName, + BuildSettingsKey.PluginOutputPath => settings.pluginOutputPath == defaults.pluginOutputPath, + BuildSettingsKey.IncludePluginDependencies => settings.includePluginDependencies == defaults.includePluginDependencies, + _ => throw new InvalidOperationException($"Unknown Build Settings field '{key}'.")}; internal void Reset( BuildSettings settings, BuildSettings defaults @@ -154,27 +127,48 @@ BuildSettings defaults internal static bool ValuesEqual( BuildSettings left, BuildSettings right + ) => string.Equals( + left.gameProductName, + right.gameProductName, + StringComparison.Ordinal ) - => string.Equals(left.gameProductName, right.gameProductName, StringComparison.Ordinal) - && string.Equals(left.gamePersistentDataPath, right.gamePersistentDataPath, StringComparison.Ordinal) - && string.Equals(left.gameStartupScene, right.gameStartupScene, StringComparison.Ordinal) - && string.Equals(left.gameOutputDirectory, right.gameOutputDirectory, StringComparison.Ordinal) - && left.gameWindowWidth == right.gameWindowWidth - && left.gameWindowHeight == right.gameWindowHeight - && left.gameTarget == right.gameTarget - && left.gameManagedDeployment == right.gameManagedDeployment - && string.Equals(left.pluginDisplayName, right.pluginDisplayName, StringComparison.Ordinal) - && string.Equals(left.pluginOutputPath, right.pluginOutputPath, StringComparison.Ordinal) - && left.includePluginDependencies == right.includePluginDependencies; - + && string.Equals( + left.gamePersistentDataPath, + right.gamePersistentDataPath, + StringComparison.Ordinal + ) + && string.Equals( + left.gameStartupScene, + right.gameStartupScene, + StringComparison.Ordinal + ) + && string.Equals( + left.gameOutputDirectory, + right.gameOutputDirectory, + StringComparison.Ordinal + ) + && left.gameWindowWidth == right.gameWindowWidth + && left.gameWindowHeight == right.gameWindowHeight + && left.gameTarget == right.gameTarget + && left.gameManagedDeployment == right.gameManagedDeployment + && string.Equals( + left.pluginDisplayName, + right.pluginDisplayName, + StringComparison.Ordinal + ) + && string.Equals( + left.pluginOutputPath, + right.pluginOutputPath, + StringComparison.Ordinal + ) + && left.includePluginDependencies == right.includePluginDependencies; private static bool DrawTextValue( string value, Action apply ) { - NativeImGui.SetNextItemWidth(-1f); - if (!EditorImGui.InputText("##value", ref value, C_TEXT_CAPACITY)) + Ui.SetNextItemWidth(-1f); + if (!Ui.InputText("##value", ref value, C_TEXT_CAPACITY)) return false; - apply(value); return true; } @@ -183,10 +177,9 @@ private static bool DrawPositiveInt( int value, Action apply ) { - NativeImGui.SetNextItemWidth(-1f); - if (!NativeImGui.InputInt("##value", ref value, 1, 100, 0)) + Ui.SetNextItemWidth(-1f); + if (!Ui.InputInt("##value", ref value, 1, 100, 0)) return false; - apply(Math.Max(1, value)); return true; } @@ -196,19 +189,20 @@ private static bool DrawManagedDeployment( BuildPipeline buildPipeline ) { bool changed = false; - NativeImGui.SetNextItemWidth(-1f); - if (!EditorWidget.BeginBoundedCombo("##value", settings.gameManagedDeployment?.value ?? "Platform default")) + Ui.SetNextItemWidth(-1f); + if (!EditorUI.current.widgets.BeginBoundedCombo("##value", settings.gameManagedDeployment?.value ?? "Platform default")) return false; try { - if (NativeImGui.Selectable("Platform default", settings.gameManagedDeployment is null)) + if (Ui.Selectable("Platform default", settings.gameManagedDeployment is null)) { settings.gameManagedDeployment = null; changed = true; } + foreach (ManagedDeploymentId deployment in buildPipeline.GetManagedDeployments(settings.gameTarget)) { - if (!NativeImGui.Selectable(deployment.value, settings.gameManagedDeployment == deployment)) + if (!Ui.Selectable(deployment.value, settings.gameManagedDeployment == deployment)) continue; settings.gameManagedDeployment = deployment; changed = true; @@ -216,8 +210,9 @@ BuildPipeline buildPipeline } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } + return changed; } @@ -226,10 +221,8 @@ private static bool DrawTarget( BuildPipeline buildPipeline ) { bool changed = false; - NativeImGui.SetNextItemWidth(-1f); - if (!EditorWidget.BeginBoundedCombo( - "##value", - GetTargetLabel(settings.gameTarget, buildPipeline))) + Ui.SetNextItemWidth(-1f); + if (!EditorUI.current.widgets.BeginBoundedCombo("##value", GetTargetLabel(settings.gameTarget, buildPipeline))) return false; try { @@ -238,17 +231,17 @@ BuildPipeline buildPipeline } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } + return changed; } private static bool DrawIncludeDependencies(BuildSettings settings) { bool value = settings.includePluginDependencies; - if (!NativeImGui.Checkbox("##value", ref value)) + if (!Ui.Checkbox("##value", ref value)) return false; - settings.includePluginDependencies = value; return true; } @@ -259,84 +252,68 @@ private static bool DrawTargetChoice( BuildPipeline buildPipeline ) { bool selected = settings.gameTarget == target; - bool changed = NativeImGui.Selectable( - GetTargetLabel(target, buildPipeline), - selected); + bool changed = Ui.Selectable(GetTargetLabel(target, buildPipeline), selected); if (changed) settings.gameTarget = target; if (selected) - NativeImGui.SetItemDefaultFocus(); + Ui.SetItemDefaultFocus(); return changed; } private static string GetTargetLabel( BuildTargetId target, BuildPipeline buildPipeline - ) - => buildPipeline.TryGetGameTargetDisplayName(target, out string displayName) - ? displayName - : $"Missing ({target})"; - + ) => buildPipeline.TryGetGameTargetDisplayName(target, out string displayName) ? displayName : $"Missing ({target})"; } internal static class BuildSettingsPresentation { - internal static IReadOnlyList fields { get; } = - [ - new BuildSettingsField( - BuildSettingsKey.GameProductName, - "Build/Game/Product Name", - "Game Export Defaults", - "Player-facing product name copied into each game export."), + internal static IReadOnlyList fields { get; } = [ + new BuildSettingsField(BuildSettingsKey.GameProductName, "Build/Game/Product Name", "Game Export Defaults", "Player-facing product name copied into each game export."), new BuildSettingsField( BuildSettingsKey.GamePersistentDataPath, "Build/Game/Persistent Data Folder", "Game Export Defaults", - "Folder below local application data. Empty uses the Project ID; use portable names separated by '/'."), + "Folder below local application data. Empty uses the Project ID; use portable names separated by '/'." + ), new BuildSettingsField( BuildSettingsKey.GameStartupScene, "Build/Game/Startup Scene", "Game Export Defaults", - "Mount-qualified Scene path loaded when the exported Player starts."), - new BuildSettingsField( - BuildSettingsKey.GameWindowWidth, - "Build/Game/Initial Window Width", - "Game Export Defaults", - "Initial logical Player window width."), - new BuildSettingsField( - BuildSettingsKey.GameWindowHeight, - "Build/Game/Initial Window Height", - "Game Export Defaults", - "Initial logical Player window height."), - new BuildSettingsField( - BuildSettingsKey.GameTarget, - "Build/Game/Target", - "Game Export Defaults", - "Platform target selected when the game export window opens."), + "Mount-qualified Scene path loaded when the exported Player starts." + ), + new BuildSettingsField(BuildSettingsKey.GameWindowWidth, "Build/Game/Initial Window Width", "Game Export Defaults", "Initial logical Player window width."), + new BuildSettingsField(BuildSettingsKey.GameWindowHeight, "Build/Game/Initial Window Height", "Game Export Defaults", "Initial logical Player window height."), + new BuildSettingsField(BuildSettingsKey.GameTarget, "Build/Game/Target", "Game Export Defaults", "Platform target selected when the game export window opens."), new BuildSettingsField( BuildSettingsKey.GameManagedDeployment, "Build/Game/Managed Deployment", "Game Export Defaults", - "Selects the managed publisher independently of the platform. AOT compiles code before execution."), + "Selects the managed publisher independently of the platform. AOT compiles code before execution." + ), new BuildSettingsField( BuildSettingsKey.GameOutputDirectory, "Build/Game/Output Directory", "Game Export Defaults", - "Game output directory. Relative paths resolve from the project root."), + "Game output directory. Relative paths resolve from the project root." + ), new BuildSettingsField( BuildSettingsKey.PluginDisplayName, "Build/Plugin/Display Name", "Plugin Export Defaults", - "User-facing Plugin name copied into each Plugin export."), + "User-facing Plugin name copied into each Plugin export." + ), new BuildSettingsField( BuildSettingsKey.PluginOutputPath, "Build/Plugin/Destination IPlugin", "Plugin Export Defaults", - "Package destination ending in .iplugin. Relative paths resolve from the project root."), + "Package destination ending in .iplugin. Relative paths resolve from the project root." + ), new BuildSettingsField( BuildSettingsKey.IncludePluginDependencies, "Build/Plugin/Include Dependencies", "Plugin Export Defaults", - "Whether dependency Plugin packages are embedded by default. Export changes remain temporary.") + "Whether dependency Plugin packages are embedded by default. Export changes remain temporary." + ) ]; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/ProjectIdentitySettingsEditor.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/ProjectIdentitySettingsEditor.cs index 56412cbd0..97ebdacc5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/ProjectIdentitySettingsEditor.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/ProjectIdentitySettingsEditor.cs @@ -1,24 +1,20 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - using Inno.Core.Settings; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Settings; using EditorImGui = Inno.Editor.ImGui.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.Panel.Settings; - [ProjectSettingPath("Project/Identity/Project ID")] internal sealed class ProjectIdentitySettingsEditor : ProjectSettingEditor { private string m_error = string.Empty; - /// /// Gets the presentation section that groups this setting. /// public override string section => "Identity"; - /// /// Gets the user-facing explanation of this feature or setting. /// @@ -45,7 +41,8 @@ protected override void OnDraw(ProjectIdentitySettings setting) m_error = exception.Message; } } + if (!string.IsNullOrEmpty(m_error)) - ImGuiWidget.ColoredText(EditorPalette.error, m_error); + EditorUI.current.widgets.ColoredText(EditorPalette.error, m_error); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsModal.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsModal.cs index 3806e6b07..38b33cbd6 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsModal.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsModal.cs @@ -1,57 +1,50 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; using System; +using System.IO; using System.Collections.Generic; using System.Numerics; - using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Settings; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Inno.Core.Logging; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Settings; - [EditorModal("editor.settings", "Settings", order: 100)] -internal sealed unsafe class SettingsModal( +internal sealed class SettingsModal( EditorSettings editorSettings, ProjectSettingsEditor projectSettings, - SettingsWindowModule window + SettingsWindowModule window, + LogRouter logs ) : EditorModal { private readonly SettingsNavigation m_navigation = new(); private readonly SettingsTree m_tree = new(); - - private SettingsEditSession? m_navigationSession; + private long m_navigationRevision = -1; private string m_query = string.Empty; private float m_treePaneRatio = 0.25f; - + private readonly Logger m_log = logs.CreateLogger(); + private string m_applyError = string.Empty; /// /// Gets whether this implementation is visible. /// public override bool isVisible => window.isVisible; - /// /// Gets whether this implementation can move. /// public override bool canMove => true; - /// /// Gets whether this implementation can resize. /// public override bool canResize => true; - /// /// Lets the navigation tree and current settings page own their scrolling. /// public override bool allowScrolling => false; - /// /// Gets the preferred initial window size in logical editor units. /// public override Vector2 initialSize => new(1050f, 700f); - /// /// Gets the smallest editor window size that keeps every setting control usable. /// @@ -64,12 +57,31 @@ SettingsWindowModule window /// The context that supplies state and services for this operation. /// protected override void OnDraw(EditorContext context) + { + try + { + DrawWindow(); + } + catch (Exception failure) + { + try + { + window.FailPresentation(); + } + catch (Exception retirementFailure) + { + throw new AggregateException("Settings presentation and its owned retirement failed.", failure, retirementFailure); + } + throw; + } + } + + private void DrawWindow() { SettingsEditSession? session = window.session; if (session is null) return; - if (session.editorCatalogRevision != editorSettings.catalogRevision || - session.projectCatalogRevision != projectSettings.catalogRevision) + if (session.editorCatalogRevision != editorSettings.catalogRevision || session.projectCatalogRevision != projectSettings.catalogRevision) { window.Refresh(); session = window.session; @@ -78,20 +90,25 @@ protected override void OnDraw(EditorContext context) } IReadOnlyList pages = session.pages; - EnsureNavigationSession(session, pages); + EnsureNavigationSession(pages); SettingsPage? selected = SettingsTree.FindPage(pages, m_navigation.currentPath); selected ??= SettingsTree.FindFirstMatch(pages, m_query); if (selected is not null) m_navigation.Replace(selected.path); - - float footerHeight = NativeImGui.GetFrameHeightWithSpacing() + - NativeImGui.Raw.GetStyle()->ItemSpacing.Y; - Vector2 available = NativeImGui.GetContentRegionAvail(); - DrawBody( - session, - pages, - new Vector2(available.X, MathF.Max(1f, available.Y - footerHeight))); - NativeImGui.Separator(); + float footerHeight = Ui.GetFrameHeightWithSpacing() + Ui.GetLayout().itemSpacing.Y; + if (m_applyError.Length != 0) + footerHeight += Ui.GetFrameHeightWithSpacing(); + Vector2 available = Ui.GetContentRegionAvailable(); + DrawBody(session, pages, new Vector2(available.X, MathF.Max(1f, available.Y - footerHeight))); + Ui.Separator(); + if (m_applyError.Length != 0) + { + EditorUiSession.current.widgets.ColoredText(EditorPalette.error, + session.isHistoryFaulted ? "History integrity was lost. Restart the Editor." + : session.canApply ? "Apply failed. Edits remain staged; retry or Cancel." + : "Settings recovery is required. See Console for details."); + EditorUiSession.current.widgets.DrawItemTooltip(m_applyError); + } DrawButtons(session); } @@ -100,46 +117,33 @@ private void DrawBody( IReadOnlyList pages, Vector2 size ) { - ImGuiTableFlags flags = ImGuiTableFlags.NoPadOuterX | - ImGuiTableFlags.NoKeepColumnsVisible | - ImGuiTableFlags.SizingFixedFit | - ImGuiTableFlags.NoSavedSettings; - float splitterWidth = MathF.Max( - 5f * EditorWidget.style.zoom, - NativeImGui.Raw.GetStyle()->DockingSeparatorSize); + ImGuiTableFlags flags = ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.NoKeepColumnsVisible | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings; + float splitterWidth = MathF.Max(5f * EditorUiSession.current.widgets.style.zoom, Ui.GetLayout().dockingSeparatorSize); float treeWidth = ResolveTreeWidth(size.X, splitterWidth); - NativeImGui.PushStyleVar((int)ImGuiStyleVar.CellPadding, Vector2.Zero); + Ui.PushStyleVar(ImGuiStyleVar.CellPadding, Vector2.Zero); bool tableStarted = false; try { - tableStarted = NativeImGui.BeginTable("##settings_split", 3, (int)flags, size); + tableStarted = Ui.BeginTable("##settings_split", 3, flags, size); if (tableStarted) { - NativeImGui.TableSetupColumn( - "##settings_tree", - (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), - treeWidth); - NativeImGui.TableSetupColumn( - "##settings_splitter", - (int)(ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), - splitterWidth); - NativeImGui.TableSetupColumn( - "##settings_page", - (int)ImGuiTableColumnFlags.WidthStretch); - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); + Ui.TableSetupColumn("##settings_tree", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), treeWidth); + Ui.TableSetupColumn("##settings_splitter", (ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.NoResize), splitterWidth); + Ui.TableSetupColumn("##settings_page", ImGuiTableColumnFlags.WidthStretch); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); DrawTreePane(pages, size.Y); - _ = NativeImGui.TableSetColumnIndex(1); + _ = Ui.TableSetColumnIndex(1); DrawSplitter(splitterWidth, size.X, treeWidth, size.Y); - _ = NativeImGui.TableSetColumnIndex(2); + _ = Ui.TableSetColumnIndex(2); DrawPagePane(session, pages, size.Y); } } finally { if (tableStarted) - NativeImGui.EndTable(); - NativeImGui.PopStyleVar(); + Ui.EndTable(); + Ui.PopStyleVar(); } } @@ -147,24 +151,21 @@ private void DrawTreePane( IReadOnlyList pages, float height ) { - bool visible = NativeImGui.BeginChild( + bool visible = Ui.BeginChild( "##settings_tree_pane", new Vector2(0f, height), - (int)(ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding), - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)); + (ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding), + (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) + ); try { if (!visible) return; DrawNavigationButtons(); - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarSectionSpacing); + Ui.SameLine(0f, EditorUiSession.current.widgets.style.toolbarSectionSpacing); bool wasSearching = !string.IsNullOrWhiteSpace(m_query); - NativeImGui.SetNextItemWidth(-1f); - bool queryChanged = EditorWidget.SearchInput( - "settings", - "Search settings", - ref m_query, - width: -1f); + Ui.SetNextItemWidth(-1f); + bool queryChanged = EditorUiSession.current.widgets.SearchInput("settings", "Search settings", ref m_query, width: -1f); if (queryChanged) { SettingsPage? first = SettingsTree.FindFirstMatch(pages, m_query); @@ -180,27 +181,24 @@ float height m_navigation.Replace(string.Empty); } } - NativeImGui.Separator(); - bool scrollVisible = NativeImGui.BeginChild("##settings_tree_scroll", Vector2.Zero, 0, 0); + + Ui.Separator(); + bool scrollVisible = Ui.BeginChild("##settings_tree_scroll", Vector2.Zero, 0, 0); try { if (scrollVisible) { - m_tree.Draw( - pages, - m_query, - m_navigation.currentPath, - page => m_navigation.NavigateTo(page.path)); + m_tree.Draw(pages, m_query, m_navigation.currentPath, page => m_navigation.NavigateTo(page.path)); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } @@ -209,10 +207,7 @@ private void DrawPagePane( IReadOnlyList pages, float height ) { - bool visible = NativeImGui.BeginChild( - "##settings_page_pane", - new Vector2(0f, height), - (int)(ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding)); + bool visible = Ui.BeginChild("##settings_page_pane", new Vector2(0f, height), (ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding)); try { if (!visible) @@ -229,12 +224,12 @@ float height } else { - NativeImGui.TextUnformatted("No matching settings"); + Ui.Text("No matching settings"); } } finally { - NativeImGui.EndChild(); + Ui.EndChild(); } } @@ -244,41 +239,28 @@ private void DrawSplitter( float treeWidth, float height ) { - _ = NativeImGui.InvisibleButton( - "##settings_tree_splitter_grip", - new Vector2(width, MathF.Max(1f, height))); - bool hovered = NativeImGui.IsItemHovered(); - bool active = NativeImGui.IsItemActive(); + _ = Ui.InvisibleButton("##settings_tree_splitter_grip", new Vector2(width, MathF.Max(1f, height))); + bool hovered = Ui.IsItemHovered(); + bool active = Ui.IsItemActive(); if (hovered || active) - NativeImGui.SetMouseCursor((int)ImGuiMouseCursor.ResizeEw); + Ui.SetMouseCursor(ImGuiMouseCursor.ResizeEw); if (active) { - Vector2 delta = NativeImGui.GetMouseDragDelta((int)ImGuiMouseButton.Left); + Vector2 delta = Ui.GetMouseDragDelta(ImGuiMouseButton.Left); if (MathF.Abs(delta.X) > 0f) { float usable = MathF.Max(0f, availableWidth - width); float requested = ClampTreeWidth(treeWidth + delta.X, usable); - m_treePaneRatio = usable > float.Epsilon - ? Math.Clamp(requested / usable, 0f, 1f) - : 0.25f; - NativeImGui.ResetMouseDragDelta((int)ImGuiMouseButton.Left); + m_treePaneRatio = usable > float.Epsilon ? Math.Clamp(requested / usable, 0f, 1f) : 0.25f; + Ui.ResetMouseDragDelta(ImGuiMouseButton.Left); } } - Vector2 minimum = NativeImGui.GetItemRectMin(); - Vector2 maximum = NativeImGui.GetItemRectMax(); - System.Numerics.Vector4 color = active - ? EditorPalette.assetAccent - : hovered - ? EditorPalette.assetBorder - : EditorPalette.assetBorderSoft; + Vector2 minimum = Ui.GetItemMinimum(); + Vector2 maximum = Ui.GetItemMaximum(); + System.Numerics.Vector4 color = active ? EditorPalette.contentAccent : hovered ? EditorPalette.contentBorder : EditorPalette.contentBorderSoft; float centerX = (minimum.X + maximum.X) * 0.5f; - EditorImGui.DrawLine( - new Vector2(centerX, minimum.Y), - new Vector2(centerX, maximum.Y), - NativeImGui.ColorConvertFloat4ToU32(color), - EditorWidget.style.borderSize - ); + Ui.DrawLine(new Vector2(centerX, minimum.Y), new Vector2(centerX, maximum.Y), Ui.ToPackedColor(color), EditorUiSession.current.widgets.style.borderSize); } private float ResolveTreeWidth( @@ -293,72 +275,101 @@ private static float ClampTreeWidth( float requested, float usable ) { - float minimum = MathF.Min( - EditorWidget.style.assetPaneMinimumVisibleWidth, - usable * 0.5f); + float minimum = MathF.Min(EditorUiSession.current.widgets.style.paneMinimumVisibleWidth, usable * 0.5f); float maximum = MathF.Max(minimum, usable - minimum); return Math.Clamp(requested, minimum, maximum); } private void DrawButtons(SettingsEditSession session) { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); + EditorUiLayout style = Ui.GetLayout(); if (!session.isDirty) { - float okWidth = NativeImGui.CalcTextSize("OK").X + style->FramePadding.X * 2f; - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + - MathF.Max(0f, NativeImGui.GetContentRegionAvail().X - okWidth)); - if (NativeImGui.Button("OK")) - window.Close(); + float okWidth = Ui.MeasureText("OK").X + style.framePadding.X * 2f; + Ui.SetCursorPosX(Ui.GetCursorPosX() + MathF.Max(0f, Ui.GetContentRegionAvailable().X - okWidth)); + if (Ui.Button("OK")) + TryClose(); return; } - float cancelWidth = NativeImGui.CalcTextSize("Cancel").X + style->FramePadding.X * 2f; - float applyWidth = NativeImGui.CalcTextSize("Apply").X + style->FramePadding.X * 2f; - float totalWidth = cancelWidth + style->ItemSpacing.X + applyWidth; - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + - MathF.Max(0f, NativeImGui.GetContentRegionAvail().X - totalWidth)); - if (NativeImGui.Button("Cancel")) + float cancelWidth = Ui.MeasureText("Cancel").X + style.framePadding.X * 2f; + float applyWidth = Ui.MeasureText("Apply").X + style.framePadding.X * 2f; + float totalWidth = cancelWidth + style.itemSpacing.X + applyWidth; + Ui.SetCursorPosX(Ui.GetCursorPosX() + MathF.Max(0f, Ui.GetContentRegionAvailable().X - totalWidth)); + if (Ui.Button("Cancel")) + TryClose(); + Ui.SameLine(0f, -1f); + Ui.BeginDisabled(!session.canApply); + try + { + if (Ui.Button("Apply")) + { + try + { + _ = session.Apply(); + m_applyError = string.Empty; + } + catch (Exception failure) when (IsOperationalFailure(failure)) + { + ReportFailure(failure); + } + } + } + finally + { + Ui.EndDisabled(); + } + } + + private void TryClose() + { + try + { window.Close(); + m_applyError = string.Empty; + } + catch (Exception failure) when (IsOperationalFailure(failure)) + { + ReportFailure(failure); + } + } - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.Button("Apply")) - _ = session.Apply(); + private void ReportFailure(Exception failure) + { + m_applyError = failure.Message; + m_log.Write(LogLevel.Error, "Settings operation failed; edits and History ownership were retained: {0}", [failure]); } + private static bool IsOperationalFailure(Exception failure) + => failure is IOException or UnauthorizedAccessException or ArgumentException or InvalidOperationException or AggregateException; + private void DrawNavigationButtons() { - float frameHeight = NativeImGui.GetFrameHeight(); + float frameHeight = Ui.GetFrameHeight(); var buttonSize = new Vector2(frameHeight, frameHeight); - NativeImGui.BeginDisabled(!m_navigation.canGoBack); - if (NativeImGui.Button( - $"{Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleLeft}##SettingsBack", - buttonSize)) + Ui.BeginDisabled(!m_navigation.canGoBack); + if (Ui.Button($"{Inno.Editor.ImGui.ImGuiIcon.AngleLeft}##SettingsBack", buttonSize)) { m_navigation.GoBack(); } - NativeImGui.EndDisabled(); - NativeImGui.SameLine(0f, EditorWidget.style.assetToolbarTightSpacing); - NativeImGui.BeginDisabled(!m_navigation.canGoForward); - if (NativeImGui.Button( - $"{Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleRight}##SettingsForward", - buttonSize)) + Ui.EndDisabled(); + Ui.SameLine(0f, EditorUiSession.current.widgets.style.toolbarTightSpacing); + Ui.BeginDisabled(!m_navigation.canGoForward); + if (Ui.Button($"{Inno.Editor.ImGui.ImGuiIcon.AngleRight}##SettingsForward", buttonSize)) { m_navigation.GoForward(); } - NativeImGui.EndDisabled(); + + Ui.EndDisabled(); } - private void EnsureNavigationSession( - SettingsEditSession session, - IReadOnlyList pages - ) { - if (ReferenceEquals(m_navigationSession, session)) + private void EnsureNavigationSession(IReadOnlyList pages) + { + if (m_navigationRevision == window.sessionRevision) return; - m_navigationSession = session; + m_navigationRevision = window.sessionRevision; + m_applyError = string.Empty; SettingsPage? first = SettingsTree.FindFirstMatch(pages, m_query); m_navigation.Reset(first?.path ?? string.Empty); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsPageView.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsPageView.cs index 7da2b004d..bda719e53 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsPageView.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsPageView.cs @@ -1,42 +1,31 @@ -using EditorImGui = Inno.Editor.ImGui.ImGui; -using System.Runtime.CompilerServices; -using NumericsVector2 = System.Numerics.Vector2; -using NativeVector2 = Inno.Native.ImGui.ImVec2C; -using NumericsVector4 = System.Numerics.Vector4; -using NativeVector4 = Inno.Native.ImGui.ImVec4C; +using System.Numerics; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; - using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Settings; - -internal sealed unsafe class SettingsPageView(SettingsEditSession session) +internal sealed class SettingsPageView(SettingsEditSession session) { + private static SettingsStyle settingsStyle => new(EditorUI.current.widgets.style); + internal void Draw( SettingsPage page, Action navigate ) { DrawPageHeader(page); - NativeImGui.Spacing(); + Ui.Spacing(); PushDisabledText(); - EditorWidget.WrappedText(page.description); - NativeImGui.PopStyleColor(); - NativeImGui.Dummy(new System.Numerics.Vector2( - 0f, - 16f * EditorWidget.style.zoom)); - + EditorUI.current.widgets.WrappedText(page.description); + Ui.PopStyleColor(); + Ui.Dummy(new System.Numerics.Vector2(0f, 16f * EditorUI.current.widgets.style.zoom)); if (page.children.Count != 0) { DrawOverview(page.children, navigate); - NativeImGui.Dummy(new System.Numerics.Vector2( - 0f, - 8f * EditorWidget.style.zoom)); + Ui.Dummy(new System.Numerics.Vector2(0f, 8f * EditorUI.current.widgets.style.zoom)); } if (!page.hasSettings) @@ -47,40 +36,30 @@ Action navigate } int fieldIndex = 0; - foreach (IGrouping group in page.settings.GroupBy( - static setting => setting.section ?? string.Empty, - StringComparer.OrdinalIgnoreCase)) + foreach (IGrouping group in page.settings.GroupBy(static setting => setting.section ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { if (!string.IsNullOrEmpty(group.Key)) - NativeImGui.SeparatorText(group.Key); + Ui.SeparatorText(group.Key); DrawSection(group, ref fieldIndex); - NativeImGui.Spacing(); + Ui.Spacing(); } } private void DrawPageHeader(SettingsPage page) { - ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoSavedSettings | - ImGuiTableFlags.NoPadOuterX; - if (!NativeImGui.BeginTable("##settings_page_header", 2, (int)flags, default(System.Numerics.Vector2), 0f)) + ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.NoPadOuterX; + if (!Ui.BeginTable("##settings_page_header", 2, flags, default(System.Numerics.Vector2), 0f)) return; try { - NativeImGui.TableSetupColumn( - "##settings_page_title", - (int)ImGuiTableColumnFlags.WidthStretch, - 1f); - NativeImGui.TableSetupColumn( - "##settings_page_reset", - (int)ImGuiTableColumnFlags.WidthFixed, - GetResetColumnWidth()); - NativeImGui.TableNextRow(); - _ = NativeImGui.TableSetColumnIndex(0); - NativeImGui.AlignTextToFramePadding(); - NativeImGui.TextUnformatted(page.label); - _ = NativeImGui.TableSetColumnIndex(1); - NativeImGui.PushID(page.path); + Ui.TableSetupColumn("##settings_page_title", ImGuiTableColumnFlags.WidthStretch, 1f); + Ui.TableSetupColumn("##settings_page_reset", ImGuiTableColumnFlags.WidthFixed, GetResetColumnWidth()); + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); + Ui.AlignTextToFramePadding(); + Ui.Text(page.label); + _ = Ui.TableSetColumnIndex(1); + Ui.PushId(page.path); try { bool canReset = session.CanReset(page); @@ -89,12 +68,12 @@ private void DrawPageHeader(SettingsPage page) } finally { - NativeImGui.PopID(); + Ui.PopId(); } } finally { - NativeImGui.EndTable(); + Ui.EndTable(); } } @@ -105,21 +84,19 @@ Action navigate if (children.Count == 0) { PushDisabledText(); - NativeImGui.TextUnformatted("No settings are currently registered on this page."); - NativeImGui.PopStyleColor(); + Ui.Text("No settings are currently registered on this page."); + Ui.PopStyleColor(); return; } for (int i = 0; i < children.Count; i++) { SettingsPage child = children[i]; - bool clicked = EditorWidget.HoverText( - $"settings_overview_{child.path}", - child.label); + bool clicked = EditorUI.current.widgets.HoverText($"settings_overview_{child.path}", child.label); if (clicked) navigate(child); - EditorWidget.DrawItemTooltip(child.description); - NativeImGui.Spacing(); + EditorUI.current.widgets.DrawItemTooltip(child.description); + Ui.Spacing(); } } @@ -127,37 +104,25 @@ private void DrawSection( IEnumerable settings, ref int fieldIndex ) { - ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | - ImGuiTableFlags.NoSavedSettings | - ImGuiTableFlags.NoPadOuterX; - float tableWidth = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); - NativeImGui.PushStyleVar( - (int)ImGuiStyleVar.CellPadding, - EditorWidget.style.settingsFieldPadding); + ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.NoPadOuterX; + float tableWidth = MathF.Max(1f, Ui.GetContentRegionAvailable().X); + Ui.PushStyleVar(ImGuiStyleVar.CellPadding, settingsStyle.fieldPadding); bool tableStarted = false; try { - tableStarted = NativeImGui.BeginTable( - "##settings_section", - 3, - (int)flags, - new System.Numerics.Vector2(tableWidth, 0f)); + tableStarted = Ui.BeginTable("##settings_section", 3, flags, new System.Numerics.Vector2(tableWidth, 0f)); if (!tableStarted) return; - - EditorWidget.SetupPropertyColumns(); - NativeImGui.TableSetupColumn( - "##settings_reset", - (int)ImGuiTableColumnFlags.WidthFixed, - GetResetColumnWidth()); + EditorUI.current.widgets.SetupPropertyColumns(); + Ui.TableSetupColumn("##settings_reset", ImGuiTableColumnFlags.WidthFixed, GetResetColumnWidth()); foreach (SettingsField setting in settings) DrawFieldRow(setting, fieldIndex++); } finally { if (tableStarted) - NativeImGui.EndTable(); - NativeImGui.PopStyleVar(); + Ui.EndTable(); + Ui.PopStyleVar(); } } @@ -165,33 +130,26 @@ private void DrawFieldRow( SettingsField setting, int fieldIndex ) { - NativeImGui.TableNextRow(); - uint background = NativeImGui.ColorConvertFloat4ToU32( - fieldIndex % 2 == 0 - ? EditorPalette.settingsField - : EditorPalette.settingsFieldAlternate); - NativeImGui.TableSetBgColor((int)ImGuiTableBgTarget.RowBg0, background); - NativeImGui.TableSetBgColor((int)ImGuiTableBgTarget.RowBg1, background); - - NativeImGui.PushID(setting.path); + Ui.TableNextRow(); + uint background = Ui.ToPackedColor(fieldIndex % 2 == 0 ? SettingsStyle.field : SettingsStyle.alternateField); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, background); + Ui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, background); + Ui.PushId(setting.path); try { - _ = NativeImGui.TableSetColumnIndex(0); - NativeImGui.SetCursorPosX( - NativeImGui.GetCursorPosX() + NativeImGui.Raw.GetStyle()->WindowPadding.X); - EditorWidget.PropertyLabel(setting.label, setting.description); - - _ = NativeImGui.TableSetColumnIndex(1); + _ = Ui.TableSetColumnIndex(0); + Ui.SetCursorPosX(Ui.GetCursorPosX() + Ui.GetLayout().windowPadding.X); + EditorUI.current.widgets.PropertyLabel(setting.label, setting.description); + _ = Ui.TableSetColumnIndex(1); bool groupStarted = false; try { - NativeImGui.BeginGroup(); + Ui.BeginGroup(); groupStarted = true; session.UpdateDirty(setting, session.Draw(setting)); - NativeImGui.EndGroup(); + Ui.EndGroup(); groupStarted = false; - - _ = NativeImGui.TableSetColumnIndex(2); + _ = Ui.TableSetColumnIndex(2); bool canReset = session.CanReset(setting); if (DrawResetButton(canReset)) session.Reset(setting); @@ -199,92 +157,69 @@ int fieldIndex finally { if (groupStarted) - NativeImGui.EndGroup(); + Ui.EndGroup(); } } finally { - NativeImGui.PopID(); + Ui.PopId(); } + DrawFieldBackgroundGutters(background); } private static void DrawFieldBackgroundGutters(uint background) { - ImGuiTable* table = NativeImGui.Raw.GetCurrentTable(); - ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); - float tableMinimumX = table->OuterRect.Min.X; - float tableMaximumX = table->OuterRect.Max.X; - float rowMinimumY = table->RowPosY1; - float rowMaximumY = table->RowPosY2; - float windowMinimumX = window->InnerRect.Min.X; - float windowMaximumX = window->InnerRect.Max.X; + var table = Ui.GetTableBounds(); + var window = Ui.GetWindowBounds(); + float tableMinimumX = table.minimumX; + float tableMaximumX = table.maximumX; + float rowMinimumY = table.rowMinimum; + float rowMaximumY = table.rowMaximum; + float windowMinimumX = window.minimumX; + float windowMaximumX = window.maximumX; if (rowMaximumY <= rowMinimumY) return; - - ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - NativeVector2 clipMinimum = Unsafe.BitCast(new System.Numerics.Vector2(windowMinimumX, rowMinimumY)); - NativeVector2 clipMaximum = Unsafe.BitCast(new System.Numerics.Vector2(windowMaximumX, rowMaximumY)); - NativeImGui.Raw.PushClipRect( - drawList, - &clipMinimum, - &clipMaximum, - 0 - ); + EditorDrawList drawList = Ui.GetDrawList(); + drawList.PushClip(new Vector2(windowMinimumX, rowMinimumY), new Vector2(windowMaximumX, rowMaximumY), false); try { if (tableMinimumX > windowMinimumX) { - EditorImGui.DrawFilledRectangle( - new System.Numerics.Vector2(windowMinimumX, rowMinimumY), - new System.Numerics.Vector2(tableMinimumX, rowMaximumY), - background, - 0 - ); + Ui.DrawFilledRectangle(new System.Numerics.Vector2(windowMinimumX, rowMinimumY), new System.Numerics.Vector2(tableMinimumX, rowMaximumY), background, 0); } + if (tableMaximumX < windowMaximumX) { - EditorImGui.DrawFilledRectangle( - new System.Numerics.Vector2(tableMaximumX, rowMinimumY), - new System.Numerics.Vector2(windowMaximumX, rowMaximumY), - background, - 0 - ); + Ui.DrawFilledRectangle(new System.Numerics.Vector2(tableMaximumX, rowMinimumY), new System.Numerics.Vector2(windowMaximumX, rowMaximumY), background, 0); } } finally { - NativeImGui.Raw.PopClipRect(drawList); + drawList.PopClip(); } } private static float GetResetColumnWidth() { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - return style->ItemSpacing.X + - NativeImGui.CalcTextSize("Reset").X + - style->FramePadding.X * 2f + - style->WindowPadding.X; + EditorUiLayout style = Ui.GetLayout(); + return style.itemSpacing.X + Ui.MeasureText("Reset").X + style.framePadding.X * 2f + style.windowPadding.X; } private static bool DrawResetButton(bool enabled) { - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); - NativeImGui.SetCursorPosX(NativeImGui.GetCursorPosX() + style->ItemSpacing.X); - NativeImGui.BeginDisabled(!enabled); + EditorUiLayout style = Ui.GetLayout(); + Ui.SetCursorPosX(Ui.GetCursorPosX() + style.itemSpacing.X); + Ui.BeginDisabled(!enabled); try { - return NativeImGui.SmallButton("Reset"); + return Ui.SmallButton("Reset"); } finally { - NativeImGui.EndDisabled(); + Ui.EndDisabled(); } } - private static void PushDisabledText() - => NativeImGui.PushStyleColor( - (int)ImGuiCol.Text, - Unsafe.BitCast(NativeImGui.Raw.GetStyle()->Colors[(int)ImGuiCol.TextDisabled])); - + private static void PushDisabledText() => Ui.PushStyleColor(ImGuiCol.Text, Ui.GetStyleColorValue(ImGuiCol.TextDisabled)); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsTree.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsTree.cs index 2e93de8dc..53a35da01 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsTree.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Presentation/SettingsTree.cs @@ -1,17 +1,14 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using Inno.Native.ImGui; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Settings; - internal sealed class SettingsTree { private string? m_revealPath; - internal void Reveal(string path) { ArgumentException.ThrowIfNullOrWhiteSpace(path); @@ -54,6 +51,7 @@ string path if (nested is not null) return nested; } + return null; } @@ -74,6 +72,7 @@ string query if (nested is not null) return nested; } + return null; } @@ -87,19 +86,13 @@ Action select bool hasVisibleChildren = false; for (int i = 0; i < page.children.Count; i++) hasVisibleChildren |= Matches(page.children[i], query); - if ((!string.IsNullOrWhiteSpace(query) && hasVisibleChildren) || - (hasVisibleChildren && IsRevealed(page.path, revealPath))) - EditorWidget.SetNextTreeNodeOpen(true); - - TreeNodeResult result = EditorWidget.TreeNode( + if ((!string.IsNullOrWhiteSpace(query) && hasVisibleChildren) || (hasVisibleChildren && IsRevealed(page.path, revealPath))) + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult result = EditorUI.current.widgets.TreeNode( $"settings_tree_{page.path}", - _ => NativeImGui.TextUnformatted(page.label), - new TreeNodeOptions - { - selected = string.Equals(page.path, selectedPath, StringComparison.Ordinal), - isLeaf = !hasVisibleChildren, - hideGuideLines = false - }); + _ => Ui.Text(page.label), + new TreeNodeOptions { selected = string.Equals(page.path, selectedPath, StringComparison.Ordinal), isLeaf = !hasVisibleChildren, hideGuideLines = false } + ); if (result.isClicked) select(page); if (!result.isOpen) @@ -109,17 +102,14 @@ Action select if (Matches(page.children[i], query)) DrawPage(page.children[i], query, selectedPath, revealPath, select); } - NativeImGui.TreePop(); + + Ui.TreePop(); } private static bool IsRevealed( string pagePath, string? revealPath - ) - => revealPath is not null && - (string.Equals(pagePath, revealPath, StringComparison.Ordinal) || - revealPath.StartsWith(pagePath + "/", StringComparison.Ordinal)); - + ) => revealPath is not null && (string.Equals(pagePath, revealPath, StringComparison.Ordinal) || revealPath.StartsWith(pagePath + "/", StringComparison.Ordinal)); private static bool Matches( SettingsPage page, string query @@ -133,6 +123,7 @@ string query if (Matches(page.children[i], query)) return true; } + return false; } @@ -140,22 +131,42 @@ private static bool MatchesSelf( SettingsPage page, string query ) { - if (page.label.Contains(query, StringComparison.OrdinalIgnoreCase) || - page.path.Contains(query, StringComparison.OrdinalIgnoreCase) || - page.description.Contains(query, StringComparison.OrdinalIgnoreCase)) + if (page.label.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) + || page.path.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) + || page.description.Contains( + query, + StringComparison.OrdinalIgnoreCase + )) { return true; } + for (int i = 0; i < page.settings.Count; i++) { SettingsField setting = page.settings[i]; - if (setting.label.Contains(query, StringComparison.OrdinalIgnoreCase) || - (setting.section?.Contains(query, StringComparison.OrdinalIgnoreCase) ?? false) || - setting.description.Contains(query, StringComparison.OrdinalIgnoreCase)) + if (setting.label.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) + || (setting.section?.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) ?? false) + || setting.description.Contains( + query, + StringComparison.OrdinalIgnoreCase + )) { return true; } } + return false; } @@ -169,17 +180,28 @@ string query for (int settingIndex = 0; settingIndex < page.settings.Count; settingIndex++) { SettingsField setting = page.settings[settingIndex]; - if (setting.label.Contains(query, StringComparison.OrdinalIgnoreCase) || - (setting.section?.Contains(query, StringComparison.OrdinalIgnoreCase) ?? false) || - setting.description.Contains(query, StringComparison.OrdinalIgnoreCase)) + if (setting.label.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) + || (setting.section?.Contains( + query, + StringComparison.OrdinalIgnoreCase + ) ?? false) + || setting.description.Contains( + query, + StringComparison.OrdinalIgnoreCase + )) { return page; } } + SettingsPage? nested = FindFirstFieldMatch(page.children, query); if (nested is not null) return nested; } + return null; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/OpenSettingsAction.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/OpenSettingsAction.cs index c7c4cbb6d..901ca08f3 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/OpenSettingsAction.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/OpenSettingsAction.cs @@ -6,6 +6,10 @@ namespace Inno.Editor.Panel.Settings; [EditorMenu(SettingsInteractionIds.C_MAIN_MENU_AREA, "Edit/Settings...", order: 1000)] internal sealed class OpenSettingsAction(SettingsWindowModule window) : EditorAction { + /// + protected override EditorActionState Query(EditorActionContext context) + => new(true, window.canOpen, unavailableReason: window.unavailableReason); + /// /// Applies the editor action to the supplied interaction context. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsEditSession.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsEditSession.cs index f89427b42..098e8f7aa 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsEditSession.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsEditSession.cs @@ -71,7 +71,7 @@ internal SettingsField(BuildSettingsField definition) internal BuildSettingsField? build => m_build; } -internal sealed class SettingsEditSession +internal sealed class SettingsEditSession : IDisposable { private readonly EditorSettings m_editorSettings; private readonly ProjectSettingsEditor m_projectSettings; @@ -92,6 +92,7 @@ internal sealed class SettingsEditSession private BuildSettings m_buildDefault; private BuildSettings m_buildOriginal; private BuildSettings m_buildValue; + private EditorHistoryTransaction? m_applyTransaction; internal SettingsEditSession( EditorSettings editorSettings, @@ -149,6 +150,13 @@ IEditorHistory history pages = BuildPages(editorSettings.definitions, definitions); } + /// + public void Dispose() + { + m_applyTransaction?.Dispose(); + m_applyTransaction = null; + } + internal long editorCatalogRevision { get; } internal long projectCatalogRevision { get; } internal IReadOnlyList definitions { get; } @@ -156,6 +164,8 @@ IEditorHistory history internal bool isEditorDirty => m_editorModified.Count > 0 || m_editorResets.Count > 0; internal bool isProjectDirty => m_projectModified.Count > 0 || m_projectResets.Count > 0; internal bool isBuildDirty => !BuildSettingsField.ValuesEqual(m_buildValue, m_buildOriginal); + internal bool canApply => m_applyTransaction is null && !m_history.isFaulted; + internal bool isHistoryFaulted => m_history.isFaulted; internal bool isDirty => isEditorDirty || isProjectDirty || isBuildDirty; internal bool Draw(SettingsField field) @@ -278,13 +288,45 @@ bool differsFromDrawBaseline internal bool Apply() { + if (!canApply) + throw new InvalidOperationException("Settings cannot be applied while their previous transaction or History requires recovery."); + bool editorDirty = isEditorDirty; + bool projectDirty = isProjectDirty; + bool buildDirty = isBuildDirty; bool changed = false; - if (isEditorDirty) - changed |= ApplyEditor(); - if (isProjectDirty) - changed |= ApplyProject(); - if (isBuildDirty) - changed |= ApplyBuild(); + m_applyTransaction = m_history.BeginTransaction("Apply Settings"); + try + { + if (editorDirty) + changed |= ApplyEditor(); + if (projectDirty) + changed |= ApplyProject(); + if (buildDirty) + changed |= ApplyBuild(); + m_applyTransaction.Commit(); + m_applyTransaction = null; + } + catch (Exception failure) + { + try + { + Dispose(); + } + catch (Exception rollbackFailure) + { + throw new AggregateException( + "Settings Apply failed and its History transaction could not retire. Recovery is required before further editing.", + failure, + rollbackFailure); + } + throw; + } + if (editorDirty) + AcceptEditorValues(); + if (projectDirty) + AcceptProjectValues(); + if (buildDirty) + AcceptBuildValues(); return changed; } @@ -294,7 +336,11 @@ private bool ApplyEditor() path => path, path => m_editorValues[path], StringComparer.Ordinal); - bool changed = m_editorSettings.Apply(values, m_editorResets); + return m_editorSettings.Apply(values, m_editorResets); + } + + private void AcceptEditorValues() + { m_editorModified.Clear(); m_editorInitiallyNonDefault.Clear(); m_editorResetIntent.Clear(); @@ -307,13 +353,16 @@ private bool ApplyEditor() if (!definition.IsDefault(value)) m_editorInitiallyNonDefault.Add(field.path); } - return changed; } private bool ApplyProject() { var values = m_projectModified.ToDictionary(id => id, id => m_projectValues[id]); - bool changed = m_projectSettings.Apply(values, m_projectResets); + return m_projectSettings.Apply(values, m_projectResets); + } + + private void AcceptProjectValues() + { m_projectModified.Clear(); m_projectInitiallyNonDefault.Clear(); m_projectResetIntent.Clear(); @@ -330,7 +379,6 @@ private bool ApplyProject() if (!definition.ValuesEqual(value, m_projectSettings.GetComposedDefault(definition))) m_projectInitiallyNonDefault.Add(definition.settingId); } - return changed; } private bool ApplyBuild() @@ -349,10 +397,14 @@ private bool ApplyBuild() throw; } + return true; + } + + private void AcceptBuildValues() + { m_buildDefault = m_buildSettings.defaultSettings; - m_buildOriginal = m_buildSettings.Load(); + m_buildOriginal = m_buildValue.Copy(); m_buildValue = m_buildOriginal.Copy(); - return true; } private static void UpdateDirty( diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsWindowModule.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsWindowModule.cs index 7081dbc4b..0ed225df8 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsWindowModule.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Runtime/SettingsWindowModule.cs @@ -1,3 +1,4 @@ +using System; using Inno.Build; using Inno.Editor.Core; using Inno.Editor.Interactions; @@ -11,48 +12,84 @@ internal sealed class SettingsWindowModule( ProjectSettingsEditor projectSettings, BuildPipeline buildPipeline, BuildSettingsStore buildSettings, - EditorInteractions interactions -) : EditorModule + EditorInteractions interactions, + EditorReloadCoordinator reloads +) : EditorModule, IEditorReloadGuard { + private const string C_PRESENTATION_FAILURE = "Settings presentation failed. Reload extensions or restart the Editor."; + private SettingsEditSession? m_session; + private IDisposable? m_reloadGuard; + private long m_failedCatalogRevision = long.MinValue; internal bool isVisible { get; private set; } internal SettingsEditSession? session => m_session; + internal long sessionRevision { get; private set; } + internal bool canOpen => m_failedCatalogRevision != editorSettings.catalogRevision; + internal string? unavailableReason => canOpen ? null : C_PRESENTATION_FAILURE; internal void Open() { + if (!canOpen) + throw new InvalidOperationException(C_PRESENTATION_FAILURE); if (!isVisible) + { m_session = new SettingsEditSession( editorSettings, projectSettings, buildPipeline, buildSettings, interactions.history); + sessionRevision++; + } isVisible = true; } internal void Close() { + m_session?.Dispose(); isVisible = false; m_session = null; + sessionRevision++; } internal void Refresh() { + m_session?.Dispose(); m_session = new SettingsEditSession( editorSettings, projectSettings, buildPipeline, buildSettings, interactions.history); + sessionRevision++; + } + + internal void FailPresentation() + { + m_failedCatalogRevision = editorSettings.catalogRevision; + Close(); } /// - /// Stops this feature before its owning runtime releases the active generation. + /// Registers the admission boundary for generation-bound Settings editing. /// /// /// The context that supplies state and services for this operation. /// - protected override void OnStop(EditorContext context) => Close(); + protected override void OnStart(EditorContext context) => m_reloadGuard = reloads.RegisterGuard(this); + + /// + protected override void OnStop(EditorContext context) + { + Close(); + m_reloadGuard?.Dispose(); + m_reloadGuard = null; + } + + bool IEditorReloadGuard.canRequestReload => !isVisible; + bool IEditorReloadGuard.canActivateReload => !isVisible; + string? IEditorReloadGuard.blockingReason + => isVisible ? "Close Settings before reloading scripts to retire its current-generation editing state." : null; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Settings/Styling/SettingsStyle.cs b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Styling/SettingsStyle.cs new file mode 100644 index 000000000..e08247097 --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.Settings/Styling/SettingsStyle.cs @@ -0,0 +1,15 @@ +using System.Numerics; +using Inno.Editor.ImGui; + +namespace Inno.Editor.Panel.Settings; + +internal readonly struct SettingsStyle +{ + private readonly float m_zoom; + + internal SettingsStyle(EditorStyleMetrics metrics) => m_zoom = metrics.zoom; + + internal static Vector4 field { get; } = new(0.36f, 0.36f, 0.38f, EditorPalette.opacityTrace); + internal static Vector4 alternateField { get; } = new(0.52f, 0.52f, 0.56f, EditorPalette.opacityTrace); + internal Vector2 fieldPadding => new Vector2(8f, 6f) * m_zoom; +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Controls.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Controls.cs index bec0932d0..e57e0c728 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Controls.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Controls.cs @@ -1,29 +1,27 @@ +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Linq; using Inno.Assets.Pipeline; using Inno.Core.Graphs; -using Inno.Native.ImGui; using Inno.Rendering; using Inno.Rendering.Assets; using Inno.Rendering.Shaders; using EditorImGui = Inno.Editor.ImGui.ImGui; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void Controls(GraphNodeRecord node) { - if (owner.drawers?.TryDraw(node.definitionId, new(node, owner.serialization, owner.context, - ( - key, - value, - continuous - ) => SetEncoded(node, key, value, continuous), owner.previews, m_inspection!, draft.readOnly)) == true) - return; + if (owner.drawers?.TryDraw(node.definitionId, new(node, owner.serialization, owner.context, ( + key, + value, + continuous + ) => SetEncoded(node, key, value, continuous), owner.previews, m_inspection!, draft.readOnly)) == true) + return; switch (node.definitionId) { case "inno.shader.constant": @@ -40,16 +38,22 @@ private void Controls(GraphNodeRecord node) Choice(node, "type", "float", ["float", "float2", "float3", "float4", "int", "uint", "bool"]); break; case "inno.shader.reroute": - InspectorRow("reroute.type", "Type", () => - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, Read(node, "valueType", new ShaderGraphType { id = "float" }).CreateType().id)); - Widget.Hint("Forwards the complete port type without changing the value."); + InspectorRow( + "reroute.type", + "Type", + () => EditorUI.current.widgets.ColoredText( + Inno.Editor.ImGui.EditorPalette.textDisabled, + Read(node, "valueType", new ShaderGraphType { id = "float" }).CreateType().id + ) + ); + EditorUI.current.widgets.Hint("Forwards the complete port type without changing the value."); break; case "inno.shader.extract": Choice(node, "type", "float4", ["float2", "float3", "float4", "float3x3", "float4x4"]); int index = Read(node, "index", 0); InspectorRow("extract.component", "Component", () => { - if (ImGuiApi.InputInt("##component", ref index, 1, 100, 0) && index >= 0) + if (EditorImGui.InputInt("##component", ref index, 1, 100, 0) && index >= 0) Set(node, "index", index); }); break; @@ -58,7 +62,7 @@ private void Controls(GraphNodeRecord node) bool level = Read(node, "explicitLevel", false); InspectorRow("sample.explicit-level", "Explicit LOD", () => { - if (ImGuiApi.Checkbox("##explicit-level", ref level)) + if (EditorImGui.Checkbox("##explicit-level", ref level)) Set(node, "explicitLevel", level); }); break; @@ -83,22 +87,25 @@ private void Controls(GraphNodeRecord node) case "inno.shader.storage-load": case "inno.shader.storage-store": case "inno.shader.storage-atomic-add": - ShaderGraphType resource = Read(node, "resource", new ShaderGraphType { isStorage = true, - storageElement = new() { id = "float4" }, access = RenderStorageAccess.ReadWrite }); + ShaderGraphType resource = Read( + node, + "resource", + new ShaderGraphType { isStorage = true, storageElement = new() { id = "float4" }, access = RenderStorageAccess.ReadWrite } + ); InspectorRow("storage.contract", "Contract", () => { - if (ImGuiApi.Button("Storage Contract…")) - ImGuiApi.OpenPopup("##storage"); + if (EditorImGui.Button("Storage Contract…")) + EditorImGui.OpenPopup("##storage"); }); if (StoragePopup(ref resource)) Set(node, "resource", resource); - Widget.WrappedText("Connect then → after to order memory effects."); + EditorUI.current.widgets.WrappedText("Connect then → after to order memory effects."); break; case "inno.shader.discard": - Widget.WrappedText("Discard fragments where condition is true."); + EditorUI.current.widgets.WrappedText("Discard fragments where condition is true."); break; default: - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Extension node"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Extension node"); break; } } @@ -110,13 +117,14 @@ private void GraphCall(GraphNodeRecord node) ShaderGraphNodeInterface nodeInterface = Read(node, ShaderGraphNodes.interfaceKey, new ShaderGraphNodeInterface()); InspectorRow("graph-node.asset", "Node Graph", () => { - if (!Widget.BeginBoundedCombo("##graph-node", selected == Guid.Empty ? "Choose Shader node…" : System.IO.Path.GetFileName(path))) + if (!EditorUI.current.widgets.BeginBoundedCombo("##graph-node", selected == Guid.Empty ? "Choose Shader node…" : System.IO.Path.GetFileName(path))) return; try { - foreach (AssetFileEntry entry in owner.assets.GetFileSystemEntries(includeDirectories: false) - .Where(static entry => entry.extension == ".ishader") - .OrderBy(static entry => entry.assetPath.ToString(), StringComparer.Ordinal)) + foreach (AssetFileEntry entry in owner.assets.GetFileSystemEntries(includeDirectories: false).Where(static entry => entry.extension == ".ishader").OrderBy( + static entry => entry.assetPath.ToString(), + StringComparer.Ordinal + )) { Guid id = owner.AssetId(entry); if (id == draft.id) @@ -128,10 +136,11 @@ private void GraphCall(GraphNodeRecord node) } catch (Exception failure) when ((failure is InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) - { - continue; - } - if (!ImGuiApi.Selectable(candidate.displayName + " · " + entry.assetPath.localPath, id == selected)) + { + continue; + } + + if (!EditorImGui.Selectable(candidate.displayName + " · " + entry.assetPath.localPath, id == selected)) continue; using var transaction = owner.interactions.history.BeginTransaction("Assign Graph Node"); Set(node, "sourceId", id); @@ -145,14 +154,14 @@ private void GraphCall(GraphNodeRecord node) } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); - Widget.DrawItemTooltip(path); - InspectorRow("graph-node.kind", "Kind", () => Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.kind.ToString())); - InspectorRow("graph-node.effect", "Effect", () => Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.effect.ToString())); + EditorUI.current.widgets.DrawItemTooltip(path); + InspectorRow("graph-node.kind", "Kind", () => EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.kind.ToString())); + InspectorRow("graph-node.effect", "Effect", () => EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.effect.ToString())); if (nodeInterface.kind == ShaderGraphNodeKind.DomainOutput) - InspectorRow("graph-node.role", "Target Role", () => Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.role)); + InspectorRow("graph-node.role", "Target Role", () => EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, nodeInterface.role)); RepairPorts(node); } @@ -180,8 +189,7 @@ private void GraphOutputs(GraphNodeRecord node) private void GraphInterfaceMetadata() { - ShaderGraphNodeSettings settings = ShaderGraphNodes.ReadSettings( - Controller.document, owner.serialization, owner.context); + ShaderGraphNodeSettings settings = ShaderGraphNodes.ReadSettings(Controller.document, owner.serialization, owner.context); string displayName = settings.displayName; InspectorRow("node-interface.name", "Node Name", () => { @@ -207,7 +215,7 @@ private void GraphInterfaceMetadata() int createOrder = settings.createOrder; InspectorRow("node-interface.order", "Order", () => { - bool changed = ImGuiApi.InputInt("##order", ref createOrder, 1, 100, 0); + bool changed = EditorImGui.InputInt("##order", ref createOrder, 1, 100, 0); Gesture(); if (changed) { @@ -217,12 +225,12 @@ private void GraphInterfaceMetadata() }); InspectorRow("node-interface.kind", "Kind", () => { - if (!Widget.BeginBoundedCombo("##kind", settings.kind.ToString())) + if (!EditorUI.current.widgets.BeginBoundedCombo("##kind", settings.kind.ToString())) return; try { foreach (ShaderGraphNodeKind kind in Enum.GetValues()) - if (ImGuiApi.Selectable(kind.ToString(), settings.kind == kind)) + if (EditorImGui.Selectable(kind.ToString(), settings.kind == kind)) { settings.kind = kind; SetGraphNodeSettings(settings); @@ -230,19 +238,19 @@ private void GraphInterfaceMetadata() } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); if (settings.kind == ShaderGraphNodeKind.Function) { InspectorRow("node-interface.effect", "Effect", () => { - if (!Widget.BeginBoundedCombo("##effect", settings.effect.ToString())) + if (!EditorUI.current.widgets.BeginBoundedCombo("##effect", settings.effect.ToString())) return; try { foreach (ShaderGraphNodeEffect effect in Enum.GetValues()) - if (ImGuiApi.Selectable(effect.ToString(), settings.effect == effect)) + if (EditorImGui.Selectable(effect.ToString(), settings.effect == effect)) { settings.effect = effect; SetGraphNodeSettings(settings); @@ -250,10 +258,11 @@ private void GraphInterfaceMetadata() } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); } + if (settings.kind == ShaderGraphNodeKind.DomainOutput) { string role = settings.role; @@ -286,10 +295,10 @@ private void GraphPorts( bool inputs, Action apply ) { - ImGuiApi.SeparatorText(inputs ? "Inputs" : "Outputs"); + EditorImGui.SeparatorText(inputs ? "Inputs" : "Outputs"); for (int index = 0; index < ports.Length; index++) { - ImGuiApi.PushID(index); + EditorImGui.PushId(index); try { ShaderGraphNodePortDefinition port = ports[index]; @@ -301,27 +310,63 @@ Action apply if (changed) { ShaderGraphNodePortDefinition[] changedPorts = ports.ToArray(); - changedPorts[index] = new() { id = id, type = port.type, required = port.required }; + changedPorts[index] = new() + { + id = id, + type = port.type, + required = port.required + }; apply(changedPorts); } }); InspectorRow("node-port.type", "Type", () => { - if (!Widget.BeginBoundedCombo("##type", port.type.id)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##type", port.type.id)) return; try { - foreach (string type in new[] { "float", "float2", "float3", "float4", "int", "int2", "int3", "int4", "uint", "uint2", "uint3", "uint4", "bool", "float3x3", "float4x4", "sampled-texture2d", "sampled-texture2d-array", "sampled-texture3d", "sampled-texture-cube" }) - if (ImGuiApi.Selectable(type, port.type.id == type)) + foreach (string type in new[] + { + "float", + "float2", + "float3", + "float4", + "int", + "int2", + "int3", + "int4", + "uint", + "uint2", + "uint3", + "uint4", + "bool", + "float3x3", + "float4x4", + "sampled-texture2d", + "sampled-texture2d-array", + "sampled-texture3d", + "sampled-texture-cube" + } + + ) + if (EditorImGui.Selectable(type, port.type.id == type)) { ShaderGraphNodePortDefinition[] changedPorts = ports.ToArray(); - changedPorts[index] = new() { id = port.id, type = new() { id = type }, required = port.required }; + changedPorts[index] = new() + { + id = port.id, + type = new() + { + id = type + }, + required = port.required + }; apply(changedPorts); } } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); if (inputs) @@ -329,16 +374,22 @@ Action apply bool required = port.required; InspectorRow("node-port.required", "Required", () => { - if (!ImGuiApi.Checkbox("##required", ref required)) + if (!EditorImGui.Checkbox("##required", ref required)) return; ShaderGraphNodePortDefinition[] changedPorts = ports.ToArray(); - changedPorts[index] = new() { id = port.id, type = port.type, required = required }; + changedPorts[index] = new() + { + id = port.id, + type = port.type, + required = required + }; apply(changedPorts); }); } + InspectorRow("node-port.remove", "", () => { - if (ImGuiApi.SmallButton("Remove")) + if (EditorImGui.SmallButton("Remove")) apply(ports.Where(( _, item @@ -347,17 +398,19 @@ Action apply } finally { - ImGuiApi.PopID(); + EditorImGui.PopId(); } } + if (CenteredAddButton(inputs ? "Add Input" : "Add Output")) { string prefix = inputs ? "input" : "output"; string id = prefix; for (int suffix = 2; ports.Any(port => port.id == id); suffix++) id = prefix + suffix; - apply([.. ports, new() { id = id, type = new() { id = "float" }, required = false }]); + apply([..ports, new() { id = id, type = new() { id = "float" }, required = false }]); } + RepairPorts(node); } @@ -366,29 +419,46 @@ private void Scalar(GraphNodeRecord node) string type = Read(node, "type", "float"); InspectorRow("constant.type", "Type", () => { - if (!Widget.BeginBoundedCombo("##constant-type", type)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##constant-type", type)) return; try { - foreach (string candidate in new[] { "float", "int", "uint", "bool" }) - if (ImGuiApi.Selectable(candidate, type == candidate)) + foreach (string candidate in new[] + { + "float", + "int", + "uint", + "bool" + } + + ) + if (EditorImGui.Selectable(candidate, type == candidate)) { using var transaction = owner.interactions.history.BeginTransaction("Change Constant Type"); Set(node, "type", candidate); switch (candidate) { - case "float": Set(node, "value", 0f); break; - case "int": Set(node, "value", 0); break; - case "uint": Set(node, "value", 0u); break; - case "bool": Set(node, "value", false); break; + case "float": + Set(node, "value", 0f); + break; + case "int": + Set(node, "value", 0); + break; + case "uint": + Set(node, "value", 0u); + break; + case "bool": + Set(node, "value", false); + break; } + transaction.Commit(); type = candidate; } } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); switch (type) @@ -397,7 +467,7 @@ private void Scalar(GraphNodeRecord node) float number = Read(node, "value", 0f); InspectorRow("constant.value", "Value", () => { - bool changed = Widget.CompactDragFloat("##value", ref number, 0.01f); + bool changed = EditorUI.current.widgets.CompactDragFloat("##value", ref number, 0.01f); Gesture(); if (changed) Set(node, "value", number, true); @@ -407,7 +477,7 @@ private void Scalar(GraphNodeRecord node) bool boolean = Read(node, "value", false); InspectorRow("constant.value", "Value", () => { - if (ImGuiApi.Checkbox("##value", ref boolean)) + if (EditorImGui.Checkbox("##value", ref boolean)) Set(node, "value", boolean); }); break; @@ -415,7 +485,7 @@ private void Scalar(GraphNodeRecord node) int integer = Read(node, "value", 0); InspectorRow("constant.value", "Value", () => { - bool integerChanged = ImGuiApi.InputInt("##value", ref integer, 1, 100, 0); + bool integerChanged = EditorImGui.InputInt("##value", ref integer, 1, 100, 0); Gesture(); if (integerChanged) Set(node, "value", integer, true); @@ -441,13 +511,12 @@ private void Source(GraphNodeRecord node) string selectedFunction = Read(node, "function", ""); InspectorRow("source.library", "Library", () => { - if (!Widget.BeginBoundedCombo("##library", selected == Guid.Empty ? "Choose source library…" : System.IO.Path.GetFileName(path))) + if (!EditorUI.current.widgets.BeginBoundedCombo("##library", selected == Guid.Empty ? "Choose source library…" : System.IO.Path.GetFileName(path))) return; try { - foreach (AssetFileEntry entry in owner.assets.GetFileSystemEntries(includeDirectories: false) - .Where(static entry => entry.assetPath.localPath.EndsWith(".ishadersource", StringComparison.OrdinalIgnoreCase))) - if (ImGuiApi.Selectable(entry.assetPath.ToString(), owner.AssetId(entry) == selected)) + foreach (AssetFileEntry entry in owner.assets.GetFileSystemEntries(includeDirectories: false).Where(static entry => entry.assetPath.localPath.EndsWith(".ishadersource", StringComparison.OrdinalIgnoreCase))) + if (EditorImGui.Selectable(entry.assetPath.ToString(), owner.AssetId(entry) == selected)) { using var transaction = owner.interactions.history.BeginTransaction("Assign Shader Function"); Set(node, "sourceId", owner.AssetId(entry)); @@ -459,34 +528,36 @@ private void Source(GraphNodeRecord node) } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); - Widget.DrawItemTooltip(path); + EditorUI.current.widgets.DrawItemTooltip(path); if (selected == Guid.Empty) - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Choose a library and one of its explicitly exported functions."); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Choose a library and one of its explicitly exported functions."); else { if (owner.assets.TryLoad(selected, out ShaderFunctionAsset? library) && library is not null && !library.isMissing) { InspectorRow("source.function", "Function", () => { - if (!Widget.BeginBoundedCombo("##function", selectedFunction.Length == 0 ? "Choose exported function…" : selectedFunction)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##function", selectedFunction.Length == 0 ? "Choose exported function…" : selectedFunction)) return; try { foreach (string function in library.exports) - if (ImGuiApi.Selectable(function, function == selectedFunction)) + if (EditorImGui.Selectable(function, function == selectedFunction)) Set(node, "function", function); } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); } + SourceSettings(selected); } + RepairPorts(node); } @@ -502,22 +573,37 @@ private void Input(GraphNodeRecord node) input.id = id; Set(node, "settings", input, true); } + InspectorRow("input.source", "Source", () => { - if (!Widget.BeginBoundedCombo("##source", input.kind.ToString())) + if (!EditorUI.current.widgets.BeginBoundedCombo("##source", input.kind.ToString())) return; try { foreach (ShaderIrInputKind kind in Enum.GetValues()) - if (ImGuiApi.Selectable(kind.ToString(), input.kind == kind)) + if (EditorImGui.Selectable(kind.ToString(), input.kind == kind)) { input.kind = kind; if (kind == ShaderIrInputKind.Storage) - input.type = new() { isStorage = true, storageElement = new() { id = "float4" }, access = RenderStorageAccess.ReadWrite }; + input.type = new() + { + isStorage = true, + storageElement = new() + { + id = "float4" + }, + access = RenderStorageAccess.ReadWrite + }; else if (kind == ShaderIrInputKind.SampledTexture) - input.type = new() { id = "sampled-texture2d" }; + input.type = new() + { + id = "sampled-texture2d" + }; else if (input.type.isStorage || input.type.id.StartsWith("sampled-texture", StringComparison.Ordinal)) - input.type = new() { id = "float4" }; + input.type = new() + { + id = "float4" + }; if (kind == ShaderIrInputKind.Builtin) { string stageId = Read(node, "stage", ""); @@ -525,15 +611,19 @@ private void Input(GraphNodeRecord node) if (output is not null && Read(output, "settings", new ShaderGraphStageSettings()).stage == ShaderStage.Compute) { input.semantic = "global-invocation-id"; - input.type = new() { id = "uint3" }; + input.type = new() + { + id = "uint3" + }; } } + Set(node, "settings", input); } } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); string type = input.type.id; @@ -541,8 +631,8 @@ private void Input(GraphNodeRecord node) { InspectorRow("input.storage", "Contract", () => { - if (ImGuiApi.Button("Storage Contract…")) - ImGuiApi.OpenPopup("##storage"); + if (EditorImGui.Button("Storage Contract…")) + EditorImGui.OpenPopup("##storage"); }); ShaderGraphType resource = input.type; if (StoragePopup(ref resource)) @@ -553,23 +643,49 @@ private void Input(GraphNodeRecord node) } else InspectorRow("input.type", "Type", () => - { - if (!Widget.BeginBoundedCombo("##type", type)) - return; - try { - foreach (string candidate in new[] { "float", "float2", "float3", "float4", "int", "int2", "int3", "int4", "uint", "uint2", "uint3", "uint4", "bool", "float3x3", "float4x4", "sampled-texture2d", "sampled-texture2d-array", "sampled-texture3d", "sampled-texture-cube" }) - if (ImGuiApi.Selectable(candidate, type == candidate)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##type", type)) + return; + try + { + foreach (string candidate in new[] { - input.type = new() { id = candidate }; - Set(node, "settings", input); + "float", + "float2", + "float3", + "float4", + "int", + "int2", + "int3", + "int4", + "uint", + "uint2", + "uint3", + "uint4", + "bool", + "float3x3", + "float4x4", + "sampled-texture2d", + "sampled-texture2d-array", + "sampled-texture3d", + "sampled-texture-cube" } - } - finally - { - Widget.EndBoundedCombo(); - } - }); + + ) + if (EditorImGui.Selectable(candidate, type == candidate)) + { + input.type = new() + { + id = candidate + }; + Set(node, "settings", input); + } + } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } + }); string semantic = input.semantic; bool semanticChanged = false; InspectorRow("input.semantic", "Semantic", () => semanticChanged = EditorImGui.InputText("##semantic", ref semantic, 256)); @@ -579,26 +695,29 @@ private void Input(GraphNodeRecord node) input.semantic = semantic; Set(node, "settings", input, true); } - Widget.DrawItemTooltip("Stage semantic, not a native expression. Builtins must be supported by the selected stage and target; compute invocation IDs use uint3."); + + EditorUI.current.widgets.DrawItemTooltip("Stage semantic, not a native expression. Builtins must be supported by the selected stage and target; compute invocation IDs use uint3."); int location = input.location; bool locationChanged = false; - InspectorRow("input.location", "Location", () => locationChanged = ImGuiApi.InputInt("##location", ref location, 1, 100, 0)); + InspectorRow("input.location", "Location", () => locationChanged = EditorImGui.InputInt("##location", ref location, 1, 100, 0)); Gesture(); if (locationChanged && location >= 0) { input.location = location; Set(node, "settings", input, true); } + if (input.kind is ShaderIrInputKind.Uniform or ShaderIrInputKind.SampledTexture or ShaderIrInputKind.Storage) { DrawParameter(input); } + if (input.kind == ShaderIrInputKind.SampledTexture) { bool expanded = draft.expandedPreviews.Contains(node.id); InspectorRow("input.preview", "Preview", () => { - if (ImGuiApi.Checkbox("##preview", ref expanded)) + if (EditorImGui.Checkbox("##preview", ref expanded)) { if (expanded) draft.expandedPreviews.Add(node.id); @@ -613,7 +732,7 @@ private void Input(GraphNodeRecord node) if (texture is not null && owner.previews.TryGetTexture(texture, out var preview)) owner.previews.Draw(preview, new(120 * Canvas.zoom, 120 * Canvas.zoom)); else - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, texture is null ? "No default texture" : "Preparing preview…"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, texture is null ? "No default texture" : "Preparing preview…"); } } } @@ -621,13 +740,25 @@ private void Input(GraphNodeRecord node) private void Output(GraphNodeRecord node) { ShaderGraphStageSettings stage = Read(node, "settings", new ShaderGraphStageSettings()); - InspectorRow("output.passes", "Passes", () => Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, string.Join(", ", - ShaderGraphPrograms.Read(Controller.document, owner.serialization, owner.context) - .Where(program => program.stages.Contains(node.id.value, StringComparer.Ordinal)).Select(static program => program.pass)))); + InspectorRow( + "output.passes", + "Passes", + () => EditorUI.current.widgets.ColoredText( + Inno.Editor.ImGui.EditorPalette.textDisabled, + string.Join( + ", ", + ShaderGraphPrograms.Read( + Controller.document, + owner.serialization, + owner.context + ).Where(program => program.stages.Contains(node.id.value, StringComparer.Ordinal)).Select(static program => program.pass) + ) + ) + ); InspectorRow("output.settings", "Settings", () => { - if (ImGuiApi.Button("Stage & Pass Settings…")) - ImGuiApi.OpenPopup("##stage-settings"); + if (EditorImGui.Button("Stage & Pass Settings…")) + EditorImGui.OpenPopup("##stage-settings"); }); StageSettingsPopup(node, stage); if (stage.stage == ShaderStage.Compute) @@ -635,7 +766,7 @@ private void Output(GraphNodeRecord node) int x = stage.threadsX, y = stage.threadsY, z = stage.threadsZ; InspectorRow("output.threads-x", "Threads X", () => { - if (ImGuiApi.InputInt("##threads-x", ref x, 1, 100, 0) && x > 0) + if (EditorImGui.InputInt("##threads-x", ref x, 1, 100, 0) && x > 0) { stage.threadsX = x; Set(node, "settings", stage); @@ -643,7 +774,7 @@ private void Output(GraphNodeRecord node) }); InspectorRow("output.threads-y", "Threads Y", () => { - if (ImGuiApi.InputInt("##threads-y", ref y, 1, 100, 0) && y > 0) + if (EditorImGui.InputInt("##threads-y", ref y, 1, 100, 0) && y > 0) { stage.threadsY = y; Set(node, "settings", stage); @@ -651,7 +782,7 @@ private void Output(GraphNodeRecord node) }); InspectorRow("output.threads-z", "Threads Z", () => { - if (ImGuiApi.InputInt("##threads-z", ref z, 1, 100, 0) && z > 0) + if (EditorImGui.InputInt("##threads-z", ref z, 1, 100, 0) && z > 0) { stage.threadsZ = z; Set(node, "settings", stage); @@ -667,26 +798,26 @@ private void Choice( string[] values ) { string current = Read(node, key, defaultValue); - InspectorRow("choice." + key, Widget.NicifyName(key), () => + InspectorRow("choice." + key, EditorDisplayNames.Nicify(key), () => { - if (!Widget.BeginBoundedCombo("##" + key, current)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##" + key, current)) return; try { foreach (string candidate in values) - if (ImGuiApi.Selectable(candidate, current == candidate)) + if (EditorImGui.Selectable(candidate, current == candidate)) Set(node, key, candidate); } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); } private void Gesture() { - if (ImGuiApi.IsItemActivated()) + if (EditorImGui.IsItemActivated()) draft.valueGesture = Guid.NewGuid().ToString("N"); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Drawing.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Drawing.cs deleted file mode 100644 index c5d7a2d55..000000000 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Drawing.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Inno.Native.ImGui; -using ImGuiApi = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Panel.ShaderEditor; - -// Carrier addresses remain stack-owned for the duration of each synchronous native call. -internal sealed unsafe partial class ShaderEditorCanvas -{ - private static void DrawNativeFilledRectangle( - ImDrawList* drawList, - ImVec2C minimum, - ImVec2C maximum, - uint color, - float rounding, - int flags - ) { - ImGuiApi.Raw.AddRectFilled( - drawList, - &minimum, - &maximum, - color, - rounding, - flags); - } - - private static void DrawNativeText( - ImDrawList* drawList, - ImVec2C position, - uint color, - sbyte* textBegin, - sbyte* textEnd - ) { - ImGuiApi.Raw.AddText( - drawList, - &position, - color, - textBegin, - textEnd); - } - - private static void DrawNativeText( - ImDrawList* drawList, - ImFont* font, - float fontSize, - ImVec2C position, - uint color, - sbyte* textBegin, - sbyte* textEnd, - float wrapWidth, - ImVec4C* clipRectangle - ) { - ImGuiApi.Raw.AddText( - drawList, - font, - fontSize, - &position, - color, - textBegin, - textEnd, - wrapWidth, - clipRectangle); - } - - private static void DrawNativeFilledCircle( - ImDrawList* drawList, - ImVec2C center, - float radius, - uint color, - int segments - ) { - ImGuiApi.Raw.AddCircleFilled( - drawList, - ¢er, - radius, - color, - segments); - } - - private static void PushNativeClipRectangle( - ImDrawList* drawList, - ImVec2C minimum, - ImVec2C maximum, - byte intersect - ) { - ImGuiApi.Raw.PushClipRect( - drawList, - &minimum, - &maximum, - intersect); - } - - private static void DrawNativeRectangle( - ImDrawList* drawList, - ImVec2C minimum, - ImVec2C maximum, - uint color, - float rounding, - int flags, - float thickness - ) { - ImGuiApi.Raw.AddRect( - drawList, - &minimum, - &maximum, - color, - rounding, - flags, - thickness); - } - - private static void DrawNativeCurve( - ImDrawList* drawList, - ImVec2C p1, - ImVec2C p2, - ImVec2C p3, - ImVec2C p4, - uint color, - float thickness, - int segments - ) { - ImGuiApi.Raw.AddBezierCubic( - drawList, - &p1, - &p2, - &p3, - &p4, - color, - thickness, - segments); - } - - private static void DrawNativeLine( - ImDrawList* drawList, - ImVec2C p1, - ImVec2C p2, - uint color, - float thickness - ) { - ImGuiApi.Raw.AddLine( - drawList, - &p1, - &p2, - color, - thickness); - } -} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.InputDefaults.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.InputDefaults.cs index 7c655e121..efa53e84a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.InputDefaults.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.InputDefaults.cs @@ -1,12 +1,11 @@ +using Inno.Editor.Core; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Linq; - using Inno.Core.Graphs; using Inno.Rendering.Shaders; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void DrawInputDefault( @@ -15,44 +14,33 @@ ShaderNodePort port ) { var endpoint = new GraphEndpoint(node.id, new(port.id)); GraphEdgeRecord? edge = Controller.document.edges.FirstOrDefault(value => value.input == endpoint); - ImGuiApi.PushID(port.id); + ImGuiApi.PushId(port.id); try { if (edge is not null) { - InspectorPortStatusRow( - "port." + port.id, - port, - null, - "From " + edge.output.nodeId.value + "." + edge.output.portId.value); + InspectorPortStatusRow("port." + port.id, port, null, "From " + edge.output.nodeId.value + "." + edge.output.portId.value); return; } + if (node.definitionId == ShaderGraphDocument.outputDefinitionId || draft.missingPorts.Contains(endpoint)) { InspectorPortStatusRow("port." + port.id, port, null, "Connect an available output"); return; } - if (m_inspectionNodes is { Length: > 1 } selected && selected.Any(value => - !draft.ports[value.id].Any(candidate => - candidate.id == port.id && candidate.type.IsEquivalentTo(port.type)) || - Controller.document.edges.Any(connection => - connection.input == new GraphEndpoint(value.id, new(port.id))))) + + if (m_inspectionNodes is { Length: > 1 } selected + && selected.Any(value => !draft.ports[value.id].Any(candidate => candidate.id == port.id && candidate.type.IsEquivalentTo(port.type)) || Controller.document.edges.Any(connection => connection.input == new GraphEndpoint(value.id, new(port.id))))) { InspectorPortStatusRow("port." + port.id, port, null, "Mixed connections or types"); return; } - InspectorPortStatusRow( - "port." + port.id, - port, - null, - port.required - ? "Required · Connect a Constant or compatible output" - : "Optional · Zero when unconnected"); + InspectorPortStatusRow("port." + port.id, port, null, port.required ? "Required · Connect a Constant or compatible output" : "Optional · Zero when unconnected"); } finally { - ImGuiApi.PopID(); + ImGuiApi.PopId(); } } @@ -61,19 +49,14 @@ private static void InspectorPortStatusRow( ShaderNodePort port, string? component, string status - ) - => Widget.PropertyRow( - "shader." + id, - PortLabel(port, component), - () => Widget.MetadataValue( - DisplayType(port.type.id), - status, - "Shader value type · " + port.type.id)); - + ) => EditorUI.current.widgets.PropertyRow( + "shader." + id, + PortLabel(port, component), + () => EditorUI.current.widgets.MetadataValue(DisplayType(port.type.id), status, "Shader value type · " + port.type.id) + ); private static string PortLabel( ShaderNodePort port, string? component ) => component is null ? port.id : port.id + " " + component; - - private static string DisplayType(string typeId) => Widget.NicifyName(typeId); + private static string DisplayType(string typeId) => EditorDisplayNames.Nicify(typeId); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.MiniMap.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.MiniMap.cs index 0277c1db0..4195f7257 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.MiniMap.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.MiniMap.cs @@ -1,31 +1,25 @@ -using System.Runtime.CompilerServices; -using NumericsVector2 = System.Numerics.Vector2; -using NativeVector2 = Inno.Native.ImGui.ImVec2C; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; - using Inno.Core.Graphs; using Inno.Editor.ImGui; -using Inno.Native.ImGui; -using ImGuiApi = Inno.Native.ImGui.ImGui; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using ImGuiApi = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.ShaderEditor; - -internal sealed unsafe partial class ShaderEditorCanvas +internal sealed partial class ShaderEditorCanvas { private const float C_MINI_MAP_WIDTH = 220f; private const float C_MINI_MAP_HEIGHT = 145f; private const float C_MINI_MAP_MARGIN = 14f; private const float C_MINI_MAP_PADDING = 9f; private readonly Dictionary m_miniMapGeometry = []; - private readonly record struct MiniMapNodeGeometry( Vector2 minimum, Vector2 maximum - ) { + ) + { internal Vector2 center => (minimum + maximum) * 0.5f; } @@ -33,9 +27,9 @@ private readonly record struct MiniMapLayout( Vector2 worldMinimum, Vector2 screenOrigin, float scale - ) { + ) + { internal Vector2 ToScreen(Vector2 position) => screenOrigin + (position - worldMinimum) * scale; - internal Vector2 ToWorld(Vector2 position) => worldMinimum + (position - screenOrigin) / scale; } @@ -43,8 +37,7 @@ private bool MiniMapContains(Vector2 point) { if (!TryGetMiniMapBounds(out Vector2 minimum, out Vector2 maximum)) return false; - return point.X >= minimum.X && point.X <= maximum.X && - point.Y >= minimum.Y && point.Y <= maximum.Y; + return point.X >= minimum.X && point.X <= maximum.X && point.Y >= minimum.Y && point.Y <= maximum.Y; } private bool TryGetMiniMapBounds( @@ -54,11 +47,8 @@ out Vector2 maximum minimum = maximum = default; if (m_size.X < 320f || m_size.Y < 220f) return false; - - float zoom = Widget.style.zoom; - Vector2 size = new( - MathF.Min(C_MINI_MAP_WIDTH * zoom, m_size.X * 0.30f), - MathF.Min(C_MINI_MAP_HEIGHT * zoom, m_size.Y * 0.30f)); + float zoom = EditorUI.current.widgets.style.zoom; + Vector2 size = new(MathF.Min(C_MINI_MAP_WIDTH * zoom, m_size.X * 0.30f), MathF.Min(C_MINI_MAP_HEIGHT * zoom, m_size.Y * 0.30f)); maximum = m_origin + new Vector2(m_size.X - C_MINI_MAP_MARGIN * zoom, C_MINI_MAP_MARGIN * zoom + size.Y); minimum = maximum - size; return true; @@ -68,39 +58,32 @@ private void DrawMiniMap() { if (!TryGetMiniMapBounds(out Vector2 minimum, out Vector2 maximum)) return; - Vector2 size = maximum - minimum; - Vector2 cursor = ImGuiApi.GetCursorScreenPos(); - ImGuiApi.SetCursorScreenPos(minimum); - ImGuiApi.PushStyleVar((int)ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGuiApi.PushStyleColor((int)ImGuiCol.ChildBg, new Vector4(0.085f, 0.092f, 0.11f, 0.95f)); + Vector2 cursor = ImGuiApi.GetCursorScreenPosition(); + ImGuiApi.SetCursorScreenPosition(minimum); + ImGuiApi.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGuiApi.PushStyleColor(ImGuiCol.ChildBg, new Vector4(0.085f, 0.092f, 0.11f, 0.95f)); bool began = false; try { bool visible = ImGuiApi.BeginChild( "##shader-mini-map", size, - (int)ImGuiChildFlags.Borders, - (int)(ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings)); + ImGuiChildFlags.Borders, + (ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings) + ); began = true; if (!visible) return; - MiniMapLayout layout = CreateMiniMapLayout(minimum, maximum); - ImGuiApi.InvisibleButton( - "##mini-map-navigation", - Vector2.Max(ImGuiApi.GetContentRegionAvail(), Vector2.One), - (int)ImGuiButtonFlags.MouseButtonLeft); - if (ImGuiApi.IsItemActive() && ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Left)) + ImGuiApi.InvisibleButton("##mini-map-navigation", Vector2.Max(ImGuiApi.GetContentRegionAvailable(), Vector2.One), ImGuiButtonFlags.MouseButtonLeft); + if (ImGuiApi.IsItemActive() && ImGuiApi.IsMouseButtonDown(ImGuiMouseButton.Left)) { Vector2 graphPoint = layout.ToWorld(Vector2.Clamp(ImGuiApi.GetMousePos(), minimum, maximum)); - Canvas.SetViewport( - new(m_size.X * 0.5f - graphPoint.X * Canvas.zoom, - m_size.Y * 0.5f - graphPoint.Y * Canvas.zoom), - Canvas.zoom); + Canvas.SetViewport(new(m_size.X * 0.5f - graphPoint.X * Canvas.zoom, m_size.Y * 0.5f - graphPoint.Y * Canvas.zoom), Canvas.zoom); } - DrawMiniMapContents(ImGuiApi.Raw.GetWindowDrawList(), layout); + DrawMiniMapContents(Inno.Editor.ImGui.ImGui.GetDrawList(), layout); } finally { @@ -108,7 +91,7 @@ private void DrawMiniMap() ImGuiApi.EndChild(); ImGuiApi.PopStyleColor(); ImGuiApi.PopStyleVar(); - ImGuiApi.SetCursorScreenPos(cursor); + ImGuiApi.SetCursorScreenPosition(cursor); } } @@ -123,18 +106,16 @@ Vector2 maximum m_miniMapGeometry.Clear(); foreach (GraphNodeRecord node in Controller.document.nodes) { - GraphPosition position = draft.dragPreview.GetValueOrDefault(node.id, node.position); + GraphPosition position = draft.pointer.previewPositions.GetValueOrDefault(node.id, node.position); Vector2 nodeMinimum = new(position.x, position.y); - Vector2 nodeMaximum = nodeMinimum + new Vector2( - NodeWidth(node), - C_HEADER + Rows(node) * C_ROW + ControlHeight(node) + 20f); + Vector2 nodeMaximum = nodeMinimum + new Vector2(NodeWidth(node), C_HEADER + Rows(node) * C_ROW + 20f); m_miniMapGeometry.Add(node.id, new(nodeMinimum, nodeMaximum)); worldMinimum = Vector2.Min(worldMinimum, nodeMinimum); worldMaximum = Vector2.Max(worldMaximum, nodeMaximum); } - Vector2 innerMinimum = minimum + new Vector2(C_MINI_MAP_PADDING * Widget.style.zoom); - Vector2 innerSize = maximum - minimum - new Vector2(C_MINI_MAP_PADDING * Widget.style.zoom * 2f); + Vector2 innerMinimum = minimum + new Vector2(C_MINI_MAP_PADDING * EditorUI.current.widgets.style.zoom); + Vector2 innerSize = maximum - minimum - new Vector2(C_MINI_MAP_PADDING * EditorUI.current.widgets.style.zoom * 2f); Vector2 worldSize = Vector2.Max(worldMaximum - worldMinimum, Vector2.One); float scale = MathF.Min(innerSize.X / worldSize.X, innerSize.Y / worldSize.Y); Vector2 screenOrigin = innerMinimum + (innerSize - worldSize * scale) * 0.5f; @@ -142,60 +123,35 @@ Vector2 maximum } private void DrawMiniMapContents( - ImDrawList* draw, + EditorDrawList draw, MiniMapLayout layout ) { foreach (GraphEdgeRecord edge in Controller.document.edges) { - if (!m_miniMapGeometry.TryGetValue(edge.output.nodeId, out MiniMapNodeGeometry source) || - !m_miniMapGeometry.TryGetValue(edge.input.nodeId, out MiniMapNodeGeometry target)) + if (!m_miniMapGeometry.TryGetValue( + edge.output.nodeId, + out MiniMapNodeGeometry source + ) + || !m_miniMapGeometry.TryGetValue( + edge.input.nodeId, + out MiniMapNodeGeometry target + )) continue; - DrawNativeLine( - draw, - Unsafe.BitCast(layout.ToScreen(source.center)), - Unsafe.BitCast(layout.ToScreen(target.center)), - Color(0.55f, 0.42f, 0.76f, 0.65f), - 1f - ); + draw.Line(layout.ToScreen(source.center), layout.ToScreen(target.center), Color(0.55f, 0.42f, 0.76f, 0.65f), 1f); } foreach (GraphNodeRecord node in Controller.document.nodes) { MiniMapNodeGeometry geometry = m_miniMapGeometry[node.id]; - uint fill = Canvas.selectedNodes.Contains(node.id) - ? Color(0.60f, 0.44f, 0.83f) - : Color(0.34f, 0.37f, 0.44f); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(layout.ToScreen(geometry.minimum)), - Unsafe.BitCast(layout.ToScreen(geometry.maximum)), - fill, - 2f, - 0 - ); + uint fill = Canvas.selectedNodes.Contains(node.id) ? Color(0.60f, 0.44f, 0.83f) : Color(0.34f, 0.37f, 0.44f); + draw.FilledRectangle(layout.ToScreen(geometry.minimum), layout.ToScreen(geometry.maximum), fill, 2f, false); } Vector2 viewMinimum = -new Vector2(Canvas.pan.x, Canvas.pan.y) / Canvas.zoom; Vector2 viewMaximum = viewMinimum + m_size / Canvas.zoom; Vector2 screenMinimum = layout.ToScreen(viewMinimum); Vector2 screenMaximum = layout.ToScreen(viewMaximum); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(screenMinimum), - Unsafe.BitCast(screenMaximum), - Color(0.65f, 0.47f, 0.88f, 0.10f), - 0, - 0 - ); - DrawNativeRectangle( - draw, - Unsafe.BitCast(screenMinimum), - Unsafe.BitCast(screenMaximum), - Color(0.76f, 0.62f, 0.95f), - 0f, - (int)ImDrawFlags.None, - 1.5f - ); + draw.FilledRectangle(screenMinimum, screenMaximum, Color(0.65f, 0.47f, 0.88f, 0.10f), 0, false); + draw.Rectangle(screenMinimum, screenMaximum, Color(0.76f, 0.62f, 0.95f), 0f, 1.5f); } - } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Parameters.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Parameters.cs index 665e3481d..566435989 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Parameters.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Parameters.cs @@ -1,44 +1,48 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Linq; using Inno.Core.Graphs; -using Inno.Native.ImGui; using Inno.Rendering; using Inno.Rendering.Shaders; using Inno.Editor.Shaders; +using Inno.Editor.Shaders.ImGui; using EditorImGui = Inno.Editor.ImGui.ImGui; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private bool StoragePopup(ref ShaderGraphType type) { - ImGuiApi.SetNextWindowSize(new(490, 390), (int)(ImGuiCond.Appearing)); - if (!ImGuiApi.BeginPopup("##storage")) + EditorImGui.SetNextWindowSize(new(490, 390), (ImGuiCond.Appearing)); + if (!EditorImGui.BeginPopup("##storage")) return false; bool changed = false; try { bool image = type.isImage; bool imageChanged = false; - InspectorRow("storage.image", "Storage Image", () => imageChanged = ImGuiApi.Checkbox("##image", ref image)); + InspectorRow("storage.image", "Storage Image", () => imageChanged = EditorImGui.Checkbox("##image", ref image)); if (imageChanged) { type.isImage = image; type.isStorage = true; - type.storageElement = image ? null : new() { id = "float4" }; + type.storageElement = image ? null : new() + { + id = "float4" + }; type.format = RenderTextureFormat.RGBA16Float; changed = true; } + RenderStorageAccess access = type.access; if (EnumControl("Access", ref access)) { type.access = access; changed = true; } + if (type.isImage) { RenderTextureFormat format = type.format; @@ -47,15 +51,17 @@ private bool StoragePopup(ref ShaderGraphType type) type.format = format; changed = true; } + RenderTextureDimension dimension = type.dimension; if (EnumControl("Dimension", ref dimension)) { type.dimension = dimension; changed = true; } + bool array = type.isArray; bool arrayChanged = false; - InspectorRow("storage.array", "Array Layers", () => arrayChanged = ImGuiApi.Checkbox("##array", ref array)); + InspectorRow("storage.array", "Array Layers", () => arrayChanged = EditorImGui.Checkbox("##array", ref array)); if (arrayChanged) { type.isArray = array; @@ -68,31 +74,52 @@ private bool StoragePopup(ref ShaderGraphType type) string selected = current; InspectorRow("storage.element", "Element", () => { - if (!Widget.BeginBoundedCombo("##element", current)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##element", current)) return; try { - foreach (string candidate in new[] { "float", "float2", "float3", "float4", "int", "int2", "int3", "int4", "uint", "uint2", "uint3", "uint4" }) - if (ImGuiApi.Selectable(candidate, current == candidate)) + foreach (string candidate in new[] + { + "float", + "float2", + "float3", + "float4", + "int", + "int2", + "int3", + "int4", + "uint", + "uint2", + "uint3", + "uint4" + } + + ) + if (EditorImGui.Selectable(candidate, current == candidate)) selected = candidate; } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); if (selected != current) { - type.storageElement = new() { id = selected }; + type.storageElement = new() + { + id = selected + }; changed = true; } } - Widget.WrappedText("Storage resources are bound by the render pass. The graph declares types and access; the Render Graph owns resource lifetime and synchronization."); + + EditorUI.current.widgets.WrappedText("Storage resources are bound by the render pass. The graph declares types and access; the Render Graph owns resource lifetime and synchronization."); } finally { - ImGuiApi.EndPopup(); + EditorImGui.EndPopup(); } + return changed; } @@ -104,11 +131,15 @@ private void DrawParameter(ShaderGraphInputSettings input) int index = Array.FindIndex(definition.properties, value => value.id.value == input.id); if (index < 0) { - Widget.Hint("Enter a binding name and supported type to declare this parameter."); + EditorUI.current.widgets.Hint("Enter a binding name and supported type to declare this parameter."); return; } + ShaderPropertyDefinition property = definition.properties[index]; - bool parameterOpen = Widget.SectionHeader("Parameter", "The stable binding ID identifies overrides. Shader defaults and Material overrides are edited independently."); + bool parameterOpen = EditorUI.current.widgets.SectionHeader( + "Parameter", + "The stable binding ID identifies overrides. Shader defaults and Material overrides are edited independently." + ); if (parameterOpen) { string displayName = property.displayName; @@ -120,18 +151,20 @@ private void DrawParameter(ShaderGraphInputSettings input) property.displayName = displayName; Save(property, true); } + ShaderPropertyBindingOwner bindingOwner = property.bindingOwner; if (EnumControl("Bound By", ref bindingOwner)) { property.bindingOwner = bindingOwner; Save(property, false); } + if (property.type is ShaderPropertyType.Vector4 or ShaderPropertyType.Color) { bool color = property.type == ShaderPropertyType.Color; InspectorRow("parameter.color", "Color", () => { - if (ImGuiApi.Checkbox("##color", ref color)) + if (EditorImGui.Checkbox("##color", ref color)) { property.type = color ? ShaderPropertyType.Color : ShaderPropertyType.Vector4; MaterialValue converted = property.defaultValue; @@ -142,20 +175,26 @@ private void DrawParameter(ShaderGraphInputSettings input) }); } } + if (property.bindingOwner != ShaderPropertyBindingOwner.Material) { if (parameterOpen) - Widget.Hint("Supplied by the Render Pass. This binding is read-only in Material Inspectors."); + EditorUI.current.widgets.Hint("Supplied by the Render Pass. This binding is read-only in Material Inspectors."); return; } + if (property.bindingKind is not (ShaderPropertyBindingKind.Uniform or ShaderPropertyBindingKind.SampledTexture)) { if (parameterOpen) - Widget.Hint("Storage resources require a Render Pass owner."); + EditorUI.current.widgets.Hint("Storage resources require a Render Pass owner."); return; } + ShaderParameterPresentation presentation = ShaderParameterPresentation.Read(Controller.document, property.id, owner.serialization, owner.context); - if (!Widget.SectionHeader("Material Inspector", "Presentation belongs only to this Shader's authoring graph. It does not modify existing Material values or enter the Player.")) + if (!EditorUI.current.widgets.SectionHeader( + "Material Inspector", + "Presentation belongs only to this Shader's authoring graph. It does not modify existing Material values or enter the Player." + )) return; string group = presentation.group; bool groupChanged = false; @@ -165,6 +204,7 @@ private void DrawParameter(ShaderGraphInputSettings input) presentation.group = group; SavePresentation(true); } + Gesture(); string description = presentation.description; bool descriptionChanged = false; @@ -174,11 +214,12 @@ private void DrawParameter(ShaderGraphInputSettings input) presentation.description = description; SavePresentation(true); } + Gesture(); bool visible = presentation.visible; InspectorRow("parameter.visible", "Visible in Material", () => { - if (ImGuiApi.Checkbox("##visible", ref visible)) + if (EditorImGui.Checkbox("##visible", ref visible)) { presentation.visible = visible; SavePresentation(false); @@ -189,7 +230,7 @@ private void DrawParameter(ShaderGraphInputSettings input) bool range = presentation.hasRange; InspectorRow("parameter.range", "Range", () => { - if (ImGuiApi.Checkbox("##range", ref range)) + if (EditorImGui.Checkbox("##range", ref range)) { presentation.hasRange = range; SavePresentation(false); @@ -197,76 +238,60 @@ private void DrawParameter(ShaderGraphInputSettings input) }); if (range) { - m_inspection.properties.DrawValue(m_inspection.editorContext, draft, "shader.parameter." + property.id.value + ".minimum", - "Minimum", typeof(double), () => presentation.minimum, value => - { - if (!double.IsFinite((double)value!)) - return; - presentation.minimum = Math.Clamp((double)value!, -float.MaxValue, presentation.maximum); - SavePresentation(true); - }, new ParameterEdits(this), draft.readOnly, minimum: -float.MaxValue, maximum: presentation.maximum); - m_inspection.properties.DrawValue(m_inspection.editorContext, draft, "shader.parameter." + property.id.value + ".maximum", - "Maximum", typeof(double), () => presentation.maximum, value => - { - if (!double.IsFinite((double)value!)) - return; - presentation.maximum = Math.Clamp((double)value!, presentation.minimum, float.MaxValue); - SavePresentation(true); - }, new ParameterEdits(this), draft.readOnly, minimum: presentation.minimum, maximum: float.MaxValue); - Widget.Hint("Editing bounds only · existing defaults and overrides are not clamped on display"); + m_inspection.properties.DrawValue(m_inspection.editorContext, draft, "shader.parameter." + property.id.value + ".minimum", "Minimum", typeof(double), () => presentation.minimum, value => + { + if (!double.IsFinite((double)value!)) + return; + presentation.minimum = Math.Clamp((double)value!, -float.MaxValue, presentation.maximum); + SavePresentation(true); + }, new ParameterEdits(this), draft.readOnly, minimum: -float.MaxValue, maximum: presentation.maximum); + m_inspection.properties.DrawValue(m_inspection.editorContext, draft, "shader.parameter." + property.id.value + ".maximum", "Maximum", typeof(double), () => presentation.maximum, value => + { + if (!double.IsFinite((double)value!)) + return; + presentation.maximum = Math.Clamp((double)value!, presentation.minimum, float.MaxValue); + SavePresentation(true); + }, new ParameterEdits(this), draft.readOnly, minimum: presentation.minimum, maximum: float.MaxValue); + EditorUI.current.widgets.Hint("Editing bounds only · existing defaults and overrides are not clamped on display"); } } + ShaderPropertyDefinition shown = property; shown.displayName = "Default"; - ShaderPropertyInspector.Draw(m_inspection, draft, "shader.parameter." + property.id.value, shown, property.defaultValue, - value => - { - property.defaultValue = value; - Save(property, true); - }, new ParameterEdits(this), draft.readOnly, presentation); + ShaderPropertyInspector.Draw(m_inspection, draft, "shader.parameter." + property.id.value, shown, property.defaultValue, value => + { + property.defaultValue = value; + Save(property, true); + }, new ParameterEdits(this), draft.readOnly, presentation); void SavePresentation(bool continuous) { Gesture(); GraphDocument candidate = Controller.document.Clone(); ShaderParameterPresentation.Write(candidate, property.id, presentation, owner.serialization, owner.context); - Controller.ReplaceDocument(candidate, "Edit Parameter Presentation", continuous && ImGuiApi.IsAnyItemActive() ? draft.valueGesture : null); + Controller.ReplaceDocument(candidate, "Edit Parameter Presentation", continuous ? EditorUI.current.widgets.editMergeKey : null); owner.Changed(draft); } + void Save( ShaderPropertyDefinition value, bool continuous ) { definition.properties[index] = value; - CommitDefinition(definition, "Edit Shader Parameter", continuous && ImGuiApi.IsAnyItemActive()); + CommitDefinition(definition, "Edit Shader Parameter", continuous && EditorImGui.IsAnyItemActive()); } } private sealed class ParameterEdits(ShaderEditorCanvas canvas) : Inno.Editor.Inspection.IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) { + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) { canvas.Gesture(); mutation(); return true; @@ -279,8 +304,11 @@ private void CommitDefinition( bool continuous = false ) { GraphDocument candidate = Controller.document.Clone(); - candidate.SetMetadata(ShaderGraphDocument.definitionKey, ShaderGraphDocument.Encode(owner.serialization.Serialize(definition, owner.context), owner.serialization, owner.context)); - Controller.ReplaceDocument(candidate, label, continuous ? draft.valueGesture : null); + candidate.SetMetadata( + ShaderGraphDocument.definitionKey, + ShaderGraphDocument.Encode(owner.serialization.Serialize(definition, owner.context), owner.serialization, owner.context) + ); + Controller.ReplaceDocument(candidate, label, continuous ? EditorUI.current.widgets.editMergeKey : null); owner.Changed(draft); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PassSettings.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PassSettings.cs index 7f7928da3..87d5b9fab 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PassSettings.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PassSettings.cs @@ -1,29 +1,28 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Linq; using Inno.Core.Graphs; -using Inno.Native.ImGui; using Inno.Rendering; using Inno.Rendering.Shaders; using EditorImGui = Inno.Editor.ImGui.ImGui; -using ImGuiApi = Inno.Native.ImGui.ImGui; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void StageSettingsPopup( GraphNodeRecord node, ShaderGraphStageSettings stage ) { - ImGuiApi.SetNextWindowSize(new(540, 600), (int)(ImGuiCond.Appearing)); - if (!ImGuiApi.BeginPopup("##stage-settings")) + EditorImGui.SetNextWindowSize(new(540, 600), (ImGuiCond.Appearing)); + if (!EditorImGui.BeginPopup("##stage-settings")) return; try { ShaderDefinition definition = ShaderGraphDocument.ReadDefinition(Controller.document, owner.serialization, owner.context); bool definitionChanged = false; - ImGuiApi.SeparatorText("Shader"); + EditorImGui.SeparatorText("Shader"); string name = definition.name; bool nameChanged = false; InspectorRow("settings.name", "Name", () => nameChanged = EditorImGui.InputText("##name", ref name, 256)); @@ -32,21 +31,21 @@ ShaderGraphStageSettings stage definition.name = name; definitionChanged = true; } + Gesture(); - ImGuiApi.SeparatorText(stage.stage + " Interface"); + EditorImGui.SeparatorText(stage.stage + " Interface"); ShaderGraphPassProgram[] programs = ShaderGraphPrograms.Read(Controller.document, owner.serialization, owner.context); string activePass = draft.inspectedPass; if (!definition.passes.Any(pass => pass.name == activePass)) - activePass = programs.FirstOrDefault(program => program.stages.Contains(node.id.value, StringComparer.Ordinal)).pass - ?? definition.passes.FirstOrDefault().name ?? ""; + activePass = programs.FirstOrDefault(program => program.stages.Contains(node.id.value, StringComparer.Ordinal)).pass ?? definition.passes.FirstOrDefault().name ?? ""; InspectorRow("settings.pass", "Pass State", () => { - if (!Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##pass-state", activePass)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##pass-state", activePass)) return; try { foreach (ShaderPassDefinition available in definition.passes) - if (ImGuiApi.Selectable(available.name, activePass == available.name)) + if (EditorImGui.Selectable(available.name, activePass == available.name)) { activePass = available.name; draft.inspectedPass = activePass; @@ -54,91 +53,111 @@ ShaderGraphStageSettings stage } finally { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); ShaderGraphPassProgram assignment = programs.FirstOrDefault(program => program.pass == activePass); bool assigned = assignment.stages?.Contains(node.id.value, StringComparer.Ordinal) == true; bool assignmentChanged = false; if (activePass.Length != 0) - InspectorRow("settings.shared-stage", "Shared Stage", () => assignmentChanged = ImGuiApi.Checkbox("##shared-stage", ref assigned)); + InspectorRow("settings.shared-stage", "Shared Stage", () => assignmentChanged = EditorImGui.Checkbox("##shared-stage", ref assigned)); if (assignmentChanged) { var stageIds = (assignment.stages ?? []).Where(id => id != node.id.value).Select(static id => new GraphNodeId(id)).ToList(); if (assigned) { - stageIds.RemoveAll(id => Controller.document.FindNode(id) is GraphNodeRecord existing - && Read(existing, "settings", new ShaderGraphStageSettings()).stage == stage.stage); + stageIds.RemoveAll(id => Controller.document.FindNode(id) is GraphNodeRecord existing && Read(existing, "settings", new ShaderGraphStageSettings()).stage == stage.stage); stageIds.Add(node.id); } + Controller.ReplaceDocument(ShaderGraphPrograms.Bind(Controller.document, activePass, stageIds, owner.serialization, owner.context), "Assign Shared Shader Stage"); owner.Changed(draft); } + for (int i = 0; i < stage.outputs.Length; i++) { - ImGuiApi.PushID(i); - ShaderGraphOutput output = stage.outputs[i]; - bool changed = false; - string id = output.id; - bool idChanged = false; - InspectorRow("output.id", "Port ID", () => idChanged = EditorImGui.InputText("##port-id", ref id, 128)); - if (idChanged) - { - output.id = id; - changed = true; - } - Gesture(); - ShaderIrOutputKind kind = output.kind; - if (EnumControl("Destination", ref kind)) - { - output.kind = kind; - changed = true; - } - string semantic = output.semantic ?? ""; - bool semanticChanged = false; - InspectorRow("output.semantic", "Semantic", () => semanticChanged = EditorImGui.InputText("##semantic", ref semantic, 128)); - if (semanticChanged) - { - output.semantic = semantic; - changed = true; - } - Gesture(); - int location = output.location; - bool locationChanged = false; - InspectorRow("output.location", "Location", () => locationChanged = ImGuiApi.InputInt("##location", ref location, 1, 100, 0)); - if (locationChanged) - { - output.location = location; - changed = true; - } - Gesture(); - if (changed) + EditorImGui.PushId(i); + try { - stage.outputs[i] = output; - Set(node, "settings", stage, true); + ShaderGraphOutput output = stage.outputs[i]; + bool changed = false; + string id = output.id; + bool idChanged = false; + InspectorRow("output.id", "Port ID", () => idChanged = EditorImGui.InputText("##port-id", ref id, 128)); + if (idChanged) + { + output.id = id; + changed = true; + } + + Gesture(); + ShaderIrOutputKind kind = output.kind; + if (EnumControl("Destination", ref kind)) + { + output.kind = kind; + changed = true; + } + + string semantic = output.semantic ?? ""; + bool semanticChanged = false; + InspectorRow("output.semantic", "Semantic", () => semanticChanged = EditorImGui.InputText("##semantic", ref semantic, 128)); + if (semanticChanged) + { + output.semantic = semantic; + changed = true; + } + + Gesture(); + int location = output.location; + bool locationChanged = false; + InspectorRow("output.location", "Location", () => locationChanged = EditorImGui.InputInt("##location", ref location, 1, 100, 0)); + if (locationChanged) + { + output.location = location; + changed = true; + } + + Gesture(); + if (changed) + { + stage.outputs[i] = output; + Set(node, "settings", stage, true); + } + + if (EditorImGui.SmallButton("Remove Output")) + { + stage.outputs = stage.outputs.Where(( + _, + index + ) => index != i).ToArray(); + Set(node, "settings", stage); + break; + } + + EditorImGui.Separator(); } - if (ImGuiApi.SmallButton("Remove Output")) + finally { - stage.outputs = stage.outputs.Where(( - _, - index - ) => index != i).ToArray(); - Set(node, "settings", stage); - ImGuiApi.PopID(); - break; + EditorImGui.PopId(); } - ImGuiApi.Separator(); - ImGuiApi.PopID(); } + if (CenteredAddButton("Add Output")) { - stage.outputs = [.. stage.outputs, new() { id = "output-" + Guid.NewGuid().ToString("N"), + stage.outputs = [..stage.outputs, new() + { + id = "output-" + Guid.NewGuid().ToString("N"), kind = stage.stage == ShaderStage.Vertex ? ShaderIrOutputKind.Varying : ShaderIrOutputKind.Color, - semantic = "texcoord", location = stage.outputs.Length }]; + semantic = "texcoord", + location = stage.outputs.Length + } + + ]; Set(node, "settings", stage); } + int passIndex = Array.FindIndex(definition.passes, pass => pass.name == activePass); - if (passIndex >= 0 && ImGuiApi.CollapsingHeader("Pass State")) + if (passIndex >= 0 && EditorImGui.CollapsingHeader("Pass State")) { ShaderPassDefinition pass = definition.passes[passIndex]; ShaderRenderState state = pass.renderState; @@ -149,60 +168,69 @@ ShaderGraphStageSettings stage state.topology = topology; changed = true; } + ShaderCullMode cull = state.cull; if (EnumControl("Cull", ref cull)) { state.cull = cull; changed = true; } + RenderFrontFace front = state.frontFace; if (EnumControl("Front Face", ref front)) { state.frontFace = front; changed = true; } + ShaderCompareFunction depth = state.depthCompare; if (EnumControl("Depth Test", ref depth)) { state.depthCompare = depth; changed = true; } + bool depthWrite = state.depthWrite, multisampling = state.multisampling; bool depthWriteChanged = false, multisamplingChanged = false; - InspectorRow("state.depth-write", "Depth Write", () => depthWriteChanged = ImGuiApi.Checkbox("##depth-write", ref depthWrite)); - InspectorRow("state.multisampling", "Multisampling", () => multisamplingChanged = ImGuiApi.Checkbox("##multisampling", ref multisampling)); + InspectorRow("state.depth-write", "Depth Write", () => depthWriteChanged = EditorImGui.Checkbox("##depth-write", ref depthWrite)); + InspectorRow("state.multisampling", "Multisampling", () => multisamplingChanged = EditorImGui.Checkbox("##multisampling", ref multisampling)); if (depthWriteChanged) { state.depthWrite = depthWrite; changed = true; } + if (multisamplingChanged) { state.multisampling = multisampling; changed = true; } + InspectorRow("state.blend-preset", "Blend Preset", () => { - if (!Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##blend-preset", state.blend.enabled ? "Blended" : "Opaque")) + if (!EditorUI.current.widgets.BeginBoundedCombo("##blend-preset", state.blend.enabled ? "Blended" : "Opaque")) return; try { - if (ImGuiApi.Selectable("Opaque")) + if (EditorImGui.Selectable("Opaque")) { state.blend = RenderBlendState.opaque; changed = true; } - if (ImGuiApi.Selectable("Alpha")) + + if (EditorImGui.Selectable("Alpha")) { state.blend = RenderBlendState.alpha; changed = true; } - if (ImGuiApi.Selectable("Premultiplied")) + + if (EditorImGui.Selectable("Premultiplied")) { state.blend = RenderBlendState.premultiplied; changed = true; } - if (ImGuiApi.Selectable("Additive")) + + if (EditorImGui.Selectable("Additive")) { state.blend = RenderBlendState.additive; changed = true; @@ -210,63 +238,71 @@ ShaderGraphStageSettings stage } finally { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); int mask = state.colorWriteMask; bool maskChanged = false; - InspectorRow("state.write-mask", "RGBA Write Mask", () => maskChanged = ImGuiApi.InputInt("##write-mask", ref mask, 1, 100, 0)); + InspectorRow("state.write-mask", "RGBA Write Mask", () => maskChanged = EditorImGui.InputInt("##write-mask", ref mask, 1, 100, 0)); if (maskChanged) { state.colorWriteMask = (byte)Math.Clamp(mask, 0, 15); changed = true; } + RenderBlendState blend = state.blend; - if (ImGuiApi.TreeNodeEx("Custom Blending", 0)) + if (EditorImGui.TreeNode("Custom Blending", 0)) { bool enabled = blend.enabled; bool enabledChanged = false; - InspectorRow("blend.enabled", "Enabled", () => enabledChanged = ImGuiApi.Checkbox("##enabled", ref enabled)); + InspectorRow("blend.enabled", "Enabled", () => enabledChanged = EditorImGui.Checkbox("##enabled", ref enabled)); if (enabledChanged) { blend.enabled = enabled; changed = true; } - RenderBlendFactor colorSource = blend.colorSource, colorDestination = blend.colorDestination, - alphaSource = blend.alphaSource, alphaDestination = blend.alphaDestination; + + RenderBlendFactor colorSource = blend.colorSource, colorDestination = blend.colorDestination, alphaSource = blend.alphaSource, alphaDestination = blend.alphaDestination; RenderBlendEquation colorEquation = blend.colorEquation, alphaEquation = blend.alphaEquation; if (EnumControl("RGB Source", ref colorSource)) { blend.colorSource = colorSource; changed = true; } + if (EnumControl("RGB Destination", ref colorDestination)) { blend.colorDestination = colorDestination; changed = true; } + if (EnumControl("RGB Equation", ref colorEquation)) { blend.colorEquation = colorEquation; changed = true; } + if (EnumControl("Alpha Source", ref alphaSource)) { blend.alphaSource = alphaSource; changed = true; } + if (EnumControl("Alpha Destination", ref alphaDestination)) { blend.alphaDestination = alphaDestination; changed = true; } + if (EnumControl("Alpha Equation", ref alphaEquation)) { blend.alphaEquation = alphaEquation; changed = true; } - ImGuiApi.TreePop(); + + EditorImGui.TreePop(); } + state.blend = blend; GraphicsCapability features = pass.requiredFeatures; if (FeatureControls(ref features)) @@ -274,6 +310,7 @@ ShaderGraphStageSettings stage pass.requiredFeatures = features; changed = true; } + if (changed) { pass.renderState = state; @@ -281,101 +318,117 @@ ShaderGraphStageSettings stage definitionChanged = true; } } + definitionChanged |= TechniqueControls(definition); - if (ImGuiApi.CollapsingHeader("Variants")) + if (EditorImGui.CollapsingHeader("Variants")) { for (int i = 0; i < definition.keywords.Length; i++) { - ImGuiApi.PushID("keyword-" + i); - ShaderKeywordDefinition keyword = definition.keywords[i]; - string id = keyword.id; - bool keywordChanged = false; - InspectorRow("variant.keyword", "Keyword", () => keywordChanged = EditorImGui.InputText("##keyword", ref id, 128)); - if (keywordChanged) - { - keyword.id = id; - definitionChanged = true; - } - Gesture(); - for (int j = 0; j < keyword.options.Length; j++) + EditorImGui.PushId("keyword-" + i); + try { - string option = keyword.options[j]; - bool optionChanged = false, remove = false; - InspectorRow("variant.option", "Option " + (j + 1), () => - { - optionChanged = EditorImGui.InputText("##option", ref option, 128); - ImGuiApi.SameLine(0f, -1f); - remove = ImGuiApi.SmallButton("Remove"); - }); - if (optionChanged) + ShaderKeywordDefinition keyword = definition.keywords[i]; + string id = keyword.id; + bool keywordChanged = false; + InspectorRow("variant.keyword", "Keyword", () => keywordChanged = EditorImGui.InputText("##keyword", ref id, 128)); + if (keywordChanged) { - keyword.options[j] = option; + keyword.id = id; definitionChanged = true; } + Gesture(); - if (remove) + for (int j = 0; j < keyword.options.Length; j++) + { + string option = keyword.options[j]; + bool optionChanged = false, remove = false; + InspectorRow("variant.option", "Option " + (j + 1), () => + { + optionChanged = EditorImGui.InputText("##option", ref option, 128); + EditorImGui.SameLine(0f, -1f); + remove = EditorImGui.SmallButton("Remove"); + }); + if (optionChanged) + { + keyword.options[j] = option; + definitionChanged = true; + } + + Gesture(); + if (remove) + { + keyword.options = keyword.options.Where(( + _, + index + ) => index != j).ToArray(); + definitionChanged = true; + break; + } + } + + if (CenteredAddButton("Add Option")) + { + keyword.options = [..keyword.options, "Option" + keyword.options.Length]; + definitionChanged = true; + } + + definition.keywords[i] = keyword; + if (EditorImGui.SmallButton("Remove Keyword")) { - keyword.options = keyword.options.Where(( + definition.keywords = definition.keywords.Where(( _, index - ) => index != j).ToArray(); + ) => index != i).ToArray(); definitionChanged = true; break; } } - if (CenteredAddButton("Add Option")) - { - keyword.options = [.. keyword.options, "Option" + keyword.options.Length]; - definitionChanged = true; - } - definition.keywords[i] = keyword; - if (ImGuiApi.SmallButton("Remove Keyword")) + finally { - definition.keywords = definition.keywords.Where(( - _, - index - ) => index != i).ToArray(); - definitionChanged = true; - ImGuiApi.PopID(); - break; + EditorImGui.PopId(); } - ImGuiApi.PopID(); } + if (CenteredAddButton("Add Keyword")) { - definition.keywords = [.. definition.keywords, new("Keyword" + definition.keywords.Length, ["Off", "On"])]; + definition.keywords = [..definition.keywords, new("Keyword" + definition.keywords.Length, ["Off", "On"])]; definitionChanged = true; } } + if (definitionChanged) { GraphDocument candidate = Controller.document.Clone(); - candidate.SetMetadata(ShaderGraphDocument.definitionKey, ShaderGraphDocument.Encode(owner.serialization.Serialize(definition, owner.context), owner.serialization, owner.context)); - Controller.ReplaceDocument(candidate, "Edit Shader Settings", ImGuiApi.IsAnyItemActive() ? draft.valueGesture : null); + candidate.SetMetadata( + ShaderGraphDocument.definitionKey, + ShaderGraphDocument.Encode(owner.serialization.Serialize(definition, owner.context), owner.serialization, owner.context) + ); + Controller.ReplaceDocument(candidate, "Edit Shader Settings", EditorImGui.IsAnyItemActive() ? draft.valueGesture : null); owner.Changed(draft); } } finally { - ImGuiApi.EndPopup(); + EditorImGui.EndPopup(); } } private bool EnumControl( string label, ref T value - ) where T : struct, Enum + ) + where T : struct, Enum { bool changed = false; T current = value; InspectorRow("enum." + label, label, () => { - if (!Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##enum", current.ToString())) + if (!EditorUI.current.widgets.BeginBoundedCombo("##enum", current.ToString())) return; try { foreach (T candidate in Enum.GetValues()) - if (ImGuiApi.Selectable(candidate.ToString(), candidate.Equals(current))) + if (EditorImGui.Selectable(candidate.ToString(), candidate.Equals(current))) { current = candidate; changed = true; @@ -383,7 +436,7 @@ ref T value } finally { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); value = current; diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PortRepair.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PortRepair.cs index f6fd36213..54cb928bc 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PortRepair.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.PortRepair.cs @@ -1,12 +1,11 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Linq; using Inno.Core.Graphs; using Inno.Rendering.Shaders; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void RepairPorts(GraphNodeRecord node) @@ -20,15 +19,15 @@ private void RepairPorts(GraphNodeRecord node) return; try { - Widget.WrappedText("Removed ports retain their connections. Choose a replacement explicitly; occupied inputs are not overwritten."); + EditorUI.current.widgets.WrappedText("Removed ports retain their connections. Choose a replacement explicitly; occupied inputs are not overwritten."); foreach (GraphEndpoint endpoint in missing) { ShaderNodePort old = Port(endpoint); - ImGuiApi.PushID(endpoint.portId.value); + ImGuiApi.PushId(endpoint.portId.value); try { - ImGuiApi.TextUnformatted(endpoint.portId.value + " · " + old.type.id); - if (!Widget.BeginBoundedCombo("##replacement", "Reconnect to…")) + ImGuiApi.Text(endpoint.portId.value + " · " + old.type.id); + if (!EditorUI.current.widgets.BeginBoundedCombo("##replacement", "Reconnect to…")) continue; foreach (ShaderNodePort replacement in draft.ports[node.id]) { @@ -48,18 +47,22 @@ private void RepairPorts(GraphNodeRecord node) candidate.RemoveEdge(edge.id); candidate.AddEdge(new(edge.id, edge.output == endpoint ? target : edge.output, edge.input == endpoint ? target : edge.input)); } + // Keep the retired declaration in metadata for a future source restoration and Undo. - candidate.FindNode(node.id)!.SetValue(ShaderEditorDocuments.C_PORT_SNAPSHOT, - ShaderGraphDocument.Encode(draft.portSnapshots[node.id], owner.serialization, owner.context)); + candidate.FindNode(node.id)!.SetValue( + ShaderEditorDocuments.C_PORT_SNAPSHOT, + ShaderGraphDocument.Encode(draft.portSnapshots[node.id], owner.serialization, owner.context) + ); Controller.ReplaceDocument(candidate, "Reconnect Shader Port"); owner.Changed(draft); break; } - Widget.EndBoundedCombo(); + + EditorUI.current.widgets.EndBoundedCombo(); } finally { - ImGuiApi.PopID(); + ImGuiApi.PopId(); } } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.SourceSettings.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.SourceSettings.cs index ea8115590..9e711558a 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.SourceSettings.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.SourceSettings.cs @@ -1,17 +1,15 @@ -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.IO; using System.Linq; using Inno.Assets; using Inno.Assets.Pipeline; -using Inno.Native.ImGui; using Inno.Rendering.Assets; using EditorImGui = Inno.Editor.ImGui.ImGui; -using ImGuiApi = Inno.Native.ImGui.ImGui; using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void SourceSettings(Guid sourceId) @@ -20,39 +18,40 @@ private void SourceSettings(Guid sourceId) return; InspectorRow("source.actions", "Source", () => { - if (ImGuiApi.SmallButton("Show in File Browser") - && owner.interactions.TryGetModule(out var browser) && browser is not null) + if (EditorImGui.SmallButton("Show in File Browser") + && owner.interactions.TryGetModule(out var browser) + && browser is not null) RevealShaderFunction.Reveal(owner, browser, sourceId); - ImGuiApi.SameLine(0f, -1f); - if (ImGuiApi.SmallButton("Import Settings…")) + EditorImGui.SameLine(0f, -1f); + if (EditorImGui.SmallButton("Import Settings…")) { LoadSourceSettings(info.assetPath, sourceId); - ImGuiApi.OpenPopup("##source-settings"); + EditorImGui.OpenPopup("##source-settings"); } }); - ImGuiApi.SetNextWindowSize(new(580, 470), (int)(ImGuiCond.Appearing)); - if (!ImGuiApi.BeginPopup("##source-settings")) + EditorImGui.SetNextWindowSize(new(580, 470), (ImGuiCond.Appearing)); + if (!EditorImGui.BeginPopup("##source-settings")) return; try { - ImGuiApi.TextUnformatted(info.assetPath.ToString()); - ImGuiApi.Separator(); + EditorImGui.Text(info.assetPath.ToString()); + EditorImGui.Separator(); if (draft.settingsSource != sourceId) LoadSourceSettings(info.assetPath, sourceId); bool readOnly = !owner.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry) || entry.isReadOnly; - ImGuiApi.BeginDisabled(readOnly); + EditorImGui.BeginDisabled(readOnly); try { var settings = owner.serialization.Deserialize(draft.sourceSettings, owner.context); bool changed = false; InspectorRow("source.language", "Language", () => { - if (!Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##language", settings.languageId)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##language", settings.languageId)) return; try { foreach (string language in owner.frontends.languageIds) - if (ImGuiApi.Selectable(language, settings.languageId == language)) + if (EditorImGui.Selectable(language, settings.languageId == language)) { settings.languageId = language; changed = true; @@ -60,7 +59,7 @@ private void SourceSettings(Guid sourceId) } finally { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); string implementation = settings.implementationId; @@ -71,6 +70,7 @@ private void SourceSettings(Guid sourceId) settings.implementationId = implementation; changed = true; } + string catalog = settings.catalogPath; bool catalogChanged = false; InspectorRow("source.catalog", "Catalog Path", () => catalogChanged = EditorImGui.InputText("##catalog", ref catalog, 256)); @@ -79,100 +79,126 @@ private void SourceSettings(Guid sourceId) settings.catalogPath = catalog; changed = true; } + int catalogOrder = settings.catalogOrder; bool orderChanged = false; - InspectorRow("source.catalog-order", "Catalog Order", () => orderChanged = ImGuiApi.InputInt("##catalog-order", ref catalogOrder, 1, 100, 0)); + InspectorRow("source.catalog-order", "Catalog Order", () => orderChanged = EditorImGui.InputInt("##catalog-order", ref catalogOrder, 1, 100, 0)); if (orderChanged) { settings.catalogOrder = catalogOrder; changed = true; } - EditorWidget.WrappedText("Catalog paths are declared by the source library and group its exported functions in Shader creation tools. They do not affect compilation or runtime assets."); - ImGuiApi.SeparatorText("Exported Functions"); + + EditorUI.current.widgets.WrappedText("Catalog paths are declared by the source library and group its exported functions in Shader creation tools. They do not affect compilation or runtime assets."); + EditorImGui.SeparatorText("Exported Functions"); for (int i = 0; i < settings.exports.Length; i++) { - ImGuiApi.PushID("export." + i); - string function = settings.exports[i]; - bool functionChanged = false, remove = false; - InspectorRow("source.export", "Function " + (i + 1), () => - { - functionChanged = EditorImGui.InputText("##name", ref function, 256); - ImGuiApi.SameLine(0f, -1f); - remove = ImGuiApi.SmallButton("Remove"); - }); - if (functionChanged) + EditorImGui.PushId("export." + i); + try { - settings.exports[i] = function; - changed = true; + string function = settings.exports[i]; + bool functionChanged = false, remove = false; + InspectorRow("source.export", "Function " + (i + 1), () => + { + functionChanged = EditorImGui.InputText("##name", ref function, 256); + EditorImGui.SameLine(0f, -1f); + remove = EditorImGui.SmallButton("Remove"); + }); + if (functionChanged) + { + settings.exports[i] = function; + changed = true; + } + + if (remove) + { + settings.exports = settings.exports.Where(( + _, + index + ) => index != i).ToArray(); + changed = true; + break; + } } - if (remove) + finally { - settings.exports = settings.exports.Where(( - _, - index - ) => index != i).ToArray(); - changed = true; - ImGuiApi.PopID(); - break; + EditorImGui.PopId(); } - ImGuiApi.PopID(); } + if (CenteredAddButton("Add Export")) { - settings.exports = [.. settings.exports, "Function"]; + settings.exports = [..settings.exports, "Function"]; changed = true; } - EditorWidget.WrappedText("Every listed name is exported as an independent graph function. All other functions remain private helpers. Ports are parsed from each exported declaration; alternative implementations must expose matching interfaces."); - ImGuiApi.SeparatorText("Alternative Implementations"); + + EditorUI.current.widgets.WrappedText("Every listed name is exported as an independent graph function. All other functions remain private helpers. Ports are parsed from each exported declaration; alternative implementations must expose matching interfaces."); + EditorImGui.SeparatorText("Alternative Implementations"); for (int i = 0; i < settings.implementations.Length; i++) { - ImGuiApi.PushID(i); - ShaderFunctionAsset? current = settings.implementations[i]; - bool remove = false; - InspectorRow("source.implementation", "Implementation " + (i + 1), () => + EditorImGui.PushId(i); + try { - if (Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##implementation", current?.assetPath.ToString() ?? "Missing source")) + ShaderFunctionAsset? current = settings.implementations[i]; + bool remove = false; + InspectorRow("source.implementation", "Implementation " + (i + 1), () => { - try + if (EditorUI.current.widgets.BeginBoundedCombo("##implementation", current?.assetPath.ToString() ?? "Missing source")) { - foreach (AssetFileEntry source in owner.assets.GetFileSystemEntries(includeDirectories: false)) - if (source.extension == ".ishadersource" && source.assetPath != info.assetPath - && ImGuiApi.Selectable(source.assetPath.ToString(), current?.assetPath == source.assetPath) - && owner.assets.TryLoad(source.assetPath, out ShaderFunctionAsset? replacement) && replacement is not null) - { - settings.implementations[i] = replacement; - changed = true; - } - } - finally - { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + try + { + foreach (AssetFileEntry source in owner.assets.GetFileSystemEntries(includeDirectories: false)) + if (source.extension == ".ishadersource" + && source.assetPath != info.assetPath + && EditorImGui.Selectable( + source.assetPath.ToString(), + current?.assetPath == source.assetPath + ) + && owner.assets.TryLoad( + source.assetPath, + out ShaderFunctionAsset? replacement + ) + && replacement is not null) + { + settings.implementations[i] = replacement; + changed = true; + } + } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } } + + EditorImGui.SameLine(0f, -1f); + remove = EditorImGui.SmallButton("Remove"); + }); + if (remove) + { + settings.implementations = settings.implementations.Where(( + _, + index + ) => index != i).ToArray(); + changed = true; + break; } - ImGuiApi.SameLine(0f, -1f); - remove = ImGuiApi.SmallButton("Remove"); - }); - if (remove) + } + finally { - settings.implementations = settings.implementations.Where(( - _, - index - ) => index != i).ToArray(); - changed = true; - ImGuiApi.PopID(); - break; + EditorImGui.PopId(); } - ImGuiApi.PopID(); } + if (CenteredAddButton("Add Implementation")) { - settings.implementations = [.. settings.implementations, null!]; + settings.implementations = [..settings.implementations, null!]; changed = true; } + if (changed) draft.sourceSettings = owner.serialization.Serialize(settings, owner.context); - ImGuiApi.Separator(); - if (ImGuiApi.Button("Apply Import Settings")) + EditorImGui.Separator(); + if (EditorImGui.Button("Apply Import Settings")) { bool imported = owner.importSettings.Apply(info.assetPath, settings, draft.sourceSettingsFingerprint); LoadSourceSettings(info.assetPath, sourceId); @@ -182,24 +208,26 @@ private void SourceSettings(Guid sourceId) } finally { - ImGuiApi.EndDisabled(); + EditorImGui.EndDisabled(); } - ImGuiApi.SameLine(0f, -1f); - if (ImGuiApi.Button("Reload")) + + EditorImGui.SameLine(0f, -1f); + if (EditorImGui.Button("Reload")) LoadSourceSettings(info.assetPath, sourceId); if (readOnly) - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Installed source settings are read-only."); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Installed source settings are read-only."); if (draft.sourceSettingsStatus.Length != 0) - EditorWidget.WrappedText(draft.sourceSettingsStatus); + EditorUI.current.widgets.WrappedText(draft.sourceSettingsStatus); } - catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.sourceSettingsStatus = failure.Message; - EditorWidget.WrappedText(draft.sourceSettingsStatus); + EditorUI.current.widgets.WrappedText(draft.sourceSettingsStatus); } finally { - ImGuiApi.EndPopup(); + EditorImGui.EndPopup(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Target.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Target.cs index fee269c56..90888483c 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Target.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Target.cs @@ -1,10 +1,9 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using Inno.Core.Graphs; using Inno.Rendering.Shaders; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private void DrawTarget() @@ -15,7 +14,7 @@ private void DrawTarget() { InspectorRow("target", "Target", () => { - if (!Widget.BeginBoundedCombo("##target", current.Length == 0 ? "Explicit Stages" : current)) + if (!EditorUI.current.widgets.BeginBoundedCombo("##target", current.Length == 0 ? "Explicit Stages" : current)) return; try { @@ -28,7 +27,7 @@ private void DrawTarget() } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } }); } @@ -36,6 +35,7 @@ private void DrawTarget() { ImGuiApi.EndDisabled(); } + void Assign(string id) { if (id == current) diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Techniques.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Techniques.cs index a0a21d3b9..1d3d1d50b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Techniques.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.Techniques.cs @@ -1,143 +1,189 @@ -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Linq; using Inno.Rendering; using EditorImGui = Inno.Editor.ImGui.ImGui; -using ImGuiApi = Inno.Native.ImGui.ImGui; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - internal sealed partial class ShaderEditorCanvas { private bool TechniqueControls(ShaderDefinition definition) { - if (!ImGuiApi.CollapsingHeader("Techniques & Roles")) + if (!EditorImGui.CollapsingHeader("Techniques & Roles")) return false; - EditorWidget.WrappedText("Techniques map pipeline-defined roles to this shader's passes. Contract and role identities are supplied by the rendering pipeline."); + EditorUI.current.widgets.WrappedText("Techniques map pipeline-defined roles to this shader's passes. Contract and role identities are supplied by the rendering pipeline."); bool changed = false; for (int i = 0; i < definition.techniques.Length; i++) { - ImGuiApi.PushID("technique-" + i); - ShaderTechniqueDefinition technique = definition.techniques[i]; - string id = technique.id.value ?? "", contract = technique.contract.value ?? ""; - bool idChanged = false; - InspectorRow("technique.id", "ID", () => idChanged = EditorImGui.InputText("##id", ref id, 128)); - if (idChanged) - { - technique.id = new() { value = id }; - changed = true; - } - Gesture(); - bool contractChanged = false; - InspectorRow("technique.contract", "Contract", () => contractChanged = EditorImGui.InputText("##contract", ref contract, 256)); - if (contractChanged) - { - technique.contract = new() { value = contract }; - changed = true; - } - Gesture(); - GraphicsCapability features = technique.requiredFeatures; - if (FeatureControls(ref features)) - { - technique.requiredFeatures = features; - changed = true; - } - for (int j = 0; j < technique.passes.Length; j++) + EditorImGui.PushId("technique-" + i); + try { - ImGuiApi.PushID(j); - ShaderTechniquePass mapping = technique.passes[j]; - string role = mapping.role.value ?? ""; - bool roleChanged = false; - InspectorRow("technique.role", "Role", () => roleChanged = EditorImGui.InputText("##role", ref role, 128)); - if (roleChanged) + ShaderTechniqueDefinition technique = definition.techniques[i]; + string id = technique.id.value ?? "", contract = technique.contract.value ?? ""; + bool idChanged = false; + InspectorRow("technique.id", "ID", () => idChanged = EditorImGui.InputText("##id", ref id, 128)); + if (idChanged) + { + technique.id = new() + { + value = id + }; + changed = true; + } + + Gesture(); + bool contractChanged = false; + InspectorRow("technique.contract", "Contract", () => contractChanged = EditorImGui.InputText("##contract", ref contract, 256)); + if (contractChanged) { - mapping.role = new() { value = role }; + technique.contract = new() + { + value = contract + }; changed = true; } + Gesture(); - bool passChanged = false; - InspectorRow("technique.pass", "Pass", () => + GraphicsCapability features = technique.requiredFeatures; + if (FeatureControls(ref features)) + { + technique.requiredFeatures = features; + changed = true; + } + + for (int j = 0; j < technique.passes.Length; j++) { - if (!Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.BeginBoundedCombo("##pass", mapping.passName)) - return; + EditorImGui.PushId(j); try { - foreach (ShaderPassDefinition pass in definition.passes) - if (ImGuiApi.Selectable(pass.name, pass.name == mapping.passName)) + ShaderTechniquePass mapping = technique.passes[j]; + string role = mapping.role.value ?? ""; + bool roleChanged = false; + InspectorRow("technique.role", "Role", () => roleChanged = EditorImGui.InputText("##role", ref role, 128)); + if (roleChanged) + { + mapping.role = new() + { + value = role + }; + changed = true; + } + + Gesture(); + bool passChanged = false; + InspectorRow("technique.pass", "Pass", () => + { + if (!EditorUI.current.widgets.BeginBoundedCombo("##pass", mapping.passName)) + return; + try { - mapping.passName = pass.name; - passChanged = true; + foreach (ShaderPassDefinition pass in definition.passes) + if (EditorImGui.Selectable(pass.name, pass.name == mapping.passName)) + { + mapping.passName = pass.name; + passChanged = true; + } } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } + }); + changed |= passChanged; + technique.passes[j] = mapping; + if (EditorImGui.SmallButton("Remove Role")) + { + technique.passes = technique.passes.Where(( + _, + index + ) => index != j).ToArray(); + changed = true; + break; + } } finally { - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.EndBoundedCombo(); + EditorImGui.PopId(); + } + } + + if (CenteredAddButton("Add Role")) + { + technique.passes = [..technique.passes, new() + { + role = new("role-" + technique.passes.Length), + passName = definition.passes.FirstOrDefault().name ?? "" } - }); - changed |= passChanged; - technique.passes[j] = mapping; - if (ImGuiApi.SmallButton("Remove Role")) + + ]; + changed = true; + } + + definition.techniques[i] = technique; + if (EditorImGui.SmallButton("Remove Technique")) { - technique.passes = technique.passes.Where(( + definition.techniques = definition.techniques.Where(( _, index - ) => index != j).ToArray(); + ) => index != i).ToArray(); changed = true; - ImGuiApi.PopID(); break; } - ImGuiApi.PopID(); - } - if (CenteredAddButton("Add Role")) - { - technique.passes = [.. technique.passes, new() { role = new("role-" + technique.passes.Length), passName = definition.passes.FirstOrDefault().name ?? "" }]; - changed = true; + + EditorImGui.Separator(); } - definition.techniques[i] = technique; - if (ImGuiApi.SmallButton("Remove Technique")) + finally { - definition.techniques = definition.techniques.Where(( - _, - index - ) => index != i).ToArray(); - changed = true; - ImGuiApi.PopID(); - break; + EditorImGui.PopId(); } - ImGuiApi.Separator(); - ImGuiApi.PopID(); } + if (CenteredAddButton("Add Technique")) { - definition.techniques = [.. definition.techniques, new() { id = new("technique-" + Guid.NewGuid().ToString("N")), contract = default, passes = [] }]; + definition.techniques = [..definition.techniques, new() + { + id = new("technique-" + Guid.NewGuid().ToString("N")), + contract = default, + passes = [] + } + + ]; changed = true; } + return changed; } private static bool FeatureControls(ref GraphicsCapability features) { - if (!ImGuiApi.TreeNodeEx("Required Capabilities", 0)) + if (!EditorImGui.TreeNode("Required Capabilities", 0)) return false; - bool changed = false; - GraphicsCapability updated = features; - foreach (GraphicsCapability feature in Enum.GetValues()) + try { - ulong bits = Convert.ToUInt64(feature); - if (bits == 0 || (bits & (bits - 1)) != 0) - continue; - bool enabled = (updated & feature) != 0; - bool featureChanged = false; - InspectorRow("feature." + feature, feature.ToString(), () => featureChanged = ImGuiApi.Checkbox("##enabled", ref enabled)); - if (featureChanged) + bool changed = false; + GraphicsCapability updated = features; + foreach (GraphicsCapability feature in Enum.GetValues()) { - updated = enabled ? updated | feature : updated & ~feature; - changed = true; + ulong bits = Convert.ToUInt64(feature); + if (bits == 0 || (bits & (bits - 1)) != 0) + continue; + bool enabled = (updated & feature) != 0; + bool featureChanged = false; + InspectorRow("feature." + feature, feature.ToString(), () => featureChanged = EditorImGui.Checkbox("##enabled", ref enabled)); + if (featureChanged) + { + updated = enabled ? updated | feature : updated & ~feature; + changed = true; + } } + + features = updated; + return changed; + } + finally + { + EditorImGui.TreePop(); } - features = updated; - ImGuiApi.TreePop(); - return changed; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.cs index 9e63f7ee0..2cd28d719 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Canvas/ShaderEditorCanvas.cs @@ -1,7 +1,6 @@ -using System.Runtime.CompilerServices; -using NumericsVector2 = System.Numerics.Vector2; -using NativeVector2 = Inno.Native.ImGui.ImVec2C; -using BGCS.Runtime; +using Inno.Editor.Core; +using Inno.Editor.Graph.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Linq; @@ -13,15 +12,13 @@ using Inno.Editor.Interactions; using Inno.Editor.Rendering; using Inno.Editor.Shaders; -using Inno.Native.ImGui; +using Inno.Editor.Shaders.ImGui; using Inno.Rendering.Assets; using Inno.Rendering.Shaders; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.ShaderEditor; - -internal sealed unsafe partial class ShaderEditorCanvas( +internal sealed partial class ShaderEditorCanvas( ShaderEditorDocuments owner, ShaderEditorDocuments.Draft draft ) { @@ -29,8 +26,8 @@ private readonly record struct PortHandle( GraphEndpoint endpoint, GraphPortDirection direction ); - internal const string C_AREA = "panel/rendering.shader-editor"; + private static readonly Vector4 C_EMPTY_GROUP_HEADER = new(0.16f, 0.17f, 0.20f, EditorPalette.opacityOpaque); private const string C_NODE_WIDTH = "editor.width"; private const float C_DEFAULT_WIDTH = 270f; private const float C_MINIMUM_WIDTH = 180f; @@ -40,28 +37,31 @@ GraphPortDirection direction private const float C_RESIZE_HANDLE = 14f; private GraphDocumentController Controller => owner.Controller(draft); private GraphCanvasState Canvas => draft.canvas; + private GraphCanvasView View => new(Canvas, m_origin, m_size); + private Vector2 m_origin; private Vector2 m_size; - internal void Draw() { DrawHeader(owner, draft); - m_origin = ImGuiApi.GetCursorScreenPos(); - m_size = Vector2.Max(ImGuiApi.GetContentRegionAvail(), Vector2.One); + m_origin = ImGuiApi.GetCursorScreenPosition(); + m_size = Vector2.Max(ImGuiApi.GetContentRegionAvailable(), Vector2.One); RefreshPorts(); + RefreshGeometry(); owner.RefreshCompilation(draft); if (draft.frameRequested) { + owner.CompleteCanvasEdit(draft); Frame(); draft.frameRequested = false; } - Dictionary points = PortPositions(); + ImGuiApi.SetNextItemAllowOverlap(); - _ = ImGuiApi.InvisibleButton("##shader-canvas", m_size, (int)(ImGuiButtonFlags.MouseButtonLeft | ImGuiButtonFlags.MouseButtonMiddle | ImGuiButtonFlags.MouseButtonRight)); - bool hovered = ImGuiApi.IsItemHovered((int)ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); + _ = ImGuiApi.InvisibleButton("##shader-canvas", m_size, (ImGuiButtonFlags.MouseButtonLeft | ImGuiButtonFlags.MouseButtonMiddle | ImGuiButtonFlags.MouseButtonRight)); + bool hovered = ImGuiApi.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); Vector2 mouse = ImGuiApi.GetMousePos(); bool miniMapHovered = MiniMapContains(mouse); - if (hovered && !miniMapHovered && ImGuiApi.IsMouseClicked((int)ImGuiMouseButton.Right)) + if (hovered && !miniMapHovered && ImGuiApi.IsMouseClicked(ImGuiMouseButton.Right)) { draft.menuPosition = ToGraph(mouse); draft.createFromPort = null; @@ -71,100 +71,65 @@ internal void Draw() draft.selectedGroupId = group.id; Canvas.SelectNodes(group.nodes.Select(static id => new GraphNodeId(id))); } + GraphNodeRecord? hit = hitGroup is null ? HitNode(mouse) : null; - draft.selectedEdge = hit is null ? HitEdge(mouse, points) : null; + draft.selectedEdge = hit is null ? HitEdge(mouse) : null; if (draft.selectedEdge is not null) Canvas.ClearSelection(); if (hit is not null && !Canvas.selectedNodes.Contains(hit.id)) Canvas.SelectNodes([hit.id]); SetStage(hit); } + if (owner.assets.TryGetFileSystemEntry(draft.path, out AssetFileEntry entry)) { EditorInteraction interaction = owner.interactions.For(C_AREA, entry); - if (ImGuiApi.IsWindowFocused((int)ImGuiFocusedFlags.RootAndChildWindows)) + if (ImGuiApi.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)) interaction.Focus(); - _ = EditorMenuRenderer.ContextMenu("##shader-menu", interaction); + _ = EditorUI.current.menus.ContextMenu("##shader-menu", interaction); } + Navigate(hovered && !miniMapHovered); - ImDrawList* draw = ImGuiApi.Raw.GetWindowDrawList(); - PushNativeClipRectangle( - draw, - Unsafe.BitCast(m_origin), - Unsafe.BitCast(m_origin + m_size), - (true ? (byte)1 : (byte)0) - ); + EditorDrawList draw = ImGuiApi.GetDrawList(); + draw.PushClip(m_origin, m_origin + m_size, true); try { - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(m_origin), - Unsafe.BitCast(m_origin + m_size), - Color(0.065f, 0.071f, 0.083f), - 0, - 0 - ); + draw.FilledRectangle(m_origin, m_origin + m_size, Color(0.065f, 0.071f, 0.083f), 0, false); Grid(draw); Groups(draw); - Edges(draw, points); + Edges(draw); foreach (GraphNodeRecord node in Controller.document.nodes) - Node(draw, node, points); - Pointer(hovered && !miniMapHovered, points); - if (draft.boxSelecting) + Node(draw, node, mouse); + Pointer(hovered && !miniMapHovered); + if (draft.pointer.isBoxSelecting) { - Vector2 min = Vector2.Min(draft.pointerStart, mouse), max = Vector2.Max(draft.pointerStart, mouse); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(min), - Unsafe.BitCast(max), - Color(0.52f, 0.37f, 0.78f, EditorPalette.opacitySubtle), - 0, - 0 - ); - DrawNativeRectangle( - draw, - Unsafe.BitCast(min), - Unsafe.BitCast(max), - Color(0.65f, 0.47f, 0.88f), - 0, - 0, - 1f - ); + Vector2 min = Vector2.Min(draft.pointer.boxStart, mouse), max = Vector2.Max(draft.pointer.boxStart, mouse); + draw.FilledRectangle(min, max, Color(0.52f, 0.37f, 0.78f, EditorPalette.opacitySubtle), 0, false); + draw.Rectangle(min, max, Color(0.65f, 0.47f, 0.88f), 0, 1f); } + string status = draft.readOnly ? "Read-only · copy to project to edit" : Controller.isDirty ? "Unsaved changes · Save to apply" : draft.status; status += " · Saved asset: " + draft.compilationStatus; - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(status, allowsNull: false)) { - DrawNativeText( - draw, - Unsafe.BitCast(m_origin + new Vector2(12, 10)), - ImGuiApi.GetColorU32((int)ImGuiCol.TextDisabled), - (sbyte*)nativeText.pointer, - null - ); + draw.Text(m_origin + new Vector2(12, 10), ImGuiApi.GetStyleColor(ImGuiCol.TextDisabled), status, 1f); } + if (draft.compilationDiagnostics.Length != 0 && mouse.Y < m_origin.Y + 32 && hovered) - Widget.DrawTooltip(draft.compilationDiagnostics); + EditorUI.current.widgets.DrawTooltip(draft.compilationDiagnostics); if (draft.error.Length != 0) - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(draft.error, allowsNull: false)) - { - DrawNativeText( - draw, - Unsafe.BitCast(m_origin + new Vector2(12, 34)), - Color(1f, 0.47f, 0.44f), - (sbyte*)nativeText.pointer, - null - ); - } + { + draw.Text(m_origin + new Vector2(12, 34), Color(1f, 0.47f, 0.44f), draft.error, 1f); + } } finally { - ImGuiApi.Raw.PopClipRect(draw); + draw.PopClip(); } + DrawMiniMap(); // Re-submit the canvas footprint after absolutely positioned node controls. A cursor // move alone can exceed ImGui's pixel-rounded item bounds at fractional UI scales. - ImGuiApi.SetCursorScreenPos(m_origin); + ImGuiApi.SetCursorScreenPosition(m_origin); ImGuiApi.Dummy(m_size); SynchronizeInspection(); } @@ -174,7 +139,8 @@ private void SynchronizeInspection() if (Canvas.selectedNodes.SequenceEqual(draft.inspectedNodes)) return; draft.inspectedNodes = Canvas.selectedNodes.ToArray(); - owner.interactions.SetSelection(new ShaderInspectionSelection(draft.id, draft.inspectedNodes)); + draft.inspectionSelection = new ShaderInspectionSelection(draft.id, draft.inspectedNodes); + owner.interactions.SetSelection(draft.inspectionSelection); } internal void DrawInspector( @@ -198,47 +164,61 @@ private void DrawInspectorContents(IReadOnlyList selected) bool graphNode = ShaderGraphNodes.IsNodeGraph(Controller.document); if (!graphNode) { - if (Widget.SectionHeader("Preview", "The preview uses the current draft without publishing it to Scene or Game views.")) + if (EditorUI.current.widgets.SectionHeader("Preview", "The preview uses the current draft without publishing it to Scene or Game views.")) { var preview = owner.Preview(draft); if (preview is null) { - Widget.Hint("Run Check to build a preview for the current draft."); + EditorUI.current.widgets.Hint("Run Check to build a preview for the current draft."); } else if (preview.state == EditorShaderCompilationState.Failed) { - Widget.Hint("Shader check failed. See Console."); + EditorUI.current.widgets.Hint("Shader check failed. See Console."); } - else if (preview.state == EditorShaderCompilationState.Succeeded && preview.artifact is not null - && owner.interactions.TryGetModule(out var images) && images is not null) + else if (preview.state == EditorShaderCompilationState.Succeeded + && preview.artifact is not null + && owner.interactions.TryGetModule(out var images) + && images is not null) { var shader = owner.assets.Load(draft.path); - images.Draw(draft.id, new Inno.Rendering.Assets.MaterialAsset { shader = shader }, preview, - MathF.Max(1f, MathF.Min(256f, ImGuiApi.GetContentRegionAvail().X))); + images.Draw( + draft.id, + new Inno.Rendering.Assets.MaterialAsset { shader = shader }, + preview, + MathF.Max(1f, MathF.Min(256f, ImGuiApi.GetContentRegionAvailable().X)) + ); } } } else { - if (Widget.SectionHeader("Graph Node", "Function Inputs and Function Outputs independently define the reusable typed interface. Check validates the interface; callers validate the inlined computation.")) + if (EditorUI.current.widgets.SectionHeader( + "Graph Node", + "Function Inputs and Function Outputs independently define the reusable typed interface. Check validates the interface; callers validate the inlined computation." + )) { try { ShaderGraphNodeInterface nodeInterface = ShaderGraphNodes.ReadInterface(Controller.document, owner.serialization, owner.context); - InspectorRow("graph-node.summary", "Interface", () => Widget.WrappedText( - $"{nodeInterface.inputs.Length} input(s) · {nodeInterface.outputs.Length} output(s) · {nodeInterface.kind}")); + InspectorRow( + "graph-node.summary", + "Interface", + () => EditorUI.current.widgets.WrappedText($"{nodeInterface.inputs.Length} input(s) · {nodeInterface.outputs.Length} output(s) · {nodeInterface.kind}") + ); } catch (Exception failure) when (failure is InvalidOperationException or ArgumentException or FormatException or NotSupportedException) { - Widget.Hint(failure.Message); + EditorUI.current.widgets.Hint(failure.Message); } } } + if (selected.Count == 0) { - if (Widget.SectionHeader(graphNode ? "Node Definition" : "Shader", - graphNode ? "Select Function Inputs or Function Outputs to edit this reusable node's public interface." - : "Target and public interface belong to this Shader, not to any Material override.")) + if (EditorUI.current.widgets.SectionHeader( + graphNode ? "Node Definition" : "Shader", + graphNode ? "Select Function Inputs or Function Outputs to edit this reusable node's public interface." : "Target and public interface belong to this Shader, not to any Material override." + )) { if (!graphNode) DrawTarget(); @@ -246,38 +226,53 @@ private void DrawInspectorContents(IReadOnlyList selected) { var definition = ShaderGraphDocument.ReadDefinition(Controller.document, owner.serialization, owner.context); foreach (var property in definition.properties) - InspectorRow("definition." + property.id.value, property.displayName, - () => Widget.WrappedText(property.type + " · " + property.bindingOwner)); + InspectorRow( + "definition." + property.id.value, + property.displayName, + () => EditorUI.current.widgets.WrappedText(property.type + " · " + property.bindingOwner) + ); } + owner.RefreshCompilation(draft); - InspectorRow("compilation", "Compilation", () => Widget.WrappedText(draft.compilationStatus)); + InspectorRow("compilation", "Compilation", () => EditorUI.current.widgets.WrappedText(draft.compilationStatus)); } + return; } + GraphNodeRecord[] nodes = selected.Select(Controller.document.FindNode).OfType().ToArray(); if (nodes.Length != selected.Count) - Widget.Hint("Some selected nodes are unavailable. Stable identities are retained."); + EditorUI.current.widgets.Hint("Some selected nodes are unavailable. Stable identities are retained."); if (nodes.Length == 0) return; if (nodes.Any(node => node.definitionId != nodes[0].definitionId)) { - Widget.Hint("Select nodes of the same kind to edit common settings."); + EditorUI.current.widgets.Hint("Select nodes of the same kind to edit common settings."); return; } + m_inspectionNodes = nodes; - ImGuiApi.PushID(draft.id.ToString("N")); - ImGuiApi.PushID(nodes[0].id.value); + ImGuiApi.PushId(draft.id.ToString("N")); + ImGuiApi.PushId(nodes[0].id.value); ImGuiApi.BeginDisabled(draft.readOnly); try { - if (Widget.SectionHeader(Title(nodes[0]), "Inputs, defaults and output configuration are edited here. Changes remain in the Shader draft until Save.")) + if (EditorUI.current.widgets.SectionHeader( + Title(nodes[0]), + "Inputs, defaults and output configuration are edited here. Changes remain in the Shader draft until Save." + )) { if (nodes.Length > 1) - Widget.Hint("Editing " + nodes.Length + " nodes · differing values are replaced together"); + EditorUI.current.widgets.Hint("Editing " + nodes.Length + " nodes · differing values are replaced together"); Controls(nodes[0]); } + ShaderNodePort[] inputs = draft.ports[nodes[0].id].Where(port => port.direction == GraphPortDirection.Input).ToArray(); - if (inputs.Length != 0 && Widget.SectionHeader("Inputs", "Connected inputs show their source. Optional inputs use an automatic zero; required inputs must be connected in the graph.")) + if (inputs.Length != 0 + && EditorUI.current.widgets.SectionHeader( + "Inputs", + "Connected inputs show their source. Optional inputs use an automatic zero; required inputs must be connected in the graph." + )) { foreach (ShaderNodePort port in inputs) DrawInputDefault(nodes[0], port); @@ -286,39 +281,36 @@ private void DrawInspectorContents(IReadOnlyList selected) finally { ImGuiApi.EndDisabled(); - ImGuiApi.PopID(); - ImGuiApi.PopID(); + ImGuiApi.PopId(); + ImGuiApi.PopId(); m_inspectionNodes = null; } } private GraphNodeRecord[]? m_inspectionNodes; private Inno.Editor.Inspection.InspectionDrawContext? m_inspection; - private static void InspectorRow( string id, string label, Action draw - ) => Widget.PropertyRow("shader." + id, label, draw); - + ) => EditorUI.current.widgets.PropertyRow("shader." + id, label, draw); internal static void DrawHeader( ShaderEditorDocuments owner, ShaderEditorDocuments.Draft? draft ) { ArgumentNullException.ThrowIfNull(owner); - Widget.HeaderSurface("##shader-header", () => + EditorUI.current.widgets.HeaderSurface("##shader-header", () => { ImGuiApi.SetNextItemWidth(-1f); - string title = draft is null - ? "Select Shader" - : System.IO.Path.GetFileName(draft.path.localPath) + (owner.Controller(draft).isDirty ? " *" : ""); - if (Widget.BeginBoundedCombo("##shader-header-select", title)) + string title = draft is null ? "Select Shader" : System.IO.Path.GetFileName(draft.path.localPath) + (owner.Controller(draft).isDirty ? " *" : ""); + if (EditorUI.current.widgets.BeginBoundedCombo("##shader-header-select", title)) { try { - foreach (AssetFileEntry candidate in owner.assets.GetFileSystemEntries(includeDirectories: false) - .Where(static candidate => candidate.extension.Equals(".ishader", StringComparison.OrdinalIgnoreCase)) - .OrderBy(static candidate => candidate.assetPath.ToString(), StringComparer.Ordinal)) + foreach (AssetFileEntry candidate in owner.assets.GetFileSystemEntries(includeDirectories: false).Where(static candidate => candidate.extension.Equals(".ishader", StringComparison.OrdinalIgnoreCase)).OrderBy( + static candidate => candidate.assetPath.ToString(), + StringComparer.Ordinal + )) { bool selected = draft is not null && candidate.assetPath == draft.path; if (ImGuiApi.Selectable(candidate.assetPath.ToString(), selected)) @@ -326,34 +318,36 @@ internal static void DrawHeader( owner.interactions.SetSelection(candidate); _ = owner.Open(candidate); } + if (selected) ImGuiApi.SetItemDefaultFocus(); } } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } + bool noDocument = draft is null; ImGuiApi.BeginDisabled(noDocument || draft!.readOnly); if (ImGuiApi.Button("Save") && draft is not null && owner.assets.TryGetFileSystemEntry(draft.path, out AssetFileEntry entry)) _ = owner.interactions.For(C_AREA, entry).Execute("shader/save"); - Widget.DrawItemTooltip("Save this shader and apply its changes (Command/Ctrl + S). Invalid graphs can be saved; rendering retains the last successful programs."); + EditorUI.current.widgets.DrawItemTooltip("Save this shader and apply its changes (Command/Ctrl + S). Invalid graphs can be saved; rendering retains the last successful programs."); ImGuiApi.SameLine(0f, -1f); if (ImGuiApi.Button("Revert") && draft is not null) _ = owner.interactions.documents.Revert(draft.documentId); - Widget.DrawItemTooltip("Restore the saved shader. This draft change can be undone."); + EditorUI.current.widgets.DrawItemTooltip("Restore the saved shader. This draft change can be undone."); ImGuiApi.EndDisabled(); ImGuiApi.SameLine(0f, -1f); ImGuiApi.BeginDisabled(noDocument); if (ImGuiApi.Button("Format") && draft is not null && owner.assets.TryGetFileSystemEntry(draft.path, out AssetFileEntry formatEntry)) _ = owner.interactions.For(C_AREA, formatEntry).Execute("shader/format"); - Widget.DrawItemTooltip("Arrange the graph from inputs on the left to outputs on the right. This changes only authoring positions and is undoable."); + EditorUI.current.widgets.DrawItemTooltip("Arrange the graph from inputs on the left to outputs on the right. This changes only authoring positions and is undoable."); ImGuiApi.SameLine(0f, -1f); if (ImGuiApi.Button("Check") && draft is not null && owner.assets.TryGetFileSystemEntry(draft.path, out AssetFileEntry checkEntry)) _ = owner.interactions.For(C_AREA, checkEntry).Execute("shader/check"); - Widget.DrawItemTooltip("Compile-check the current draft without saving or publishing it, and show diagnostics with source locations."); + EditorUI.current.widgets.DrawItemTooltip("Compile-check the current draft without saving or publishing it, and show diagnostics with source locations."); ImGuiApi.EndDisabled(); }, spanWindowPadding: true); } @@ -375,23 +369,24 @@ private void RefreshPorts() if (node.definitionId == ShaderGraphDocument.outputDefinitionId) { ShaderGraphStageSettings settings = Read(node, "settings", new ShaderGraphStageSettings()); - ports.AddRange(settings.outputs.Select(static output => new ShaderNodePort(output.id, - ShaderSourceType.Atomic(output.kind == ShaderIrOutputKind.Depth ? "float" : output.kind == ShaderIrOutputKind.Varying ? "any" : "float4"), - GraphPortDirection.Input))); + ports.AddRange(settings.outputs.Select(static output => new ShaderNodePort(output.id, ShaderSourceType.Atomic(output.kind == ShaderIrOutputKind.Depth ? "float" : output.kind == ShaderIrOutputKind.Varying ? "any" : "float4"), GraphPortDirection.Input))); } else ports.AddRange(owner.Describe(node)); } - catch (Exception failure) when ((failure is InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.nodeErrors[node.id] = failure.Message; } + foreach (ShaderPortSnapshot old in draft.portSnapshots[node.id]) if (!ports.Any(port => port.id == old.id)) { ports.Add(new(old.id, old.type.CreateType(), old.direction, old.required)); draft.missingPorts.Add(new(node.id, new(old.id))); } + foreach (GraphEdgeRecord edge in Controller.document.edges) { if (edge.input.nodeId == node.id) @@ -399,8 +394,8 @@ private void RefreshPorts() if (edge.output.nodeId == node.id) KeepEndpoint(edge.output, GraphPortDirection.Output); } - draft.portSnapshots[node.id] = ports.Select(static port => new ShaderPortSnapshot - { id = port.id, type = ShaderGraphType.Capture(port.type), direction = port.direction, required = port.required }).ToArray(); + + draft.portSnapshots[node.id] = ports.Select(static port => new ShaderPortSnapshot { id = port.id, type = ShaderGraphType.Capture(port.type), direction = port.direction, required = port.required }).ToArray(); draft.ports.Add(node.id, ports.ToArray()); void KeepEndpoint( GraphEndpoint endpoint, @@ -412,6 +407,7 @@ GraphPortDirection direction draft.missingPorts.Add(endpoint); } } + draft.portRevision = Controller.revision; draft.groups = GroupShaderNodes.Read(owner, Controller.document); draft.assetRevision = owner.assets.revision; @@ -421,69 +417,82 @@ GraphPortDirection direction private void Navigate(bool hovered) { - ImGuiIO* io = ImGuiApi.Raw.GetIO(); - draft.navigation.Update(hovered, ImGuiApi.IsMouseClicked((int)ImGuiMouseButton.Left), ImGuiApi.IsMouseClicked((int)ImGuiMouseButton.Middle), - ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Left), ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Middle), io->KeyAlt != 0); + if (ImGuiApi.IsAnyPopupOpen()) + { + owner.CompleteCanvasEdit(draft); + draft.navigation.Cancel(); + return; + } + EditorUiInput io = ImGuiApi.GetInput(); + draft.navigation.Update( + hovered, + ImGuiApi.IsMouseClicked(ImGuiMouseButton.Left), + ImGuiApi.IsMouseClicked(ImGuiMouseButton.Middle), + ImGuiApi.IsMouseButtonDown(ImGuiMouseButton.Left), + ImGuiApi.IsMouseButtonDown(ImGuiMouseButton.Middle), + io.alt + ); if (draft.navigation.isPanning) { - Canvas.PanBy(io->MouseDelta.X, io->MouseDelta.Y); - ImGuiApi.SetMouseCursor((int)ImGuiMouseCursor.ResizeAll); + Canvas.PanBy(io.mouseDelta.X, io.mouseDelta.Y); + ImGuiApi.SetMouseCursor(ImGuiMouseCursor.ResizeAll); } - if (hovered && io->MouseWheel != 0) + + if (hovered && io.wheel != 0) { + owner.CompleteCanvasEdit(draft); Vector2 pivot = ImGuiApi.GetMousePos() - m_origin; - Canvas.ZoomAt(EditorPlanarNavigation.WheelFactor(io->MouseWheel), pivot.X, pivot.Y); + Canvas.ZoomAt(EditorPlanarNavigation.WheelFactor(io.wheel), pivot.X, pivot.Y); } - if (hovered && io->WantTextInput == 0 && ImGuiApi.IsKeyPressed(ImGuiKey.F, false)) + + if (hovered && !io.wantsText && ImGuiApi.IsKeyPressed(ImGuiKey.F, false)) + { + owner.CompleteCanvasEdit(draft); Frame(); - if (io->WantTextInput == 0 && ImGuiApi.IsKeyPressed(ImGuiKey.Escape, false)) + } + if (!io.wantsText && ImGuiApi.IsKeyPressed(ImGuiKey.Escape, false)) { - draft.dragging = draft.boxSelecting = false; - draft.resizingNode = null; + ShaderEditorDocuments.CancelCanvasEdit(draft); draft.navigation.Cancel(); - draft.dragPreview.Clear(); - Canvas.CancelConnection(); draft.createFromPort = null; + RefreshGeometry(); } } - private void Pointer( - bool hovered, - Dictionary points - ) { - if (draft.navigation.isPanning || ImGuiApi.Raw.GetIO()->KeyAlt != 0) + private void Pointer(bool hovered) + { + if (ImGuiApi.IsAnyPopupOpen()) + return; + if (draft.navigation.isPanning || ImGuiApi.GetInput().alt) + { + owner.CompleteCanvasEdit(draft); + return; + } + if (draft.pendingMove is not null) + { + owner.CompleteCanvasEdit(draft); return; + } Vector2 mouse = ImGuiApi.GetMousePos(); if (draft.resizingNode is GraphNodeId resizing) { - ImGuiApi.SetMouseCursor((int)ImGuiMouseCursor.ResizeNwse); - if (ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Left)) + ImGuiApi.SetMouseCursor(ImGuiMouseCursor.ResizeNwse); + if (ImGuiApi.IsMouseButtonDown(ImGuiMouseButton.Left)) { - draft.resizePreviewWidth = Math.Clamp( - draft.resizeStartWidth + (mouse.X - draft.pointerStart.X) / Canvas.zoom, - C_MINIMUM_WIDTH, - C_MAXIMUM_WIDTH); + draft.resizePreviewWidth = Math.Clamp(draft.resizeStartWidth + (mouse.X - draft.resizePointerStart.X) / Canvas.zoom, C_MINIMUM_WIDTH, C_MAXIMUM_WIDTH); } else { - GraphNodeRecord? resizedNode = Controller.document.FindNode(resizing); - if (resizedNode is not null && MathF.Abs(NodeWidth(resizedNode, includePreview: false) - draft.resizePreviewWidth) > 0.01f) - { - GraphDocument resizedGraph = Controller.document.Clone(); - resizedGraph.FindNode(resizing)!.SetValue( - C_NODE_WIDTH, - ShaderGraphDocument.Encode(draft.resizePreviewWidth, owner.serialization, owner.context)); - Controller.ReplaceDocument(resizedGraph, "Resize Shader Node"); - owner.Changed(draft); - } - draft.resizingNode = null; + owner.CompleteCanvasEdit(draft); } + return; } + GraphNodeRecord? resizeTarget = !draft.readOnly && hovered ? HitResizeHandle(mouse) : null; if (resizeTarget is not null) - ImGuiApi.SetMouseCursor((int)ImGuiMouseCursor.ResizeNwse); - if (hovered && ImGuiApi.IsMouseClicked((int)ImGuiMouseButton.Left)) + ImGuiApi.SetMouseCursor(ImGuiMouseCursor.ResizeNwse); + if (hovered && ImGuiApi.IsMouseClicked(ImGuiMouseButton.Left)) { if (resizeTarget is not null) { @@ -493,325 +502,141 @@ Dictionary points draft.resizingNode = resizeTarget.id; draft.resizeStartWidth = NodeWidth(resizeTarget, includePreview: false); draft.resizePreviewWidth = draft.resizeStartWidth; - draft.pointerStart = mouse; - return; - } - PortHandle? hit = HitPort(mouse, points); - if (!draft.readOnly && hit is PortHandle handle && !draft.missingPorts.Contains(handle.endpoint)) - { - Canvas.BeginConnection(handle.endpoint, handle.direction); + draft.resizePointerStart = mouse; return; } - GraphNodeRecord? node = HitNode(mouse); - ShaderCanvasGroup? group = node is null ? HitGroupHeader(mouse) : null; + + GraphNodeRecord? hitNode = HitNode(mouse); + ShaderCanvasGroup? group = hitNode is null ? HitGroupHeader(mouse) : null; if (group is ShaderCanvasGroup selectedGroup) { draft.selectedEdge = null; draft.selectedGroupId = selectedGroup.id; Canvas.SelectNodes(selectedGroup.nodes.Select(static id => new GraphNodeId(id))); + SynchronizeInspection(); if (!draft.readOnly) - { - draft.dragStart.Clear(); - foreach (GraphNodeId id in Canvas.selectedNodes) - if (Controller.document.FindNode(id) is GraphNodeRecord member) - draft.dragStart[id] = member.position; - draft.dragging = draft.dragStart.Count != 0; - draft.pointerStart = mouse; - } + draft.pointer.BeginMove(draft.layout, Canvas.selectedNodes, mouse); return; } - if (node is not null && mouse.Y <= Rect(node).min.Y + C_HEADER * Canvas.zoom) - { - draft.selectedGroupId = ""; - draft.selectedEdge = null; - if (ImGuiApi.Raw.GetIO()->KeyShift != 0) - Canvas.ToggleNode(node.id); - else if (!Canvas.selectedNodes.Contains(node.id)) - Canvas.SelectNodes([node.id]); - SetStage(node); - if (draft.readOnly) - return; - draft.dragStart.Clear(); - foreach (GraphNodeId id in Canvas.selectedNodes) - draft.dragStart[id] = Controller.document.FindNode(id)!.position; - draft.dragging = true; - draft.pointerStart = mouse; - } - else if (node is null) + if (hitNode is null && HitPort(mouse) is null) { draft.selectedGroupId = ""; - if (ImGuiApi.Raw.GetIO()->KeyShift == 0) - Canvas.ClearSelection(); - draft.selectedEdge = HitEdge(mouse, points); + draft.selectedEdge = HitEdge(mouse); if (draft.selectedEdge is not null) + { + if (!ImGuiApi.GetInput().shift) + Canvas.ClearSelection(); return; - draft.boxSelecting = true; - draft.pointerStart = mouse; + } } } - if (draft.dragging && ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Left)) + + GraphCanvasPointerResult intent = draft.pointer.Step(View, draft.layout, new GraphCanvasPointerInput + { + position = mouse, + isHovered = hovered, + isPressed = ImGuiApi.IsMouseClicked(ImGuiMouseButton.Left), + isDown = ImGuiApi.IsMouseButtonDown(ImGuiMouseButton.Left), + isReleased = ImGuiApi.IsMouseReleased(ImGuiMouseButton.Left), + shift = ImGuiApi.GetInput().shift, + readOnly = draft.readOnly + }); + if (intent.nodePressed is GraphNodeId pressed && Controller.document.FindNode(pressed) is { } node) + { + draft.selectedGroupId = ""; + draft.selectedEdge = null; + SetStage(node); + SynchronizeInspection(); + if (!draft.readOnly && !draft.pointer.isMoving) + draft.pointer.BeginMove(draft.layout, Canvas.selectedNodes, mouse); + } + if (intent.movedNodes is { } positions) { - Vector2 delta = (mouse - draft.pointerStart) / Canvas.zoom; - foreach ((GraphNodeId id, GraphPosition position) in draft.dragStart) - draft.dragPreview[id] = new(position.x + delta.X, position.y + delta.Y); + draft.pendingMove = positions; + owner.CompleteCanvasEdit(draft); } - if (!ImGuiApi.IsMouseDown((int)ImGuiMouseButton.Left)) + if (intent.output is GraphEndpoint output && intent.input is GraphEndpoint input) { - if (draft.dragging && draft.dragPreview.Count != 0) + ShaderNodePort outputPort = Port(output); + ShaderNodePort inputPort = Port(input); + if (outputPort.type.IsEquivalentTo(inputPort.type) || inputPort.type.id == "any") { - Controller.MoveNodes(draft.dragPreview); + Controller.Connect(output, input); owner.Changed(draft); } - if (draft.boxSelecting) - { - Vector2 min = Vector2.Min(draft.pointerStart, mouse), max = Vector2.Max(draft.pointerStart, mouse); - Canvas.SelectNodes(Controller.document.nodes.Where(node => - { - var rect = Rect(node); - return rect.max.X >= min.X && rect.min.X <= max.X && rect.max.Y >= min.Y && rect.min.Y <= max.Y; - }).Select(static node => node.id)); - } - if (Canvas.pendingConnection is GraphEndpoint sourceEndpoint - && Canvas.pendingConnectionDirection is GraphPortDirection sourceDirection) - { - var source = new PortHandle(sourceEndpoint, sourceDirection); - PortHandle? target = HitPort(mouse, points); - if (target is PortHandle destination - && destination.direction != source.direction - && !draft.missingPorts.Contains(destination.endpoint)) - { - PortHandle output = source.direction == GraphPortDirection.Output ? source : destination; - PortHandle input = source.direction == GraphPortDirection.Input ? source : destination; - ShaderNodePort outputPort = Port(output); - ShaderNodePort inputPort = Port(input); - if (outputPort.type.IsEquivalentTo(inputPort.type) || inputPort.type.id == "any") - { - Controller.Connect(output.endpoint, input.endpoint); - owner.Changed(draft); - } - } - else if (source.direction == GraphPortDirection.Output - && target is null && hovered && HitNode(mouse) is null) - { - draft.createFromPort = source.endpoint; - draft.menuPosition = ToGraph(mouse); - ImGuiApi.OpenPopup("##shader-menu"); - } - Canvas.CancelConnection(); - } - draft.dragging = draft.boxSelecting = false; - draft.dragPreview.Clear(); + } + if (intent.releasedOutput is GraphEndpoint released) + { + draft.createFromPort = released; + draft.menuPosition = ToGraph(mouse); + ImGuiApi.OpenPopup("##shader-menu"); } } - private unsafe void Node( - ImDrawList* draw, + private void Node( + EditorDrawList draw, GraphNodeRecord node, - Dictionary points + Vector2 mouse ) { var rect = Rect(node); if (rect.max.X < m_origin.X || rect.min.X > m_origin.X + m_size.X || rect.max.Y < m_origin.Y || rect.min.Y > m_origin.Y + m_size.Y) return; float zoom = Canvas.zoom; - bool selected = Canvas.selectedNodes.Contains(node.id); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(rect.min + new Vector2(3, 5)), - Unsafe.BitCast(rect.max + new Vector2(3, 5)), - Color(0, 0, 0, EditorPalette.opacityMuted), - 6 * zoom, - 0 - ); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(rect.min), - Unsafe.BitCast(rect.max), - Color(0.115f, 0.12f, 0.14f), - 6 * zoom, - 0 - ); - Vector4 headerColor = NodeHeaderColor(node); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(rect.min), - Unsafe.BitCast(new(rect.max.X, rect.min.Y + C_HEADER * zoom)), - ImGuiApi.ColorConvertFloat4ToU32(headerColor), - 6 * zoom, - (int)ImDrawFlags.RoundCornersTop - ); - DrawNativeRectangle( - draw, - Unsafe.BitCast(rect.min), - Unsafe.BitCast(rect.max), - selected ? Color(0.65f, 0.47f, 0.88f) : Color(0.26f, 0.27f, 0.31f), - 6 * zoom, - (int)ImDrawFlags.None, - selected ? 2 : 1 - ); - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(Title(node), allowsNull: false)) - { - DrawNativeText( - draw, - ImGuiApi.Raw.GetFont(), - ImGuiApi.GetFontSize() * zoom, - Unsafe.BitCast(rect.min + new Vector2(10, 7) * zoom), - ImGuiApi.GetColorU32((int)ImGuiCol.Text), - (sbyte*)nativeText.pointer, - null, - 0f, - null - ); - } + if (!draft.layout.TryGetNode(node.id, out GraphCanvasNode? presentation)) + return; + _ = View.DrawNode(draw, presentation, ImGuiApi.ToPackedColor(NodeHeaderColor(node)), Canvas.selectedNodes.Contains(node.id), + previewPosition: draft.pointer.previewPositions.TryGetValue(node.id, out GraphPosition preview) ? preview : null); ShaderNodePort[] nodePorts = draft.ports[node.id]; - bool splitPorts = UsesSplitPortLayout(nodePorts); - float divider = (rect.min.X + rect.max.X) * 0.5f; - float bodyTop = rect.min.Y + C_HEADER * zoom; - if (splitPorts) + if (UsesSplitPortLayout(nodePorts)) { - float bodyPadding = 7f * zoom; - DrawNativeLine( - draw, - Unsafe.BitCast(new Vector2(divider, bodyTop + bodyPadding)), - Unsafe.BitCast(new Vector2(divider, rect.max.Y - bodyPadding)), - Color(0.62f, 0.63f, 0.69f, EditorPalette.opacityFaint), - 1f - ); + float divider = (rect.min.X + rect.max.X) * 0.5f; + float padding = 7f * zoom; + draw.Line(new(divider, rect.min.Y + C_HEADER * zoom + padding), new(divider, rect.max.Y - padding), + Color(0.62f, 0.63f, 0.69f, EditorPalette.opacityFaint), 1f); } foreach (ShaderNodePort port in nodePorts) { - var handle = new PortHandle(new(node.id, new(port.id)), port.direction); - Vector2 point = points[handle]; - bool missing = draft.missingPorts.Contains(new(node.id, new(port.id))); - bool optional = port.direction == GraphPortDirection.Input && !port.required; - uint portColor = missing ? Color(0.95f, 0.35f, 0.3f) : Color(0.60f, 0.48f, 0.84f); - if (missing || !optional) - DrawNativeFilledCircle(draw, Unsafe.BitCast(point), 5 * zoom, portColor, 0); - else - { - DrawNativeFilledCircle(draw, Unsafe.BitCast(point), 5 * zoom, portColor, 0); - DrawNativeFilledCircle( - draw, - Unsafe.BitCast(point), - 2.75f * zoom, - Color(0.115f, 0.12f, 0.14f), - 0 - ); - } - float halfPadding = 12f * zoom; - float portAreaWidth = splitPorts - ? (rect.max.X - rect.min.X) * 0.5f - : rect.max.X - rect.min.X; - float availableWidth = MathF.Max(1f, portAreaWidth - halfPadding * 2f); - string label = FitPortLabel(port.id, availableWidth, zoom); - float labelWidth = ImGuiApi.CalcTextSize(label).X * zoom; - bool input = port.direction == GraphPortDirection.Input; - float x = input ? rect.min.X + halfPadding : rect.max.X - labelWidth - halfPadding; - float clipLeft = splitPorts && !input ? divider + 1f : rect.min.X + 1f; - float clipRight = splitPorts && input ? divider - 1f : rect.max.X - 1f; - Vector2 clipMin = new(clipLeft, bodyTop); - Vector2 clipMax = new(clipRight, rect.max.Y); - PushNativeClipRectangle( - draw, - Unsafe.BitCast(clipMin), - Unsafe.BitCast(clipMax), - (true ? (byte)1 : (byte)0) - ); - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(label, allowsNull: false)) + var endpoint = new GraphEndpoint(node.id, new GraphPortId(port.id)); + if (!TryPortPosition(endpoint, port.direction, out Vector2 point)) + continue; + if (Vector2.DistanceSquared(point, mouse) <= 64f) { - DrawNativeText( - draw, - ImGuiApi.Raw.GetFont(), - ImGuiApi.GetFontSize() * zoom, - Unsafe.BitCast(new(x, point.Y - 8 * zoom)), - ImGuiApi.ColorConvertFloat4ToU32(optional ? EditorPalette.textDisabled : EditorPalette.text), - (sbyte*)nativeText.pointer, - null, - 0f, - null - ); - } - ImGuiApi.Raw.PopClipRect(draw); - if (Vector2.DistanceSquared(point, ImGuiApi.GetMousePos()) <= 64) - Widget.DrawTooltip(port.type.id + (missing - ? " · missing port; reconnect explicitly" + bool missing = draft.missingPorts.Contains(endpoint); + bool optional = port.direction == GraphPortDirection.Input && !port.required; + EditorUI.current.widgets.DrawTooltip(port.type.id + (missing ? " · missing port; reconnect explicitly" : optional ? " · optional input" : port.direction == GraphPortDirection.Input ? " · required input" : "")); + } } + if (!draft.readOnly) { bool resizeHovered = ContainsResizeHandle(rect, ImGuiApi.GetMousePos()); - uint gripColor = Color( - 0.62f, - 0.63f, - 0.69f, - resizeHovered || draft.resizingNode == node.id - ? EditorPalette.opacityEmphasized - : EditorPalette.opacityMedium); + uint gripColor = Color(0.62f, 0.63f, 0.69f, resizeHovered || draft.resizingNode == node.id ? EditorPalette.opacityEmphasized : EditorPalette.opacityMedium); const float inset = 4f; - DrawNativeLine( - draw, - Unsafe.BitCast(rect.max - new Vector2(C_RESIZE_HANDLE, inset) * zoom), - Unsafe.BitCast(rect.max - new Vector2(inset, C_RESIZE_HANDLE) * zoom), - gripColor, - 1f - ); - DrawNativeLine( - draw, - Unsafe.BitCast(rect.max - new Vector2(C_RESIZE_HANDLE * 0.62f, inset) * zoom), - Unsafe.BitCast(rect.max - new Vector2(inset, C_RESIZE_HANDLE * 0.62f) * zoom), - gripColor, - 1f - ); + draw.Line(rect.max - new Vector2(C_RESIZE_HANDLE, inset) * zoom, rect.max - new Vector2(inset, C_RESIZE_HANDLE) * zoom, gripColor, 1f); + draw.Line(rect.max - new Vector2(C_RESIZE_HANDLE * 0.62f, inset) * zoom, rect.max - new Vector2(inset, C_RESIZE_HANDLE * 0.62f) * zoom, gripColor, 1f); } + if (draft.ports[node.id].Length == 0 && node.definitionId == ShaderGraphNodes.inputDefinitionId) - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated("No caller inputs", allowsNull: false)) - { - DrawNativeText( - draw, - ImGuiApi.Raw.GetFont(), - ImGuiApi.GetFontSize() * zoom, - Unsafe.BitCast(rect.min + new Vector2(12, C_HEADER + 8) * zoom), - ImGuiApi.GetColorU32((int)ImGuiCol.TextDisabled), - (sbyte*)nativeText.pointer, - null, - 0f, - null - ); - } - if (draft.nodeErrors.TryGetValue(node.id, out string? error)) { - DrawNativeFilledCircle( - draw, - Unsafe.BitCast(new(rect.max.X - 14 * zoom, rect.min.Y + 16 * zoom)), - 4 * zoom, - Color(1, 0.4f, 0.35f), - 0 + draw.Text( + rect.min + new Vector2(12, C_HEADER + 8) * zoom, + ImGuiApi.GetStyleColor(ImGuiCol.TextDisabled), + "No caller inputs", + (ImGuiApi.GetFontSize() * zoom) / ImGuiApi.GetFontSize() ); + } + + if (draft.nodeErrors.TryGetValue(node.id, out string? error)) + { + draw.FilledCircle(new(rect.max.X - 14 * zoom, rect.min.Y + 16 * zoom), 4 * zoom, Color(1, 0.4f, 0.35f)); if (Contains(rect, ImGuiApi.GetMousePos())) - Widget.DrawTooltip(error); + EditorUI.current.widgets.DrawTooltip(error); } } - private void Grid(ImDrawList* draw) - { - float spacing = 32 * Canvas.zoom; - for (float x = ((Canvas.pan.x % spacing) + spacing) % spacing; x < m_size.X; x += spacing) - DrawNativeLine( - draw, - Unsafe.BitCast(m_origin + new Vector2(x, 0)), - Unsafe.BitCast(m_origin + new Vector2(x, m_size.Y)), - Color(0.105f, 0.115f, 0.135f), - 1f - ); - for (float y = ((Canvas.pan.y % spacing) + spacing) % spacing; y < m_size.Y; y += spacing) - DrawNativeLine( - draw, - Unsafe.BitCast(m_origin + new Vector2(0, y)), - Unsafe.BitCast(m_origin + new Vector2(m_size.X, y)), - Color(0.105f, 0.115f, 0.135f), - 1f - ); - } - private unsafe void Groups(ImDrawList* draw) + private void Grid(EditorDrawList draw) => View.DrawGrid(Color(0.105f, 0.115f, 0.135f)); + private void Groups(EditorDrawList draw) { foreach (ShaderCanvasGroup group in draft.groups) { @@ -821,70 +646,41 @@ private unsafe void Groups(ImDrawList* draw) bool selected = draft.selectedGroupId == group.id; Vector4 headerColor = GroupHeaderColor(group); Vector4 bodyColor = EditorPalette.WithOpacity(headerColor, EditorPalette.opacitySoft); - Vector4 borderColor = selected - ? new(0.65f, 0.47f, 0.88f, EditorPalette.opacityOpaque) - : new( - MathF.Min(1f, headerColor.X * 1.22f), - MathF.Min(1f, headerColor.Y * 1.22f), - MathF.Min(1f, headerColor.Z * 1.22f), - EditorPalette.opacityEmphasized); - float headerHeight = C_HEADER * Canvas.zoom; - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(min), - Unsafe.BitCast(max), - ImGuiApi.ColorConvertFloat4ToU32(bodyColor), - 8 * Canvas.zoom, - 0 - ); - DrawNativeFilledRectangle( - draw, - Unsafe.BitCast(min), - Unsafe.BitCast(new(max.X, min.Y + headerHeight)), - ImGuiApi.ColorConvertFloat4ToU32(headerColor), - 8 * Canvas.zoom, - (int)ImDrawFlags.RoundCornersTop - ); - DrawNativeLine( - draw, - Unsafe.BitCast(new(min.X, min.Y + headerHeight)), - Unsafe.BitCast(new(max.X, min.Y + headerHeight)), - ImGuiApi.ColorConvertFloat4ToU32(borderColor), - 1f + Vector4 borderColor = selected ? new( + 0.65f, + 0.47f, + 0.88f, + EditorPalette.opacityOpaque + ) : new( + MathF.Min(1f, headerColor.X * 1.22f), + MathF.Min(1f, headerColor.Y * 1.22f), + MathF.Min(1f, headerColor.Z * 1.22f), + EditorPalette.opacityEmphasized ); - DrawNativeRectangle( - draw, - Unsafe.BitCast(min), - Unsafe.BitCast(max), - ImGuiApi.ColorConvertFloat4ToU32(borderColor), - 8 * Canvas.zoom, - (int)ImDrawFlags.None, - selected ? 2f : 1f - ); - using (NativeStringLease nativeText = Utf8Codec.EncodeNullTerminated(group.title, allowsNull: false)) + float headerHeight = C_HEADER * Canvas.zoom; + draw.FilledRectangle(min, max, ImGuiApi.ToPackedColor(bodyColor), 8 * Canvas.zoom, false); + draw.FilledRectangle(min, new(max.X, min.Y + headerHeight), ImGuiApi.ToPackedColor(headerColor), 8 * Canvas.zoom, true); + draw.Line(new(min.X, min.Y + headerHeight), new(max.X, min.Y + headerHeight), ImGuiApi.ToPackedColor(borderColor), 1f); + draw.Rectangle(min, max, ImGuiApi.ToPackedColor(borderColor), 8 * Canvas.zoom, selected ? 2f : 1f); { - DrawNativeText( - draw, - ImGuiApi.Raw.GetFont(), - ImGuiApi.GetFontSize() * Canvas.zoom, - Unsafe.BitCast(min + new Vector2(12, 7) * Canvas.zoom), - ImGuiApi.GetColorU32((int)ImGuiCol.Text), - (sbyte*)nativeText.pointer, - null, - 0f, - null + draw.Text( + min + new Vector2(12, 7) * Canvas.zoom, + ImGuiApi.GetStyleColor(ImGuiCol.Text), + group.title, + (ImGuiApi.GetFontSize() * Canvas.zoom) / ImGuiApi.GetFontSize() ); } } } + private ShaderCanvasGroup? HitGroupHeader(Vector2 point) { foreach (ShaderCanvasGroup group in draft.groups.Reverse()) - if (GroupRect(group) is { } bounds && point.X >= bounds.min.X && point.X <= bounds.max.X - && point.Y >= bounds.min.Y && point.Y <= bounds.min.Y + 32f * Canvas.zoom) + if (GroupRect(group) is { } bounds && point.X >= bounds.min.X && point.X <= bounds.max.X && point.Y >= bounds.min.Y && point.Y <= bounds.min.Y + 32f * Canvas.zoom) return group; return null; } + private (Vector2 min, Vector2 max)? GroupRect(ShaderCanvasGroup group) { Vector2 min = new(float.MaxValue), max = new(float.MinValue); @@ -895,125 +691,138 @@ private unsafe void Groups(ImDrawList* draw) min = Vector2.Min(min, rect.min); max = Vector2.Max(max, rect.max); } + if (min.X == float.MaxValue) return null; return (min - new Vector2(20, 40) * Canvas.zoom, max + new Vector2(20) * Canvas.zoom); } - private Dictionary PortPositions() + + private void RefreshGeometry() { - var points = new Dictionary(); + bool changed = draft.layoutRevision != Controller.revision + || draft.layoutAssetRevision != owner.assets.revision || draft.layoutTypeRevision != owner.typeVersion; + if (!changed && draft.resizingNode is GraphNodeId resizing + && draft.layout.TryGetNode(resizing, out GraphCanvasNode? current) + && Controller.document.FindNode(resizing) is { } resizedNode) + { + if (current.size.X != NodeWidth(resizedNode)) + _ = draft.layout.SetNodes(draft.layout.nodes.Select(node => node.id == resizing ? DescribeNode(resizedNode) : node)); + return; + } + if (!changed) + return; + var nodes = new List(Controller.document.nodes.Count); foreach (GraphNodeRecord node in Controller.document.nodes) + nodes.Add(DescribeNode(node)); + _ = draft.layout.SetNodes(nodes); + draft.layoutRevision = Controller.revision; + draft.layoutAssetRevision = owner.assets.revision; + draft.layoutTypeRevision = owner.typeVersion; + + GraphCanvasNode DescribeNode(GraphNodeRecord node) { - var rect = Rect(node); - int left = 0, right = 0; + float width = NodeWidth(node); + bool split = UsesSplitPortLayout(draft.ports[node.id]); + int left = 0; + int right = 0; + var ports = new List(); foreach (ShaderNodePort port in draft.ports[node.id]) { - int row = port.direction == GraphPortDirection.Input ? left++ : right++; - var handle = new PortHandle(new(node.id, new(port.id)), port.direction); - points[handle] = new(port.direction == GraphPortDirection.Input ? rect.min.X : rect.max.X, - rect.min.Y + (C_HEADER + C_ROW * (row + 0.5f)) * Canvas.zoom); + bool input = port.direction == GraphPortDirection.Input; + int row = input ? left++ : right++; + float y = C_HEADER + C_ROW * (row + 0.5f); + var endpoint = new GraphEndpoint(node.id, new GraphPortId(port.id)); + ports.Add(new GraphCanvasPort(endpoint, port.direction, new(input ? 0f : width, y), port.id, + input && !port.required, draft.missingPorts.Contains(endpoint), + (new(split && !input ? width * 0.5f : 0f, y - C_ROW * 0.5f), + new(split && input ? width * 0.5f : width, y + C_ROW * 0.5f)))); } + return new GraphCanvasNode(node.id, node.position, + new(width, C_HEADER + Rows(node) * C_ROW + 20f), C_HEADER, Title(node), ports); } - return points; } - private void Edges( - ImDrawList* draw, - Dictionary points + + private bool TryPortPosition( + GraphEndpoint endpoint, + GraphPortDirection direction, + out Vector2 position ) { + if (draft.layout.TryGetNode(endpoint.nodeId, out GraphCanvasNode? node)) + foreach (GraphCanvasPort port in node.ports) + if (port.endpoint == endpoint && port.direction == direction) + { + position = View.ToScreen(draft.pointer.previewPositions.GetValueOrDefault(node.id, node.position)) + port.position * Canvas.zoom; + return true; + } + position = default; + return false; + } + + private void Edges(EditorDrawList draw) + { foreach (GraphEdgeRecord edge in Controller.document.edges) - if (points.TryGetValue(new(edge.output, GraphPortDirection.Output), out Vector2 a) - && points.TryGetValue(new(edge.input, GraphPortDirection.Input), out Vector2 b)) + if (TryPortPosition(edge.output, GraphPortDirection.Output, out Vector2 a) && TryPortPosition(edge.input, GraphPortDirection.Input, out Vector2 b)) Curve(a, b, edge.id == draft.selectedEdge); if (Canvas.pendingConnection is GraphEndpoint pending && Canvas.pendingConnectionDirection is GraphPortDirection direction - && points.TryGetValue(new(pending, direction), out Vector2 start)) + && TryPortPosition( + pending, + direction, + out Vector2 start + )) { if (direction == GraphPortDirection.Output) Curve(start, ImGuiApi.GetMousePos()); else Curve(ImGuiApi.GetMousePos(), start); } + void Curve( Vector2 a, Vector2 b, bool selected = false ) { - float tangent = MathF.Max(36, MathF.Abs(b.X - a.X) * 0.45f); - DrawNativeCurve( - draw, - new ImVec2C { X = a.X, Y = a.Y }, - new ImVec2C { X = a.X + tangent, Y = a.Y }, - new ImVec2C { X = b.X - tangent, Y = b.Y }, - new ImVec2C { X = b.X, Y = b.Y }, - selected ? Color(0.87f, 0.72f, 1f) : Color(0.59f, 0.46f, 0.82f), - selected ? 3f : 2f, - 0); + View.DrawConnection(draw, a, b, selected ? Color(0.87f, 0.72f, 1f) : Color(0.59f, 0.46f, 0.82f), selected ? 3f : 2f); } } - private GraphEdgeId? HitEdge( - Vector2 mouse, - Dictionary points - ) { + + private GraphEdgeId? HitEdge(Vector2 mouse) + { foreach (GraphEdgeRecord edge in Controller.document.edges) { - if (!points.TryGetValue(new(edge.output, GraphPortDirection.Output), out Vector2 a) - || !points.TryGetValue(new(edge.input, GraphPortDirection.Input), out Vector2 b)) - continue; - float tangent = MathF.Max(36, MathF.Abs(b.X - a.X) * 0.45f); - Vector2 c = a + new Vector2(tangent, 0), d = b - new Vector2(tangent, 0), previous = a; - for (int segment = 1; segment <= 32; segment++) - { - float t = segment / 32f, s = 1 - t; - Vector2 next = s * s * s * a + 3 * s * s * t * c + 3 * s * t * t * d + t * t * t * b; - Vector2 line = next - previous; - float projection = Math.Clamp(Vector2.Dot(mouse - previous, line) / MathF.Max(0.001f, line.LengthSquared()), 0, 1); - if (Vector2.DistanceSquared(mouse, previous + projection * line) < 36) - return edge.id; - previous = next; - } + if (!TryPortPosition(edge.output, GraphPortDirection.Output, out Vector2 a) || !TryPortPosition(edge.input, GraphPortDirection.Input, out Vector2 b)) + continue; + if (GraphCanvasView.HitConnection(mouse, a, b)) + return edge.id; } + return null; } + private (Vector2 min, Vector2 max) Rect(GraphNodeRecord node) { - GraphPosition position = draft.dragPreview.GetValueOrDefault(node.id, node.position); - Vector2 min = m_origin + new Vector2(Canvas.pan.x, Canvas.pan.y) + new Vector2(position.x, position.y) * Canvas.zoom; - return (min, min + new Vector2(NodeWidth(node), C_HEADER + Rows(node) * C_ROW + ControlHeight(node) + 20) * Canvas.zoom); + if (draft.layout.TryGetNode(node.id, out GraphCanvasNode? presentation)) + return View.NodeBounds(presentation, draft.pointer.previewPositions.TryGetValue(node.id, out GraphPosition preview) ? preview : null); + Vector2 minimum = View.ToScreen(node.position); + return (minimum, minimum + new Vector2(NodeWidth(node), C_HEADER + Rows(node) * C_ROW + 20f) * Canvas.zoom); } - private static float ControlHeight(GraphNodeRecord node) => 0f; + private int Rows(GraphNodeRecord node) { int rows = Math.Max( draft.ports[node.id].Count(static port => port.direction == GraphPortDirection.Input), - draft.ports[node.id].Count(static port => port.direction == GraphPortDirection.Output)); + draft.ports[node.id].Count(static port => port.direction == GraphPortDirection.Output) + ); return rows == 0 && node.definitionId is ShaderGraphNodes.inputDefinitionId or ShaderGraphNodes.outputDefinitionId ? 1 : rows; } - private static bool UsesSplitPortLayout(IEnumerable ports) - => ports.Any(static port => port.direction == GraphPortDirection.Input) - && ports.Any(static port => port.direction == GraphPortDirection.Output); - private GraphNodeRecord? HitNode(Vector2 point) => Controller.document.nodes.Reverse().FirstOrDefault(node => Contains(Rect(node), point)); - private PortHandle? HitPort( - Vector2 point, - Dictionary ports - ) { - foreach ((PortHandle handle, Vector2 position) in ports) - if (Vector2.DistanceSquared(point, position) <= MathF.Pow(MathF.Max(8, 7 * Canvas.zoom), 2)) - return handle; - foreach ((PortHandle handle, Vector2 position) in ports) - { - GraphNodeRecord node = Controller.document.FindNode(handle.endpoint.nodeId)!; - (Vector2 min, Vector2 max) rect = Rect(node); - bool splitPorts = UsesSplitPortLayout(draft.ports[node.id]); - float divider = (rect.min.X + rect.max.X) * 0.5f; - bool input = handle.direction == GraphPortDirection.Input; - float minimumX = splitPorts && !input ? divider : rect.min.X; - float maximumX = splitPorts && input ? divider : rect.max.X; - float halfRow = C_ROW * Canvas.zoom * 0.5f; - if (point.X >= minimumX && point.X <= maximumX && point.Y >= position.Y - halfRow && point.Y <= position.Y + halfRow) - return handle; - } - return null; - } + + private static bool UsesSplitPortLayout(IEnumerable ports) => ports.Any(static port => port.direction == GraphPortDirection.Input) + && ports.Any(static port => port.direction == GraphPortDirection.Output); + private GraphNodeRecord? HitNode(Vector2 point) + => View.HitNode(draft.layout, point) is { } node ? Controller.document.FindNode(node.id) : null; + + private PortHandle? HitPort(Vector2 point) => View.HitPort(draft.layout, point) is { } port ? new PortHandle(port.endpoint, port.direction) : null; + private GraphNodeRecord? HitResizeHandle(Vector2 point) { foreach (GraphNodeRecord node in Controller.document.nodes.Reverse()) @@ -1022,16 +831,18 @@ Dictionary ports if (ContainsResizeHandle(rect, point)) return node; } + return null; } + private bool ContainsResizeHandle( (Vector2 min, Vector2 max) rect, Vector2 point ) { float extent = MathF.Max(10f, C_RESIZE_HANDLE * Canvas.zoom); - return point.X >= rect.max.X - extent && point.X <= rect.max.X + 2f && - point.Y >= rect.max.Y - extent && point.Y <= rect.max.Y + 2f; + return point.X >= rect.max.X - extent && point.X <= rect.max.X + 2f && point.Y >= rect.max.Y - extent && point.Y <= rect.max.Y + 2f; } + private float NodeWidth( GraphNodeRecord node, bool includePreview = true @@ -1041,34 +852,10 @@ private float NodeWidth( float width = Read(node, C_NODE_WIDTH, C_DEFAULT_WIDTH); return float.IsFinite(width) ? Math.Clamp(width, C_MINIMUM_WIDTH, C_MAXIMUM_WIDTH) : C_DEFAULT_WIDTH; } - private static string FitPortLabel( - string label, - float availableWidth, - float zoom - ) { - if (ImGuiApi.CalcTextSize(label).X * zoom <= availableWidth) - return label; - const string ellipsis = "…"; - float ellipsisWidth = ImGuiApi.CalcTextSize(ellipsis).X * zoom; - if (ellipsisWidth >= availableWidth) - return string.Empty; - int low = 0; - int high = label.Length; - while (low < high) - { - int length = (low + high + 1) / 2; - if (ImGuiApi.CalcTextSize(label[..length]).X * zoom + ellipsisWidth <= availableWidth) - low = length; - else - high = length - 1; - } - return label[..low] + ellipsis; - } - private ShaderNodePort Port(PortHandle handle) - => draft.ports[handle.endpoint.nodeId].Single(port => - port.id == handle.endpoint.portId.value && port.direction == handle.direction); + + private ShaderNodePort Port(PortHandle handle) => draft.ports[handle.endpoint.nodeId].Single(port => port.id == handle.endpoint.portId.value && port.direction == handle.direction); private ShaderNodePort Port(GraphEndpoint endpoint) => draft.ports[endpoint.nodeId].Single(port => port.id == endpoint.portId.value); - private GraphPosition ToGraph(Vector2 point) => new((point.X - m_origin.X - Canvas.pan.x) / Canvas.zoom, (point.Y - m_origin.Y - Canvas.pan.y) / Canvas.zoom); + private GraphPosition ToGraph(Vector2 point) => View.ToGraph(point); private static bool Contains( (Vector2 min, Vector2 max) rect, Vector2 point @@ -1078,9 +865,7 @@ private static uint Color( float g, float b, float a = EditorPalette.opacityOpaque - ) - => ImGuiApi.ColorConvertFloat4ToU32(new(r, g, b, a)); - + ) => ImGuiApi.ToPackedColor(new(r, g, b, a)); private Vector4 NodeHeaderColor(GraphNodeRecord node) { string key = node.definitionId + "/" + Title(node); @@ -1088,18 +873,14 @@ private Vector4 NodeHeaderColor(GraphNodeRecord node) { ShaderGraphDocument.outputDefinitionId => StableHeaderColor(key, 258f, 34f), ShaderGraphNodes.outputDefinitionId => StableHeaderColor(key, 258f, 34f), - "inno.shader.stage-input" or "inno.shader.constant" or ShaderGraphNodes.inputDefinitionId - => StableHeaderColor(key, 22f, 24f), + "inno.shader.stage-input" or "inno.shader.constant" or ShaderGraphNodes.inputDefinitionId => StableHeaderColor(key, 22f, 24f), "inno.shader.sample" => StableHeaderColor(key, 158f, 30f), "inno.shader.source" => StableHeaderColor(key, 198f, 42f), "inno.shader.binary" => StableHeaderColor(key, 205f, 45f), - "inno.shader.construct" or "inno.shader.extract" or "inno.shader.select" or "inno.shader.reroute" - => StableHeaderColor(key, 178f, 58f), - "inno.shader.storage-load" or "inno.shader.storage-store" or "inno.shader.storage-atomic-add" - => StableHeaderColor(key, 105f, 42f), + "inno.shader.construct" or "inno.shader.extract" or "inno.shader.select" or "inno.shader.reroute" => StableHeaderColor(key, 178f, 58f), + "inno.shader.storage-load" or "inno.shader.storage-store" or "inno.shader.storage-atomic-add" => StableHeaderColor(key, 105f, 42f), "inno.shader.discard" => StableHeaderColor(key, 350f, 22f), - _ => StableHeaderColor(key, 0f, 360f) - }; + _ => StableHeaderColor(key, 0f, 360f)}; } private Vector4 GroupHeaderColor(ShaderCanvasGroup group) @@ -1113,8 +894,9 @@ private Vector4 GroupHeaderColor(ShaderCanvasGroup group) total += NodeHeaderColor(node); count++; } + if (count == 0) - return EditorPalette.shaderNodeHeader; + return C_EMPTY_GROUP_HEADER; Vector4 average = total / count; return EditorPalette.WithOpacity(average, EditorPalette.opacityNearOpaque); } @@ -1130,6 +912,7 @@ float hueRange hash ^= value; hash *= 16777619; } + float hue = (startHue + hueRange * (hash & 0xffff) / 65535f) % 360f; float saturation = 0.52f + 0.12f * ((hash >> 16) & 0xff) / 255f; float brightness = 0.34f + 0.08f * ((hash >> 24) & 0xff) / 255f; @@ -1143,13 +926,12 @@ float hueRange < 3f => (0f, chroma, secondary), < 4f => (0f, secondary, chroma), < 5f => (secondary, 0f, chroma), - _ => (chroma, 0f, secondary) - }; + _ => (chroma, 0f, secondary)}; float match = brightness - chroma; return new(red + match, green + match, blue + match, EditorPalette.opacityOpaque); } - private static bool CenteredAddButton(string label) => Widget.CenteredButton(label, Widget.style.inspectorAddButtonTopPadding); + private static bool CenteredAddButton(string label) => EditorUI.current.widgets.CenteredButton(label, EditorUI.current.widgets.style.addButtonTopPadding); private T Read( GraphNodeRecord node, string key, @@ -1160,37 +942,49 @@ private void Set( string key, T value, bool continuous = false - ) - => SetEncoded(node, key, ShaderGraphDocument.Encode(value, owner.serialization, owner.context), continuous); + ) => SetEncoded(node, key, ShaderGraphDocument.Encode(value, owner.serialization, owner.context), continuous ? draft.valueGesture : null); private void SetEncoded( GraphNodeRecord node, string key, GraphSerializedValue value, - bool continuous + string? mergeKey ) { - if (ImGuiApi.IsItemActivated()) - draft.valueGesture = Guid.NewGuid().ToString("N"); if (m_inspectionNodes is { Length: > 1 } selected) { GraphDocument candidate = Controller.document.Clone(); foreach (GraphNodeRecord target in selected) { if (key == "settings" && target.definitionId == "inno.shader.stage-input") - candidate = ShaderGraphBindings.ChangeInput(candidate, target.id, - ShaderGraphDocument.Decode(value, owner.serialization, owner.context), owner.serialization, owner.context); + candidate = ShaderGraphBindings.ChangeInput( + candidate, + target.id, + ShaderGraphDocument.Decode(value, owner.serialization, owner.context), + owner.serialization, + owner.context + ); else candidate.FindNode(target.id)!.SetValue(key, value.Clone()); } - Controller.ReplaceDocument(candidate, "Edit Shader Nodes", continuous ? draft.valueGesture : null); + + Controller.ReplaceDocument(candidate, "Edit Shader Nodes", mergeKey); } else if (key == "settings" && node.definitionId == "inno.shader.stage-input") - Controller.ReplaceDocument(ShaderGraphBindings.ChangeInput(Controller.document, node.id, - ShaderGraphDocument.Decode(value, owner.serialization, owner.context), owner.serialization, owner.context), - "Edit Shader Input", continuous ? draft.valueGesture : null); + Controller.ReplaceDocument( + ShaderGraphBindings.ChangeInput( + Controller.document, + node.id, + ShaderGraphDocument.Decode(value, owner.serialization, owner.context), + owner.serialization, + owner.context + ), + "Edit Shader Input", + mergeKey + ); else - Controller.SetNodeValue(node.id, key, value, continuous ? draft.valueGesture : null); + Controller.SetNodeValue(node.id, key, value, mergeKey); owner.Changed(draft); } + private void SetStage(GraphNodeRecord? node) { if (node is null) @@ -1199,6 +993,7 @@ private void SetStage(GraphNodeRecord? node) if (stage.Length != 0) draft.activeStage = new(stage); } + private void Frame() { GraphNodeRecord[] selection = Controller.document.nodes.Where(node => Canvas.selectedNodes.Count == 0 || Canvas.selectedNodes.Contains(node.id)).ToArray(); @@ -1206,10 +1001,11 @@ private void Frame() return; float minX = selection.Min(static node => node.position.x), minY = selection.Min(static node => node.position.y); float maxX = selection.Max(node => node.position.x + NodeWidth(node)); - float maxY = selection.Max(node => node.position.y + C_HEADER + Rows(node) * C_ROW + ControlHeight(node) + 20); + float maxY = selection.Max(node => node.position.y + C_HEADER + Rows(node) * C_ROW + 20); float scale = Math.Clamp(MathF.Min(MathF.Max(1, m_size.X - 80) / MathF.Max(1, maxX - minX), MathF.Max(1, m_size.Y - 80) / MathF.Max(1, maxY - minY)), 0.1f, 1f); Canvas.SetViewport(new(m_size.X / 2 - (minX + maxX) / 2 * scale, m_size.Y / 2 - (minY + maxY) / 2 * scale), scale); } + internal string Title(GraphNodeRecord node) { if (owner.drawers?.GetDisplayName(node.definitionId) is { } displayName) @@ -1217,25 +1013,29 @@ internal string Title(GraphNodeRecord node) if (node.definitionId == ShaderGraphDocument.outputDefinitionId) return Read(node, "settings", new ShaderGraphStageSettings()).stage + " Output"; if (node.definitionId == "inno.shader.binary") - return Widget.NicifyName(Read(node, "operation", "add").Replace('-', ' ')); + return EditorDisplayNames.Nicify(Read(node, "operation", "add").Replace('-', ' ')); if (node.definitionId == "inno.shader.stage-input") { ShaderGraphInputSettings input = Read(node, "settings", new ShaderGraphInputSettings()); - return input.id.Length != 0 ? Widget.NicifyName(input.id) : "Stage Input"; + return input.id.Length != 0 ? EditorDisplayNames.Nicify(input.id) : "Stage Input"; } + if (node.definitionId == "inno.shader.source") { string function = Read(node, "function", ""); return function.Length == 0 ? "Source Function" : function; } + if (node.definitionId == ShaderGraphNodes.callDefinitionId) return Read(node, ShaderGraphNodes.interfaceKey, new ShaderGraphNodeInterface()).displayName; if (node.definitionId == ShaderGraphNodes.inputDefinitionId) - return ShaderGraphNodes.ReadSettings(Controller.document, owner.serialization, owner.context).kind == ShaderGraphNodeKind.DomainOutput - ? "Domain Inputs" - : "Function Inputs"; + return ShaderGraphNodes.ReadSettings( + Controller.document, + owner.serialization, + owner.context + ).kind == ShaderGraphNodeKind.DomainOutput ? "Domain Inputs" : "Function Inputs"; if (node.definitionId == ShaderGraphNodes.outputDefinitionId) return "Function Outputs"; - return Widget.NicifyName(node.definitionId.Replace("inno.shader.", "", StringComparison.Ordinal).Replace('-', ' ')); + return EditorDisplayNames.Nicify(node.definitionId.Replace("inno.shader.", "", StringComparison.Ordinal).Replace('-', ' ')); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.CanvasEdits.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.CanvasEdits.cs new file mode 100644 index 000000000..c1b20ea5e --- /dev/null +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.CanvasEdits.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.Shaders; +using Inno.Rendering.Shaders; + +namespace Inno.Editor.Panel.ShaderEditor; + +internal sealed partial class ShaderEditorDocuments +{ + internal void CompleteCanvasEdit(Draft draft) + { + GraphDocumentController controller = Controller(draft); + // A preview remains presentation-only until this atomic domain edit succeeds. + // Preserve a released intent when History rejects the operation so it can be retried. + draft.pendingMove ??= draft.pointer.previewPositions.Count == 0 + ? null + : new Dictionary(draft.pointer.previewPositions); + if (draft.pendingMove is { } positions) + { + bool changed = false; + foreach (var entry in positions) + changed |= controller.document.FindNode(entry.Key) is not { } current || current.position != entry.Value; + if (changed) + { + controller.MoveNodes(positions); + Changed(draft); + } + draft.pendingMove = null; + } + if (draft.resizingNode is GraphNodeId resizing + && controller.document.FindNode(resizing) is { } node) + { + GraphDocument replacement = controller.document.Clone(); + replacement.FindNode(node.id)!.SetValue("editor.width", ShaderGraphDocument.Encode(draft.resizePreviewWidth, serialization, context)); + controller.ReplaceDocument(replacement, "Resize Shader Node"); + Changed(draft); + } + CancelCanvasEdit(draft); + } + + private void CompleteCanvasEdits() + { + foreach (Draft draft in m_drafts.Values) + CompleteCanvasEdit(draft); + } + + internal static void CancelCanvasEdit(Draft draft) + { + draft.pendingMove = null; + draft.pointer.Cancel(); + draft.resizingNode = null; + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.cs index 88b5e2fa8..73cb1336b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Documents/ShaderEditorDocuments.cs @@ -1,3 +1,4 @@ +using Inno.Editor.Graph.ImGui; using System; using System.Collections.Generic; using System.Diagnostics; @@ -14,7 +15,9 @@ using Inno.Editor.Interactions; using Inno.Editor.Rendering; using Inno.Editor.Shaders; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Shaders.ImGui; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Rendering; using Inno.Extensibility.Types; using Inno.Rendering.Assets; @@ -22,7 +25,6 @@ using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Panel.ShaderEditor; - [EditorModule("rendering.shader-documents", order: 170)] internal sealed partial class ShaderEditorDocuments : EditorModule { @@ -49,7 +51,6 @@ internal sealed partial class ShaderEditorDocuments : EditorModule internal readonly SerializationRegistry serialization; internal readonly EditorInteractions interactions; internal ShaderClipboardData? clipboard; - internal ShaderEditorDocuments( AssetPipeline assets, SerializationRegistry serialization, @@ -79,9 +80,7 @@ IEditorPreviewService previews internal EditorShaderDraftCompilationSnapshot? Preview(Draft draft) { GraphDocumentController controller = Controller(draft); - return draft.checkedRevision == controller.revision && draft.checkedState is not null - ? draft.preview - : null; + return draft.checkedRevision == controller.revision && draft.checkedState is not null ? draft.preview : null; } internal EditorShaderDraftCompilationSnapshot Check(Draft draft) @@ -98,21 +97,22 @@ internal EditorShaderDraftCompilationSnapshot Check(Draft draft) catch (Exception failure) when ((failure is InvalidOperationException or ArgumentException or FormatException or NotSupportedException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { - nodeSnapshot = new(EditorShaderCompilationState.Failed, false, - [new ShaderDiagnostic("SHADER_GRAPH_NODE_INTERFACE", DiagnosticSeverity.Error, failure.Message)], null); + nodeSnapshot = new( + EditorShaderCompilationState.Failed, + false, + [new ShaderDiagnostic("SHADER_GRAPH_NODE_INTERFACE", DiagnosticSeverity.Error, failure.Message)], + null + ); } + draft.checkedRevision = controller.revision; draft.checkedState = nodeSnapshot.state; return draft.preview = nodeSnapshot; } - EditorShaderDraftCompilationSnapshot snapshot = m_compilation.RequestDraft( - draft.id, - controller.document, - controller.revision, - RenderShaderVariant.empty); + + EditorShaderDraftCompilationSnapshot snapshot = m_compilation.RequestDraft(draft.id, controller.document, controller.revision, RenderShaderVariant.empty); if (snapshot.state == EditorShaderCompilationState.Compiling) return snapshot; - draft.checkedRevision = controller.revision; draft.checkedState = snapshot.state; if (snapshot.state == EditorShaderCompilationState.Failed) @@ -122,11 +122,11 @@ internal EditorShaderDraftCompilationSnapshot Check(Draft draft) m_compilation.ReleaseDraft(draft.id); snapshot = new(EditorShaderCompilationState.Failed, false, snapshot.diagnostics, null); } + return draft.preview = snapshot; } internal void ShowCheck(Draft draft) => m_checkDraftId = draft.id; - internal bool TryGetCheckDraft(out Draft draft) { draft = null!; @@ -134,7 +134,6 @@ internal bool TryGetCheckDraft(out Draft draft) } internal void CloseCheck() => m_checkDraftId = null; - internal void ReleasePreview(Draft draft) { m_compilation.ReleaseDraft(draft.id); @@ -145,20 +144,12 @@ internal void ReleasePreview(Draft draft) internal void PublishNodeDiagnostics(Draft draft) { - Diagnostic[] diagnostics = draft.nodeErrors.Select(pair => new Diagnostic( - "SHADER_NODE_INVALID", - $"Node '{pair.Key.value}' cannot be evaluated: {pair.Value}", - DiagnosticSeverity.Error, - semanticId: pair.Key.value, - objectId: draft.id, - location: new DiagnosticLocation(draft.path.ToString()))).ToArray(); + Diagnostic[] diagnostics = draft.nodeErrors.Select(pair => new Diagnostic("SHADER_NODE_INVALID", $"Node '{pair.Key.value}' cannot be evaluated: {pair.Value}", DiagnosticSeverity.Error, semanticId: pair.Key.value, objectId: draft.id, location: new DiagnosticLocation(draft.path.ToString()))).ToArray(); Diagnostics.Set(draft.id, C_NODE_DIAGNOSTIC_GROUP, diagnostics, draft.path.ToString()); } private static void ClearNodeDiagnostics(Draft draft) => Diagnostics.Clear(draft.id, C_NODE_DIAGNOSTIC_GROUP); - internal Draft Open(AssetFileEntry entry) => Open(entry, AssetId(entry)); - private Draft Open( AssetFileEntry entry, Guid id @@ -180,14 +171,16 @@ out Draft draft return true; } - internal GraphDocumentController Controller(Draft draft) - => m_graphs.TryOpenDocument(draft.id, interactions.history, out GraphDocumentController? controller) - ? controller! : throw new InvalidOperationException("The Shader graph document is not available."); - - internal Guid AssetId(AssetFileEntry entry) - => assets.TryGetInfo(entry.assetPath, out AssetInfo? info) && info is not null ? info.persistentId - : throw new InvalidOperationException("The shader asset identity is not available yet."); - + internal GraphDocumentController Controller(Draft draft) => m_graphs.TryOpenDocument( + draft.id, + interactions.history, + out GraphDocumentController? controller + ) ? controller! : throw new InvalidOperationException("The Shader graph document is not available."); + internal Guid AssetId(AssetFileEntry entry) => assets.TryGetInfo( + entry.assetPath, + out AssetInfo? info + ) + && info is not null ? info.persistentId : throw new InvalidOperationException("The shader asset identity is not available yet."); internal bool TryGet( AssetFileEntry entry, out Draft draft @@ -213,22 +206,26 @@ internal void RefreshCompilation(Draft draft) draft.compilationDiagnostics = ""; return; } + if (!assets.TryGetInfo(draft.id, out AssetInfo? info) || info is null) { draft.compilationStatus = "Source unavailable"; return; } + if (info.status != AssetImportStatus.Imported) { draft.compilationStatus = "Import " + info.status + " · see diagnostics"; draft.compilationDiagnostics = string.Join("\n", info.diagnostics); return; } + if (!assets.TryLoad(draft.id, out ShaderAsset? shader) || shader is null || shader.isMissing) { draft.compilationStatus = "Waiting for import"; return; } + // Compilation observes only the imported asset, never the unsaved document. EditorShaderCompilationSnapshot snapshot = m_compilation.Request(shader, RenderShaderVariant.empty); draft.compilationStatus = snapshot.state switch @@ -240,7 +237,8 @@ internal void RefreshCompilation(Draft draft) draft.compilationDiagnostics = string.Join("\n", snapshot.diagnostics.Select(static value => value.code + ": " + value.message)); draft.diagnostics = snapshot.diagnostics.ToArray(); } - catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or InvalidOperationException or ArgumentException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.compilationStatus = "Compilation unavailable"; draft.compilationDiagnostics = failure.Message; @@ -265,9 +263,7 @@ internal void RemoveNodes(Draft draft) if (draft.activeStage is GraphNodeId stage && controller.document.FindNode(stage) is null) draft.activeStage = null; draft.navigation.Cancel(); - draft.dragging = draft.boxSelecting = false; - draft.dragPreview.Clear(); - draft.canvas.CancelConnection(); + draft.pointer.Cancel(); } internal void Changed(Draft draft) @@ -284,12 +280,14 @@ internal void Changed(Draft draft) File.Delete(recovery); return; } + try { PreserveRecovery(draft, controller); draft.error = ""; } - catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.error = "Recovery could not be written: " + failure.Message; } @@ -318,7 +316,8 @@ internal bool Save(Draft draft) // Import runs at the next editor update, independently of persistence success and watcher timing. return true; } - catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.error = failure.Message; draft.status = "Not saved · recovery retained"; @@ -344,7 +343,8 @@ internal bool Reload(Draft draft) File.Delete(recovery); return true; } - catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.error = failure.Message; return false; @@ -368,6 +368,8 @@ protected override void OnStart(EditorContext editor) templates = m_lifetime.Own(new ShaderGraphTemplateRegistry(m_types)); targets = m_lifetime.Own(new ShaderTargetRegistry(m_types)); _ = m_lifetime.Own(interactions.documents.RegisterProvider(new Provider(this))); + var operations = m_lifetime.Own(interactions.events.CreateHub(int.MaxValue - 1)); + operations.Listen(_ => CompleteCanvasEdits()); } catch (Exception failure) { @@ -379,6 +381,7 @@ protected override void OnStart(EditorContext editor) { throw new AggregateException(failure, retirement); } + throw; } } @@ -398,7 +401,8 @@ protected override void OnUpdate(EditorContext editor) { _ = assets.Import(path); } - catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { foreach (Draft draft in m_drafts.Values.Where(draft => draft.path == path)) { @@ -407,11 +411,13 @@ protected override void OnUpdate(EditorContext editor) } } } + if (m_sourceRevision != assets.revision) { SynchronizeSources(); m_sourceRevision = assets.revision; } + foreach (Draft draft in m_drafts.Values) { GraphDocumentController controller = Controller(draft); @@ -459,11 +465,13 @@ protected override void OnStop(EditorContext editor) // Recovery is not an asset save. Closing the panel, shutdown and reload must not apply a draft. foreach (Draft draft in m_drafts.Values) { + CancelCanvasEdit(draft); if (Controller(draft).isDirty) PreserveRecovery(draft, Controller(draft)); ClearNodeDiagnostics(draft); ReleasePreview(draft); } + m_lifetime?.Dispose(); m_lifetime = null; m_frontends = null; @@ -486,21 +494,26 @@ private void Open(EditorDocumentContext document) if (recovery.assetId != document.assetId) throw new InvalidDataException("Shader recovery identity does not match its document."); } + if (assets.TryGetInfo(document.assetId, out AssetInfo? info) && info is not null && info.status != AssetImportStatus.Missing) { path = info.assetPath; interactions.documents.UpdateAssetPath(document.documentId, path.ToString()); } + ShaderGraphSourceSnapshot? source = null; string missing = ""; try { source = m_sources.Read(path); } - catch (Exception failure) when ((recovery is not null && failure is IOException or InvalidOperationException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((recovery is not null + && failure is IOException or InvalidOperationException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { missing = failure.Message; } + GraphDocumentController controller = m_graphs.OpenDocument(document.assetId, source?.document ?? new GraphDocument(), interactions.history); string staleRecovery = ""; bool canRecover = recovery is not null && (source is null || recovery.hash == source.contentHash); @@ -509,7 +522,9 @@ private void Open(EditorDocumentContext document) else if (recovery is not null) staleRecovery = ArchiveStaleRecovery(document.assetId, recoveryPath); var draft = new Draft(document.assetId, document.documentId, path, recovery?.hash ?? source!.contentHash, source?.isReadOnly ?? true) - { observedRevision = controller.revision }; + { + observedRevision = controller.revision + }; if (m_views.TryGetValue(draft.id, out ViewState view)) draft.canvas.SetViewport(new(view.x, view.y), view.zoom); else @@ -523,6 +538,7 @@ private void Open(EditorDocumentContext document) draft.status = "Source updated · stale recovery was not applied"; draft.error = "An unsaved recovery belonged to an older source revision and was archived at: " + staleRecovery; } + if (source is null) { m_graphs.SetAvailability(draft.id, false); @@ -533,10 +549,10 @@ private void Open(EditorDocumentContext document) private void PreserveRecovery( Draft draft, GraphDocumentController controller - ) - => AtomicFile.WriteAllBytes(RecoveryPath(draft.id), serialization.Serialize(new RecoveryData - { assetId = draft.id, path = draft.path.ToString(), hash = draft.hash, graph = GraphDocumentCodec.Encode(CaptureSource(draft, controller.document), serialization) })); - + ) => AtomicFile.WriteAllBytes( + RecoveryPath(draft.id), + serialization.Serialize(new RecoveryData { assetId = draft.id, path = draft.path.ToString(), hash = draft.hash, graph = GraphDocumentCodec.Encode(CaptureSource(draft, controller.document), serialization) }) + ); private GraphDocument CaptureSource( Draft draft, GraphDocument graph @@ -549,16 +565,12 @@ GraphDocument graph } private string RecoveryPath(Guid id) => Path.Combine(assets.libraryRoot, "Editor", "ShaderRecovery", id.ToString("N") + ".inno"); - private static string ArchiveStaleRecovery( Guid id, string recoveryPath ) { - string directory = Path.GetDirectoryName(recoveryPath) - ?? throw new InvalidOperationException("The Shader recovery directory is unavailable."); - string archive = Path.Combine( - directory, - id.ToString("N") + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ".stale.inno"); + string directory = Path.GetDirectoryName(recoveryPath) ?? throw new InvalidOperationException("The Shader recovery directory is unavailable."); + string archive = Path.Combine(directory, id.ToString("N") + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ".stale.inno"); File.Move(recoveryPath, archive); return archive; } @@ -581,6 +593,7 @@ private void SynchronizeSources() draft.error = "Source missing · graph and undo history retained"; continue; } + try { bool wasMissing = !controller.isAvailable; @@ -595,19 +608,22 @@ private void SynchronizeSources() draft.error = ""; continue; } + if (controller.isDirty) { PreserveRecovery(draft, controller); draft.error = "Source changed externally · reload from disk or copy these edits to a project shader"; continue; } + m_graphs.RebindDocument(draft.id, source.document); draft.hash = source.contentHash; draft.observedRevision = controller.revision; draft.error = ""; draft.status = "Source updated · compilation is separate"; } - catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is IOException or UnauthorizedAccessException or InvalidOperationException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { draft.error = failure.Message; } @@ -615,20 +631,19 @@ private void SynchronizeSources() } private void RememberView(Draft draft) => m_views[draft.id] = new(draft.id, draft.canvas.pan.x, draft.canvas.pan.y, draft.canvas.zoom); - private readonly record struct ViewState( Guid assetId, float x, float y, float zoom ); - private sealed class Provider(ShaderEditorDocuments owner) : EditorDocumentProvider { /// /// Gets the stable identity used to reference this value across subsystem boundaries. /// -public override string id => "inno.shader.graph"; + public override string id => "inno.shader.graph"; + /// /// Checks whether this document handler supports the selected asset. /// @@ -638,14 +653,14 @@ private sealed class Provider(ShaderEditorDocuments owner) : EditorDocumentProvi /// /// when the documented condition is satisfied; otherwise, . /// -public override bool CanOpen(string assetPath) => assetPath.EndsWith(".ishader", StringComparison.OrdinalIgnoreCase); + public override bool CanOpen(string assetPath) => assetPath.EndsWith(".ishader", StringComparison.OrdinalIgnoreCase); /// /// Opens the requested resource and establishes its active lifetime. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -public override void Open(EditorDocumentContext context) => owner.Open(context); + public override void Open(EditorDocumentContext context) => owner.Open(context); /// /// Persists the supplied value through the configured storage contract. /// @@ -655,7 +670,7 @@ private sealed class Provider(ShaderEditorDocuments owner) : EditorDocumentProvi /// /// when the documented condition is satisfied; otherwise, . /// -public override bool Save(EditorDocumentContext context) => owner.Save(owner.m_drafts[context.assetId]); + public override bool Save(EditorDocumentContext context) => owner.Save(owner.m_drafts[context.assetId]); /// /// Restores the draft from the latest imported asset state. /// @@ -665,14 +680,14 @@ private sealed class Provider(ShaderEditorDocuments owner) : EditorDocumentProvi /// /// when the documented condition is satisfied; otherwise, . /// -public override bool Revert(EditorDocumentContext context) => owner.Reload(owner.m_drafts[context.assetId]); + public override bool Revert(EditorDocumentContext context) => owner.Reload(owner.m_drafts[context.assetId]); /// /// Closes the active resource and releases its operation-scoped state. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -public override void Close(EditorDocumentContext context) + public override void Close(EditorDocumentContext context) { // The shared host already applied Save/Discard/Cancel policy. Close must never turn Discard into Save. if (!owner.m_drafts.TryGetValue(context.assetId, out Draft? draft)) @@ -681,6 +696,7 @@ public override void Close(EditorDocumentContext context) if (File.Exists(recovery)) File.Delete(recovery); owner.RememberView(draft); + CancelCanvasEdit(draft); ClearNodeDiagnostics(draft); owner.ReleasePreview(draft); draft.navigation.Cancel(); @@ -695,7 +711,8 @@ internal sealed class Draft( AssetPath path, string hash, bool readOnly - ) { + ) + { internal readonly Guid id = id; internal readonly Guid documentId = documentId; internal AssetPath path = path; @@ -726,16 +743,20 @@ bool readOnly internal GraphNodeId? activeStage; internal string inspectedPass = ""; internal GraphNodeId[] inspectedNodes = []; + internal ShaderInspectionSelection? inspectionSelection; internal string valueGesture = Guid.NewGuid().ToString("N"); internal readonly EditorPlanarNavigation navigation = new(); - internal bool dragging; - internal bool boxSelecting; + private GraphCanvasPointerController? m_pointer; + internal GraphCanvasPointerController pointer => m_pointer ??= new(canvas); + internal IReadOnlyDictionary? pendingMove; + internal readonly GraphCanvasLayout layout = new(); + internal ulong layoutRevision = ulong.MaxValue; + internal long layoutAssetRevision = long.MinValue; + internal long layoutTypeRevision = long.MinValue; internal GraphNodeId? resizingNode; internal float resizeStartWidth; internal float resizePreviewWidth; - internal System.Numerics.Vector2 pointerStart; - internal readonly Dictionary dragStart = []; - internal readonly Dictionary dragPreview = []; + internal System.Numerics.Vector2 resizePointerStart; internal ulong portRevision = ulong.MaxValue; internal long assetRevision = long.MinValue; internal long typeRevision = long.MinValue; @@ -750,19 +771,26 @@ private sealed class RecoveryData : ISerializable /// /// Gets or sets the persistent asset identity. /// - [SerializableProperty] public Guid assetId { get; set; } + [SerializableProperty] + public Guid assetId { get; set; } + /// /// Gets or sets the diagnostic source path. /// - [SerializableProperty] public string path { get; set; } = ""; + [SerializableProperty] + public string path { get; set; } = ""; + /// /// Gets or sets the expected disk source fingerprint. /// - [SerializableProperty] public string hash { get; set; } = ""; + [SerializableProperty] + public string hash { get; set; } = ""; + /// /// Gets or sets the complete neutral unsaved graph. /// - [SerializableProperty] public byte[] graph { get; set; } = []; + [SerializableProperty] + public byte[] graph { get; set; } = []; } } @@ -771,17 +799,17 @@ internal struct ShaderPortSnapshot /// /// Gets the stable identity used to reference this value across subsystem boundaries. /// -public string id { get; set; } + public string id { get; set; } /// /// Gets the graph port's shader value type. /// -public ShaderGraphType type { get; set; } + public ShaderGraphType type { get; set; } /// /// Gets the graph port's input or output direction. /// -public GraphPortDirection direction { get; set; } + public GraphPortDirection direction { get; set; } /// /// Gets whether required is active for the current instance. /// -public bool required { get; set; } + public bool required { get; set; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inno.Editor.Panel.ShaderEditor.csproj b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inno.Editor.Panel.ShaderEditor.csproj index 3e0433f91..7a2d6958b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inno.Editor.Panel.ShaderEditor.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inno.Editor.Panel.ShaderEditor.csproj @@ -4,61 +4,34 @@ 14.0 disable enable - true true $(WarningsAsErrors);CS1572;CS1573;CS1591 Editor - + + + + - - - - - + + + + - - + + - - - - + + + + - + - + @@ -66,5 +39,4 @@ - diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inspection/ShaderInspectionDrawers.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inspection/ShaderInspectionDrawers.cs index 1087b196c..7c71b5f09 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inspection/ShaderInspectionDrawers.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Inspection/ShaderInspectionDrawers.cs @@ -1,3 +1,4 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.IO; using Inno.Assets; @@ -7,19 +8,17 @@ using Inno.Editor.Inspection; using Inno.Editor.Shaders; using Inno.Rendering; -using ImGuiApi = Inno.Native.ImGui.ImGui; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - [InspectionDrawer(typeof(AssetFileEntry), priority: 100, conditional: true)] internal sealed class ShaderSourceDrawer(IInspectionIconProvider icons) : InspectionDrawer { /// /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => "S"; + public override string icon => "S"; + /// /// Retrieves the current icon from authoritative state. /// @@ -32,10 +31,10 @@ internal sealed class ShaderSourceDrawer(IInspectionIconProvider /// /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - AssetFileEntry target -) => icons.GetIcon(target); + protected override string GetIcon( + InspectionDrawContext context, + AssetFileEntry target + ) => icons.GetIcon(target); /// /// Checks whether this drawer supports the selected Inspector target. /// @@ -45,8 +44,7 @@ AssetFileEntry target /// /// when the documented condition is satisfied; otherwise, . /// -protected override bool CanInspect(AssetFileEntry target) - => !target.isDirectory && target.extension.Equals(".ishader", StringComparison.OrdinalIgnoreCase); + protected override bool CanInspect(AssetFileEntry target) => !target.isDirectory && target.extension.Equals(".ishader", StringComparison.OrdinalIgnoreCase); /// /// Binds a caller-visible label to the current inspection target. /// @@ -59,11 +57,10 @@ protected override bool CanInspect(AssetFileEntry target) /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - AssetFileEntry target -) - => (target.nameWithoutExtension, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + AssetFileEntry target + ) => (target.nameWithoutExtension, null); /// /// Renders the value presentation for the current editor frame. /// @@ -73,16 +70,16 @@ AssetFileEntry target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - AssetFileEntry target -) { + protected override void Draw( + InspectionDrawContext context, + AssetFileEntry target + ) { if (context.interactions.TryGetModule(out var documents)) { if (documents!.TryOpen(target, out ShaderEditorDocuments.Draft draft)) new ShaderEditorCanvas(documents, draft).DrawInspector(context, []); else - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); } } } @@ -93,7 +90,8 @@ internal sealed class ShaderSelectionDrawer(IInspectionIconProvider /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => "S"; + public override string icon => "S"; + /// /// Retrieves the current icon from authoritative state. /// @@ -106,13 +104,20 @@ internal sealed class ShaderSelectionDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - ShaderInspectionSelection target -) - => context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(target.assetId, out AssetInfo? info) && info is not null - && documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry) ? icons.GetIcon(entry) : icon; + protected override string GetIcon( + InspectionDrawContext context, + ShaderInspectionSelection target + ) => context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + target.assetId, + out AssetInfo? info + ) + && info is not null + && documents.assets.TryGetFileSystemEntry( + info.assetPath, + out AssetFileEntry entry + ) ? icons.GetIcon(entry) : icon; /// /// Binds a caller-visible label to the current inspection target. /// @@ -125,15 +130,21 @@ ShaderInspectionSelection target /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - ShaderInspectionSelection target -) { - if (context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(target.assetId, out AssetInfo? info) && info is not null) + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + ShaderInspectionSelection target + ) { + if (context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + target.assetId, + out AssetInfo? info + ) + && info is not null) return (Path.GetFileName(info.assetPath.localPath), null); return ("Shader", null); } + /// /// Renders the header presentation for the current editor frame. /// @@ -143,27 +154,38 @@ ShaderInspectionSelection target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - ShaderInspectionSelection target -) { - if (!context.interactions.TryGetModule(out var documents) || documents is null - || !documents.assets.TryGetInfo(target.assetId, out AssetInfo? info) || info is null - || !documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry)) + protected override void DrawHeader( + InspectionDrawContext context, + ShaderInspectionSelection target + ) { + if (!context.interactions.TryGetModule(out var documents) + || documents is null + || !documents.assets.TryGetInfo( + target.assetId, + out AssetInfo? info + ) + || info is null + || !documents.assets.TryGetFileSystemEntry( + info.assetPath, + out AssetFileEntry entry + )) { - Widget.ColoredText(EditorPalette.assetBreadcrumbText, "Node selection unavailable"); + EditorUI.current.widgets.ColoredText(EditorPalette.contextText, "Node selection unavailable"); return; } + if (!documents.TryOpen(entry, out ShaderEditorDocuments.Draft draft)) { - Widget.ColoredText(EditorPalette.assetBreadcrumbText, "Waiting for Shader asset import…"); + EditorUI.current.widgets.ColoredText(EditorPalette.contextText, "Waiting for Shader asset import…"); return; } + if (target.nodes.Count == 1 && documents.Controller(draft).document.FindNode(target.nodes[0]) is GraphNodeRecord node) - Widget.ColoredText(EditorPalette.assetBreadcrumbText, "Node: " + new ShaderEditorCanvas(documents, draft).Title(node)); + EditorUI.current.widgets.ColoredText(EditorPalette.contextText, "Node: " + new ShaderEditorCanvas(documents, draft).Title(node)); else - Widget.ColoredText(EditorPalette.assetBreadcrumbText, $"Nodes: {target.nodes.Count} selected"); + EditorUI.current.widgets.ColoredText(EditorPalette.contextText, $"Nodes: {target.nodes.Count} selected"); } + /// /// Renders the value presentation for the current editor frame. /// @@ -173,22 +195,22 @@ ShaderInspectionSelection target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - ShaderInspectionSelection target -) { + protected override void Draw( + InspectionDrawContext context, + ShaderInspectionSelection target + ) { if (!context.interactions.TryGetModule(out var documents) || documents is null) return; - if (!documents.assets.TryGetInfo(target.assetId, out AssetInfo? info) || info is null - || !documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry)) + if (!documents.assets.TryGetInfo(target.assetId, out AssetInfo? info) || info is null || !documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry)) { - Widget.WrappedText("Shader source unavailable. Selection identities are retained."); + EditorUI.current.widgets.WrappedText("Shader source unavailable. Selection identities are retained."); return; } + if (documents.TryOpen(entry, out ShaderEditorDocuments.Draft draft)) new ShaderEditorCanvas(documents, draft).DrawInspector(context, target.nodes); else - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); } } @@ -198,7 +220,8 @@ internal sealed class ShaderAssetDrawer(IInspectionIconProvider /// /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => "S"; + public override string icon => "S"; + /// /// Retrieves the current icon from authoritative state. /// @@ -211,12 +234,15 @@ internal sealed class ShaderAssetDrawer(IInspectionIconProvider /// /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - ShaderAsset target -) - => context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetFileSystemEntry(target.assetPath, out AssetFileEntry entry) ? icons.GetIcon(entry) : icon; + protected override string GetIcon( + InspectionDrawContext context, + ShaderAsset target + ) => context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetFileSystemEntry( + target.assetPath, + out AssetFileEntry entry + ) ? icons.GetIcon(entry) : icon; /// /// Binds a caller-visible label to the current inspection target. /// @@ -229,10 +255,10 @@ ShaderAsset target /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - ShaderAsset target -) => (target.name, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + ShaderAsset target + ) => (target.name, null); /// /// Renders the value presentation for the current editor frame. /// @@ -242,10 +268,10 @@ ShaderAsset target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - ShaderAsset target -) { + protected override void Draw( + InspectionDrawContext context, + ShaderAsset target + ) { if (!context.interactions.TryGetModule(out var documents) || documents is null) return; if (!documents.assets.TryGetFileSystemEntry(target.assetPath, out AssetFileEntry entry)) @@ -253,6 +279,6 @@ ShaderAsset target if (documents.TryOpen(entry, out ShaderEditorDocuments.Draft draft)) new ShaderEditorCanvas(documents, draft).DrawInspector(context, []); else - Widget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Waiting for Shader asset import…"); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/CreateShaderAsset.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/CreateShaderAsset.cs index f4ea9c5c3..b4b964f10 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/CreateShaderAsset.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/CreateShaderAsset.cs @@ -5,15 +5,15 @@ using Inno.Assets.Pipeline; using Inno.Core.Graphs; using Inno.Editor.Interactions; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Rendering.Shaders; namespace Inno.Editor.Panel.ShaderEditor; - [EditorAction("shader/create-asset", "panel/asset.file-browser")] internal sealed class CreateShaderAsset( ShaderEditorDocuments documents, - AssetEditorModule browser + AssetEditingModule browser ) : EditorAction { /// @@ -28,9 +28,9 @@ AssetEditorModule browser protected override EditorActionState Query(EditorActionContext context) { AssetPath path = AssetPath.Parse(context.target); - return documents.assets.sourceMounts.Any(mount => mount.id == path.source && !mount.isReadOnly) - ? EditorActionState.enabled : EditorActionState.disabled; + return documents.assets.sourceMounts.Any(mount => mount.id == path.source && !mount.isReadOnly) ? EditorActionState.enabled : EditorActionState.disabled; } + /// /// Executes the prepared operation and publishes only a completed result. /// @@ -51,10 +51,14 @@ protected override void Execute(EditorActionContext context) path = new(parent.source, prefix + (index++ == 0 ? "New Shader" : "New Shader " + index) + ".ishader"); } while (File.Exists(mount.Resolve(path.localPath)) || File.Exists(mount.Resolve(path.localPath) + ".imeta")); - GraphDocument graph = (documents.templates ?? throw new InvalidOperationException("Shader templates have not started.")) - .Create(context.argument, documents.serialization, documents.context); + GraphDocument graph = (documents.templates ?? throw new InvalidOperationException("Shader templates have not started.")).Create( + context.argument, + documents.serialization, + documents.context + ); AssetFileEntry created = browser.CreateSource(path, GraphDocumentCodec.Encode(graph, documents.serialization)); - browser.BeginCreatedSourceRename(created); + _ = context.interactions.For("panel/asset.file-browser", created).Select(); + _ = context.interactions.For("panel/asset.file-browser", created).Execute("file-browser/rename"); context.interactions.OpenPanel("rendering.shader-editor"); } } @@ -71,19 +75,15 @@ internal sealed class ShaderTemplateMenu(ShaderEditorDocuments documents) : Edit /// /// The builder consumed by build; ownership remains with the caller unless explicitly stated otherwise. /// -public override void Build( - EditorMenuContext context, - EditorMenuBuilder builder -) { + public override void Build( + EditorMenuContext context, + EditorMenuBuilder builder + ) { if (documents.templates is null) return; builder.AddGroup("Create/Rendering", order: 200, separatorBefore: true); builder.AddGroup("Create/Rendering/Shaders", order: 0); foreach (ShaderGraphTemplateInfo template in documents.templates.templates) - builder.Add( - "Create/Rendering/Shaders/" + template.displayName, - "shader/create-asset", - argument: template.id, - order: 0); + builder.Add("Create/Rendering/Shaders/" + template.displayName, "shader/create-asset", argument: template.id, order: 0); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasActions.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasActions.cs index 6f701608b..158fe9913 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasActions.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasActions.cs @@ -1,3 +1,6 @@ +using Inno.Editor.Core; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Shaders.ImGui; using System; using System.Collections.Generic; using System.Linq; @@ -12,7 +15,6 @@ using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Panel.ShaderEditor; - [EditorMenuSource(ShaderEditorCanvas.C_AREA)] internal sealed class ShaderCanvasMenu(ShaderEditorDocuments documents) : EditorMenuSource { @@ -41,53 +43,73 @@ EditorMenuBuilder builder builder.Add("Create/Outputs/Fragment Output", "shader/create-output", order: 10, argument: "Fragment"); builder.Add("Create/Outputs/Compute Output", "shader/create-output", order: 20, argument: "Compute"); } + foreach (string definition in documents.nodes.definitionIds) - if (definition is not ("inno.shader.source" or ShaderGraphNodes.callDefinitionId) - && documents.CanCreate(draft, new(definition))) + if (definition is not ("inno.shader.source" or ShaderGraphNodes.callDefinitionId) && documents.CanCreate(draft, new(definition))) { string category = Category(definition); int order = CategoryOrder(category); - if (documents.drawers?.TryGetPresentation(definition, out ShaderNodePresentation presentation) == true - && presentation.createPath.Length != 0) + if (documents.drawers?.TryGetPresentation(definition, out ShaderNodePresentation presentation) == true && presentation.createPath.Length != 0) { category = presentation.createPath; order = presentation.createOrder; builder.AddGroup("Create/" + category, order, presentation.separatorBefore); } - builder.Add("Create/" + category + "/" + (documents.drawers?.GetDisplayName(definition) - ?? Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.NicifyName(definition.Replace("inno.shader.", "", StringComparison.Ordinal).Replace('-', ' '))), - "shader/create-node", order, argument: new ShaderNodeCreation(definition)); + + builder.Add( + "Create/" + category + "/" + (documents.drawers?.GetDisplayName(definition) ?? EditorDisplayNames.Nicify(definition.Replace("inno.shader.", "", StringComparison.Ordinal).Replace('-', ' '))), + "shader/create-node", + order, + argument: new ShaderNodeCreation(definition) + ); } + var functionLibraries = new List<(AssetFileEntry source, AssetInfo info, ShaderFunctionAsset library)>(); foreach (AssetFileEntry source in documents.assets.GetFileSystemEntries(includeDirectories: false)) - if (source.extension == ".ishadersource" && documents.assets.TryGetInfo(source.assetPath, out var info) && info is not null - && documents.assets.TryLoad(info.persistentId, out ShaderFunctionAsset? library) && library is not null && !library.isMissing) + if (source.extension == ".ishadersource" + && documents.assets.TryGetInfo( + source.assetPath, + out var info + ) + && info is not null + && documents.assets.TryLoad( + info.persistentId, + out ShaderFunctionAsset? library + ) + && library is not null + && !library.isMissing) functionLibraries.Add((source, info, library)); - foreach (var catalog in functionLibraries - .Select(static value => (path: NormalizeCatalog(value.library.catalogPath), value.library.catalogOrder)) - .Where(static value => value.path.Length != 0) - .GroupBy(static value => value.path.Split('/')[0], StringComparer.Ordinal) - .Select(static group => (name: group.Key, order: group.Min(static value => value.catalogOrder))) - .OrderBy(static value => value.order).ThenBy(static value => value.name, StringComparer.Ordinal)) + foreach (var catalog in functionLibraries.Select(static value => (path: NormalizeCatalog(value.library.catalogPath), value.library.catalogOrder)).Where(static value => value.path.Length != 0).GroupBy( + static value => value.path.Split('/')[0], + StringComparer.Ordinal + ).Select(static group => (name: group.Key, order: group.Min(static value => value.catalogOrder))).OrderBy(static value => value.order).ThenBy( + static value => value.name, + StringComparer.Ordinal + )) builder.AddGroup("Create/Functions/" + catalog.name, order: 500 + catalog.order, separatorBefore: true); - foreach ((AssetFileEntry source, AssetInfo info, ShaderFunctionAsset library) in functionLibraries - .OrderBy(static value => value.library.catalogOrder) - .ThenBy(static value => value.source.nameWithoutExtension, StringComparer.Ordinal)) + foreach ((AssetFileEntry source, AssetInfo info, ShaderFunctionAsset library)in functionLibraries.OrderBy(static value => value.library.catalogOrder).ThenBy( + static value => value.source.nameWithoutExtension, + StringComparer.Ordinal + )) foreach (string function in library.exports) - if (documents.CanCreate(draft, new("inno.shader.source", info.persistentId, function))) - { - string catalog = NormalizeCatalog(library.catalogPath); - string prefix = "Create/Functions/" + (catalog.Length == 0 ? "General" : catalog); - builder.Add(prefix + "/" + source.nameWithoutExtension + "/" + function, "shader/create-node", - order: 500 + library.catalogOrder, - argument: new ShaderNodeCreation("inno.shader.source", info.persistentId, function)); - } - foreach (AssetFileEntry source in documents.assets.GetFileSystemEntries(includeDirectories: false) - .Where(static entry => entry.extension == ".ishader") - .OrderBy(static entry => entry.assetPath.ToString(), StringComparer.Ordinal)) + if (documents.CanCreate(draft, new("inno.shader.source", info.persistentId, function))) + { + string catalog = NormalizeCatalog(library.catalogPath); + string prefix = "Create/Functions/" + (catalog.Length == 0 ? "General" : catalog); + builder.Add( + prefix + "/" + source.nameWithoutExtension + "/" + function, + "shader/create-node", + order: 500 + library.catalogOrder, + argument: new ShaderNodeCreation("inno.shader.source", info.persistentId, function) + ); + } + + foreach (AssetFileEntry source in documents.assets.GetFileSystemEntries(includeDirectories: false).Where(static entry => entry.extension == ".ishader").OrderBy( + static entry => entry.assetPath.ToString(), + StringComparer.Ordinal + )) { - if (!documents.assets.TryGetInfo(source.assetPath, out AssetInfo? info) || info is null - || info.persistentId == draft.id || info.status != AssetImportStatus.Imported) + if (!documents.assets.TryGetInfo(source.assetPath, out AssetInfo? info) || info is null || info.persistentId == draft.id || info.status != AssetImportStatus.Imported) continue; ShaderGraphNodeInterface nodeInterface; try @@ -96,20 +118,20 @@ EditorMenuBuilder builder } catch (Exception failure) when ((failure is InvalidOperationException or ArgumentException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) - { - continue; - } + { + continue; + } + var creation = new ShaderNodeCreation(ShaderGraphNodes.callDefinitionId, info.persistentId); if (!documents.CanCreate(draft, creation)) continue; string catalog = NormalizeCatalog(nodeInterface.createPath); string root = nodeInterface.kind == ShaderGraphNodeKind.DomainOutput ? "Domain Outputs" : "Graph Nodes"; string group = "Create/" + root + (catalog.Length == 0 ? "" : "/" + catalog); - builder.AddGroup(group, order: nodeInterface.createOrder, - separatorBefore: nodeInterface.kind == ShaderGraphNodeKind.DomainOutput); - builder.Add(group + "/" + nodeInterface.displayName, "shader/create-node", - order: nodeInterface.createOrder, argument: creation); + builder.AddGroup(group, order: nodeInterface.createOrder, separatorBefore: nodeInterface.kind == ShaderGraphNodeKind.DomainOutput); + builder.Add(group + "/" + nodeInterface.displayName, "shader/create-node", order: nodeInterface.createOrder, argument: creation); } + builder.Add("View/Focus Selection", "shader/focus", order: 900); builder.Add("Edit/Copy", "shader/copy", order: 1000); builder.Add("Edit/Cut", "shader/cut", order: 110); @@ -124,7 +146,6 @@ EditorMenuBuilder builder builder.Add("Assets/Show Shader in File Browser", "shader/reveal-shader", order: 1900); builder.Add("Assets/Show Source in File Browser", "shader/reveal-source", order: 1910); builder.Add("Assets/Copy Shader To Project", "shader/copy-to-project", order: 1920, separatorBefore: true); - static string Category(string definition) => definition switch { "inno.shader.constant" or "inno.shader.stage-input" => "Inputs", @@ -137,7 +158,6 @@ EditorMenuBuilder builder _ when definition.Contains("surface-output", StringComparison.Ordinal) => "Domain Outputs", _ => "Domain" }; - static int CategoryOrder(string category) => category switch { "Outputs" => 0, @@ -151,9 +171,7 @@ _ when definition.Contains("surface-output", StringComparison.Ordinal) => "Domai "Domain Outputs" => 800, _ => 850 }; - - static string NormalizeCatalog(string value) - => string.Join('/', value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); + static string NormalizeCatalog(string value) => string.Join('/', value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); } } @@ -169,39 +187,33 @@ internal sealed class CreateShaderOutput(ShaderEditorDocuments documents) : Edit /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) + protected override EditorActionState Query(EditorActionContext context) { - if (!documents.TryGet(context.target, out var draft) || draft.readOnly - || !Enum.TryParse(context.argument, out Inno.Rendering.ShaderStage stage)) - return EditorActionState.disabled; + if (!documents.TryGet(context.target, out var draft) || draft.readOnly || !Enum.TryParse(context.argument, out Inno.Rendering.ShaderStage stage)) + return EditorActionState.disabled; GraphDocument graph = documents.Controller(draft).document; if (ShaderGraphDocument.ReadTarget(graph, documents.serialization, documents.context).Length != 0) return EditorActionState.disabled; - bool exists = graph.nodes.Where(static node => node.definitionId == ShaderGraphDocument.outputDefinitionId) - .Any(node => ShaderGraphDocument.Read(node, "settings", new ShaderGraphStageSettings(), documents.serialization, documents.context).stage == stage); + bool exists = graph.nodes.Where(static node => node.definitionId == ShaderGraphDocument.outputDefinitionId).Any(node => ShaderGraphDocument.Read(node, "settings", new ShaderGraphStageSettings(), documents.serialization, documents.context).stage == stage); return exists ? EditorActionState.disabled : EditorActionState.enabled; } + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); var controller = documents.Controller(draft); GraphDocument graph = controller.document.Clone(); Inno.Rendering.Assets.ShaderDefinition definition = ShaderGraphDocument.ReadDefinition(graph, documents.serialization, documents.context); Inno.Rendering.ShaderStage stage = Enum.Parse(context.argument); - Inno.Rendering.ShaderProgramKind kind = stage == Inno.Rendering.ShaderStage.Compute - ? Inno.Rendering.ShaderProgramKind.Compute : Inno.Rendering.ShaderProgramKind.Raster; + Inno.Rendering.ShaderProgramKind kind = stage == Inno.Rendering.ShaderStage.Compute ? Inno.Rendering.ShaderProgramKind.Compute : Inno.Rendering.ShaderProgramKind.Raster; ShaderGraphPassProgram[] programs = ShaderGraphPrograms.Read(graph, documents.serialization, documents.context); - ShaderGraphPassProgram? incomplete = kind == Inno.Rendering.ShaderProgramKind.Raster - ? programs.FirstOrDefault(program => definition.passes.Any(pass => pass.name == program.pass && pass.programKind == kind) - && !program.stages.Select(id => graph.FindNode(new(id))).OfType() - .Any(node => ShaderGraphDocument.Read(node, "settings", new ShaderGraphStageSettings(), documents.serialization, documents.context).stage == stage)) - : null; + ShaderGraphPassProgram? incomplete = kind == Inno.Rendering.ShaderProgramKind.Raster ? programs.FirstOrDefault(program => definition.passes.Any(pass => pass.name == program.pass && pass.programKind == kind) && !program.stages.Select(id => graph.FindNode(new(id))).OfType().Any(node => ShaderGraphDocument.Read(node, "settings", new ShaderGraphStageSettings(), documents.serialization, documents.context).stage == stage)) : null; string name; string[] stages; if (incomplete is ShaderGraphPassProgram existing && existing.pass is not null) @@ -215,15 +227,30 @@ protected override void Execute(EditorActionContext cont name = prefix; for (int suffix = 2; definition.passes.Any(pass => pass.name == name); suffix++) name = prefix + " " + suffix; - definition.passes = [.. definition.passes, new(name, kind)]; - graph.SetMetadata(ShaderGraphDocument.definitionKey, ShaderGraphDocument.Encode(documents.serialization.Serialize(definition, documents.context), documents.serialization, documents.context)); + definition.passes = [..definition.passes, new(name, kind)]; + graph.SetMetadata( + ShaderGraphDocument.definitionKey, + ShaderGraphDocument.Encode(documents.serialization.Serialize(definition, documents.context), documents.serialization, documents.context) + ); stages = []; } + GraphNodeId id = new(Guid.NewGuid().ToString("N")); - var node = new GraphNodeRecord(id, ShaderGraphDocument.outputDefinitionId) { position = draft.menuPosition }; - node.SetValue("settings", ShaderGraphDocument.Encode(new ShaderGraphStageSettings { stage = stage, - outputs = stage == Inno.Rendering.ShaderStage.Compute ? [] : [new() { id = stage == Inno.Rendering.ShaderStage.Vertex ? "position" : "color", - kind = stage == Inno.Rendering.ShaderStage.Vertex ? ShaderIrOutputKind.ClipPosition : ShaderIrOutputKind.Color }] }, documents.serialization, documents.context)); + var node = new GraphNodeRecord(id, ShaderGraphDocument.outputDefinitionId) + { + position = draft.menuPosition + }; + node.SetValue( + "settings", + ShaderGraphDocument.Encode( + new ShaderGraphStageSettings { + stage = stage, + outputs = stage == Inno.Rendering.ShaderStage.Compute ? [] : [new() { id = stage == Inno.Rendering.ShaderStage.Vertex ? "position" : "color", kind = stage == Inno.Rendering.ShaderStage.Vertex ? ShaderIrOutputKind.ClipPosition : ShaderIrOutputKind.Color }] + }, + documents.serialization, + documents.context + ) + ); graph.AddNode(node); graph = ShaderGraphPrograms.Bind(graph, name, stages.Select(static value => new GraphNodeId(value)).Append(id), documents.serialization, documents.context); controller.ReplaceDocument(graph, "Create " + stage + " Output"); @@ -245,16 +272,21 @@ internal sealed class CreateShaderNode(ShaderEditorDocuments documents) : Editor /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => documents.TryGet(context.target, out var draft) && documents.CanCreate(draft, context.argument) ? EditorActionState.enabled : EditorActionState.disabled; + protected override EditorActionState Query(EditorActionContext context) => documents.TryGet( + context.target, + out var draft + ) + && documents.CanCreate( + draft, + context.argument + ) ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The context that supplies state and services for this operation. /// - protected override void Execute(EditorActionContext context) - => documents.Create(documents.Open(context.target), context.argument); + protected override void Execute(EditorActionContext context) => documents.Create(documents.Open(context.target), context.argument); } internal abstract class ShaderSelectionAction(ShaderEditorDocuments documents) : EditorAction @@ -262,15 +294,16 @@ internal abstract class ShaderSelectionAction(ShaderEditorDocuments documents) : /// /// Gets documents presented by this Shader Editor action. /// -protected ShaderEditorDocuments documents { get; } = documents; + protected ShaderEditorDocuments documents { get; } = documents; /// /// Gets whether writes is active for the current instance. /// -protected virtual bool writes => true; + protected virtual bool writes => true; /// /// Gets whether needs selection is active for the current instance. /// -protected virtual bool needsSelection => true; + protected virtual bool needsSelection => true; + /// /// Evaluates the operation's current availability and presentation state. /// @@ -280,10 +313,14 @@ internal abstract class ShaderSelectionAction(ShaderEditorDocuments documents) : /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => documents.TryGet(context.target, out var draft) - && (!writes || !draft.readOnly) && (!needsSelection || draft.canvas.selectedNodes.Count != 0) - ? EditorActionState.enabled : EditorActionState.disabled; + protected override EditorActionState Query(EditorActionContext context) => documents.TryGet( + context.target, + out var draft + ) + && (!writes + || !draft.readOnly) + && (!needsSelection + || draft.canvas.selectedNodes.Count != 0) ? EditorActionState.enabled : EditorActionState.disabled; } [EditorAction("shader/copy", ShaderEditorCanvas.C_AREA)] @@ -293,7 +330,8 @@ internal sealed class CopyShaderNodes(ShaderEditorDocuments documents) : ShaderS /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; + /// /// Executes the prepared operation and publishes only a completed result. /// @@ -315,7 +353,8 @@ internal sealed class DeleteShaderNodes(ShaderEditorDocuments documents) : Shade /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// @@ -364,7 +403,8 @@ internal sealed class PasteShaderNodes(ShaderEditorDocuments documents) : Shader /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Evaluates the operation's current availability and presentation state. /// @@ -374,10 +414,16 @@ internal sealed class PasteShaderNodes(ShaderEditorDocuments documents) : Shader /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => base.Query(context).isEnabled && documents.clipboard is not null - && documents.TryGet(context.target, out var draft) && documents.CanPaste(draft, documents.clipboard) - ? EditorActionState.enabled : EditorActionState.disabled; + protected override EditorActionState Query(EditorActionContext context) => base.Query(context).isEnabled + && documents.clipboard is not null + && documents.TryGet( + context.target, + out var draft + ) + && documents.CanPaste( + draft, + documents.clipboard + ) ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// @@ -405,13 +451,13 @@ internal sealed class DuplicateShaderNodes(ShaderEditorDocuments documents) : Sh /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) + protected override EditorActionState Query(EditorActionContext context) { if (!base.Query(context).isEnabled || !documents.TryGet(context.target, out var draft)) return EditorActionState.disabled; - return draft.canvas.selectedNodes.Any(id => documents.Controller(draft).document.FindNode(id)?.definitionId == ShaderGraphDocument.outputDefinitionId) - ? EditorActionState.disabled : EditorActionState.enabled; + return draft.canvas.selectedNodes.Any(id => documents.Controller(draft).document.FindNode(id)?.definitionId == ShaderGraphDocument.outputDefinitionId) ? EditorActionState.disabled : EditorActionState.enabled; } + /// /// Executes the prepared operation and publishes only a completed result. /// @@ -453,15 +499,15 @@ internal sealed class SaveShader(ShaderEditorDocuments documents) : ShaderSelect /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The context that supplies state and services for this operation. /// - protected override void Execute(EditorActionContext context) - => context.interactions.documents.Save(documents.Open(context.target).documentId); + protected override void Execute(EditorActionContext context) => context.interactions.documents.Save(documents.Open(context.target).documentId); } [EditorAction("shader/format", ShaderEditorCanvas.C_AREA)] @@ -470,22 +516,19 @@ internal sealed class FormatShaderGraph(ShaderEditorDocuments documents) : Shade /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); var controller = documents.Controller(draft); - controller.ReplaceDocument(ShaderGraphAutoLayout.Apply( - controller.document, - documents.serialization, - documents.context, - documents.Describe), "Format Shader Graph"); + controller.ReplaceDocument(ShaderGraphAutoLayout.Apply(controller.document, documents.serialization, documents.context, documents.Describe), "Format Shader Graph"); draft.frameRequested = true; documents.Changed(draft); } @@ -497,17 +540,17 @@ internal sealed class ReloadShaderSource(ShaderEditorDocuments documents) : Shad /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The context that supplies state and services for this operation. /// - protected override void Execute(EditorActionContext context) - => context.interactions.documents.Revert(documents.Open(context.target).documentId); + protected override void Execute(EditorActionContext context) => context.interactions.documents.Revert(documents.Open(context.target).documentId); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasOrganizationActions.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasOrganizationActions.cs index ecad1d19c..29a18c26b 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasOrganizationActions.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Interactions/ShaderCanvasOrganizationActions.cs @@ -7,27 +7,27 @@ using Inno.Core.Graphs; using Inno.Editor.Graph; using Inno.Editor.Interactions; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Rendering; using Inno.Rendering.Shaders; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - internal struct ShaderCanvasGroup { /// /// Gets the stable identity used to reference this value across subsystem boundaries. /// -public string id { get; set; } + public string id { get; set; } /// /// Gets the title text used by the current instance. /// -public string title { get; set; } + public string title { get; set; } /// /// Gets graph nodes affected by this organization action. /// -public string[] nodes { get; set; } + public string[] nodes { get; set; } } [EditorAction("shader/focus", ShaderEditorCanvas.C_AREA)] @@ -36,18 +36,19 @@ internal sealed class FocusShaderNodes(ShaderEditorDocuments documents) : Shader /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) => documents.Open(context.target).frameRequested = true; + protected override void Execute(EditorActionContext context) => documents.Open(context.target).frameRequested = true; } [EditorAction("shader/reroute", ShaderEditorCanvas.C_AREA)] @@ -56,7 +57,8 @@ internal sealed class InsertShaderReroute(ShaderEditorDocuments documents) : Sha /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -66,22 +68,25 @@ internal sealed class InsertShaderReroute(ShaderEditorDocuments documents) : Sha /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) - => base.Query(context).isEnabled && documents.Open(context.target).selectedEdge is not null ? EditorActionState.enabled : EditorActionState.disabled; + protected override EditorActionState Query(EditorActionContext context) => base.Query(context).isEnabled + && documents.Open(context.target).selectedEdge is not null ? EditorActionState.enabled : EditorActionState.disabled; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); var controller = documents.Controller(draft); GraphDocument graph = controller.document.Clone(); GraphEdgeRecord edge = graph.edges.Single(value => value.id == draft.selectedEdge); ShaderNodePort source = draft.ports[edge.output.nodeId].Single(value => value.id == edge.output.portId.value); - var node = new GraphNodeRecord(new(Guid.NewGuid().ToString("N")), "inno.shader.reroute") { position = draft.menuPosition }; + var node = new GraphNodeRecord(new(Guid.NewGuid().ToString("N")), "inno.shader.reroute") + { + position = draft.menuPosition + }; node.SetValue("valueType", ShaderGraphDocument.Encode(ShaderGraphType.Capture(source.type), documents.serialization, documents.context)); string stage = ShaderGraphDocument.Read(graph.FindNode(edge.output.nodeId)!, "stage", "", documents.serialization, documents.context); node.SetValue("stage", ShaderGraphDocument.Encode(stage, documents.serialization, documents.context)); @@ -103,22 +108,33 @@ internal sealed class GroupShaderNodes(ShaderEditorDocuments documents) : Shader internal static ShaderCanvasGroup[] Read( ShaderEditorDocuments documents, GraphDocument graph - ) - => graph.metadata.TryGetValue(C_GROUPS, out GraphSerializedValue? value) - ? ShaderGraphDocument.Decode(value, documents.serialization, documents.context) : []; + ) => graph.metadata.TryGetValue( + C_GROUPS, + out GraphSerializedValue? value + ) ? ShaderGraphDocument.Decode( + value, + documents.serialization, + documents.context + ) : []; /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); GraphDocument graph = documents.Controller(draft).document.Clone(); ShaderCanvasGroup[] groups = Read(documents, graph); - groups = [.. groups, new() { id = Guid.NewGuid().ToString("N"), title = "Group " + (groups.Length + 1), - nodes = draft.canvas.selectedNodes.Select(static id => id.value).ToArray() }]; + groups = [..groups, new() + { + id = Guid.NewGuid().ToString("N"), + title = "Group " + (groups.Length + 1), + nodes = draft.canvas.selectedNodes.Select(static id => id.value).ToArray() + } + + ]; graph.SetMetadata(C_GROUPS, ShaderGraphDocument.Encode(groups, documents.serialization, documents.context)); documents.Controller(draft).ReplaceDocument(graph, "Group Shader Nodes"); draft.selectedGroupId = groups[^1].id; @@ -135,13 +151,14 @@ internal sealed class UngroupShaderNodes(ShaderEditorDocuments documents) : Shad /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); GraphDocument graph = documents.Controller(draft).document.Clone(); - var groups = GroupShaderNodes.Read(documents, graph).Where(group => draft.selectedGroupId.Length != 0 - ? group.id != draft.selectedGroupId - : !group.nodes.All(id => draft.canvas.selectedNodes.Contains(new(id)))).ToArray(); + var groups = GroupShaderNodes.Read( + documents, + graph + ).Where(group => draft.selectedGroupId.Length != 0 ? group.id != draft.selectedGroupId : !group.nodes.All(id => draft.canvas.selectedNodes.Contains(new(id)))).ToArray(); graph.SetMetadata(GroupShaderNodes.C_GROUPS, ShaderGraphDocument.Encode(groups, documents.serialization, documents.context)); documents.Controller(draft).ReplaceDocument(graph, "Ungroup Shader Nodes"); draft.selectedGroupId = ""; @@ -152,9 +169,8 @@ protected override void Execute(EditorActionContext context) [EditorAction("shader/collapse-subgraph", ShaderEditorCanvas.C_AREA)] internal sealed class CollapseShaderSubgraph( ShaderEditorDocuments documents, - AssetEditorModule browser -) - : ShaderSelectionAction(documents) + AssetBrowserModule browser +) : ShaderSelectionAction(documents) { /// /// Evaluates whether the requested change can be applied to the current generation. @@ -165,27 +181,21 @@ AssetEditorModule browser /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) + protected override EditorActionState Query(EditorActionContext context) { if (!base.Query(context).isEnabled) return EditorActionState.disabled; ShaderEditorDocuments.Draft draft = documents.Open(context.target); GraphDocument graph = documents.Controller(draft).document; GraphNodeRecord[] selected = draft.canvas.selectedNodes.Select(graph.FindNode).OfType().ToArray(); - if (selected.Length != draft.canvas.selectedNodes.Count || selected.Any(static node => - node.definitionId is ShaderGraphDocument.outputDefinitionId - or ShaderGraphNodes.inputDefinitionId - or ShaderGraphNodes.outputDefinitionId)) + if (selected.Length != draft.canvas.selectedNodes.Count + || selected.Any(static node => node.definitionId is ShaderGraphDocument.outputDefinitionId or ShaderGraphNodes.inputDefinitionId or ShaderGraphNodes.outputDefinitionId)) return EditorActionState.disabled; - string[] stages = selected.Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.stageKey, - "", documents.serialization, documents.context)) - .Distinct(StringComparer.Ordinal).ToArray(); + string[] stages = selected.Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.stageKey, "", documents.serialization, documents.context)).Distinct(StringComparer.Ordinal).ToArray(); if (stages.Length != 1 || stages[0].Length == 0) return EditorActionState.disabled; HashSet ids = selected.Select(static node => node.id).ToHashSet(); - return graph.edges.Any(edge => ids.Contains(edge.input.nodeId) != ids.Contains(edge.output.nodeId)) - ? EditorActionState.enabled - : EditorActionState.disabled; + return graph.edges.Any(edge => ids.Contains(edge.input.nodeId) != ids.Contains(edge.output.nodeId)) ? EditorActionState.enabled : EditorActionState.disabled; } /// @@ -194,44 +204,25 @@ or ShaderGraphNodes.inputDefinitionId /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { ShaderEditorDocuments.Draft draft = documents.Open(context.target); GraphDocumentController controller = documents.Controller(draft); GraphDocument parent = controller.document; - HashSet selected = [.. draft.canvas.selectedNodes]; + HashSet selected = [..draft.canvas.selectedNodes]; GraphNodeRecord[] nodes = parent.nodes.Where(node => selected.Contains(node.id)).ToArray(); - string stage = nodes.Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.stageKey, - "", documents.serialization, documents.context)) - .Distinct(StringComparer.Ordinal).Single(); - GraphEdgeRecord[] incomingEdges = parent.edges - .Where(edge => selected.Contains(edge.input.nodeId) && !selected.Contains(edge.output.nodeId)).ToArray(); - GraphEdgeRecord[] outgoingEdges = parent.edges - .Where(edge => selected.Contains(edge.output.nodeId) && !selected.Contains(edge.input.nodeId)).ToArray(); - + string stage = nodes.Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.stageKey, "", documents.serialization, documents.context)).Distinct(StringComparer.Ordinal).Single(); + GraphEdgeRecord[] incomingEdges = parent.edges.Where(edge => selected.Contains(edge.input.nodeId) && !selected.Contains(edge.output.nodeId)).ToArray(); + GraphEdgeRecord[] outgoingEdges = parent.edges.Where(edge => selected.Contains(edge.output.nodeId) && !selected.Contains(edge.input.nodeId)).ToArray(); var inputGroups = incomingEdges.GroupBy(static edge => edge.output).ToArray(); var outputGroups = outgoingEdges.GroupBy(static edge => edge.output).ToArray(); var inputNames = new HashSet(StringComparer.Ordinal); var outputNames = new HashSet(StringComparer.Ordinal); - ShaderGraphNodePortDefinition[] inputs = inputGroups.Select(group => Port( - Unique(group.First().input.portId.value, "input", inputNames), - draft.ports[group.First().input.nodeId].Single(port => port.id == group.First().input.portId.value).type, - required: true)).ToArray(); - ShaderGraphNodePortDefinition[] outputs = outputGroups.Select(group => Port( - Unique(group.Key.portId.value, "output", outputNames), - draft.ports[group.Key.nodeId].Single(port => port.id == group.Key.portId.value).type, - required: false)).ToArray(); - + ShaderGraphNodePortDefinition[] inputs = inputGroups.Select(group => Port(Unique(group.First().input.portId.value, "input", inputNames), draft.ports[group.First().input.nodeId].Single(port => port.id == group.First().input.portId.value).type, required: true)).ToArray(); + ShaderGraphNodePortDefinition[] outputs = outputGroups.Select(group => Port(Unique(group.Key.portId.value, "output", outputNames), draft.ports[group.Key.nodeId].Single(port => port.id == group.Key.portId.value).type, required: false)).ToArray(); ShaderDefinition parentDefinition = ShaderGraphDocument.ReadDefinition(parent, documents.serialization, documents.context); - HashSet ownedBindings = nodes.Where(static node => node.definitionId == "inno.shader.stage-input") - .Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.settingsKey, - new ShaderGraphInputSettings(), documents.serialization, documents.context).id) - .ToHashSet(StringComparer.Ordinal); - ShaderDefinition childDefinition = new( - "Graph Node", - parentDefinition.properties.Where(property => ownedBindings.Contains(property.id.value)), - [], - []); + HashSet ownedBindings = nodes.Where(static node => node.definitionId == "inno.shader.stage-input").Select(node => ShaderGraphDocument.Read(node, ShaderGraphDocument.settingsKey, new ShaderGraphInputSettings(), documents.serialization, documents.context).id).ToHashSet(StringComparer.Ordinal); + ShaderDefinition childDefinition = new("Graph Node", parentDefinition.properties.Where(property => ownedBindings.Contains(property.id.value)), [], []); GraphDocument child = ShaderGraphDocument.Create(childDefinition, documents.serialization, documents.context); string displayName = SuggestedName(documents, draft, selected); var nodeSettings = new ShaderGraphNodeSettings @@ -242,7 +233,6 @@ protected override void Execute(EditorActionContext context) effect = outputs.Length == 0 ? ShaderGraphNodeEffect.SideEffect : ShaderGraphNodeEffect.Pure }; ShaderGraphNodes.WriteSettings(child, nodeSettings, documents.serialization, documents.context); - float minX = nodes.Min(static node => node.position.x); float minY = nodes.Min(static node => node.position.y); var remap = new Dictionary(); @@ -253,57 +243,51 @@ protected override void Execute(EditorActionContext context) { position = new(source.position.x - minX + 280, source.position.y - minY + 80) }; - foreach ((string key, GraphSerializedValue value) in source.values) + foreach ((string key, GraphSerializedValue value)in source.values) if (key != ShaderGraphDocument.stageKey) copy.SetValue(key, value.Clone()); child.AddNode(copy); remap.Add(source.id, id); } - foreach (GraphEdgeRecord edge in parent.edges.Where(edge => selected.Contains(edge.output.nodeId) - && selected.Contains(edge.input.nodeId))) - child.AddEdge(new( - new("body/" + edge.id.value), - new(remap[edge.output.nodeId], edge.output.portId), - new(remap[edge.input.nodeId], edge.input.portId))); + foreach (GraphEdgeRecord edge in parent.edges.Where(edge => selected.Contains(edge.output.nodeId) && selected.Contains(edge.input.nodeId))) + child.AddEdge(new(new("body/" + edge.id.value), new(remap[edge.output.nodeId], edge.output.portId), new(remap[edge.input.nodeId], edge.input.portId))); if (inputs.Length != 0) { var boundary = new GraphNodeRecord(new("function-inputs"), ShaderGraphNodes.inputDefinitionId) { position = new(20, 80) }; - boundary.SetValue(ShaderGraphDocument.settingsKey, ShaderGraphDocument.Encode( - new ShaderGraphNodeInputSettings { ports = inputs }, documents.serialization, documents.context)); + boundary.SetValue( + ShaderGraphDocument.settingsKey, + ShaderGraphDocument.Encode(new ShaderGraphNodeInputSettings { ports = inputs }, documents.serialization, documents.context) + ); child.AddNode(boundary); for (int index = 0; index < inputGroups.Length; index++) foreach (GraphEdgeRecord edge in inputGroups[index]) - child.AddEdge(new( - new("input/" + edge.id.value), - new(boundary.id, new(inputs[index].id)), - new(remap[edge.input.nodeId], edge.input.portId))); + child.AddEdge(new(new("input/" + edge.id.value), new(boundary.id, new(inputs[index].id)), new(remap[edge.input.nodeId], edge.input.portId))); } + if (outputs.Length != 0) { var boundary = new GraphNodeRecord(new("function-outputs"), ShaderGraphNodes.outputDefinitionId) { position = new(nodes.Max(static node => node.position.x) - minX + 600, 80) }; - boundary.SetValue(ShaderGraphDocument.settingsKey, ShaderGraphDocument.Encode( - new ShaderGraphNodeOutputSettings { ports = outputs }, documents.serialization, documents.context)); + boundary.SetValue( + ShaderGraphDocument.settingsKey, + ShaderGraphDocument.Encode(new ShaderGraphNodeOutputSettings { ports = outputs }, documents.serialization, documents.context) + ); child.AddNode(boundary); for (int index = 0; index < outputGroups.Length; index++) - child.AddEdge(new( - new("output/" + index), - new(remap[outputGroups[index].Key.nodeId], outputGroups[index].Key.portId), - new(boundary.id, new(outputs[index].id)))); + child.AddEdge(new(new("output/" + index), new(remap[outputGroups[index].Key.nodeId], outputGroups[index].Key.portId), new(boundary.id, new(outputs[index].id)))); } AssetPath path = UniquePath(draft.path, displayName, documents); using EditorHistoryTransaction transaction = documents.interactions.history.BeginTransaction("Collapse Shader Subgraph"); - AssetFileEntry created = browser.CreateSource(path, GraphDocumentCodec.Encode(child, documents.serialization)); + AssetFileEntry created = browser.edits.CreateSource(path, GraphDocumentCodec.Encode(child, documents.serialization)); Guid sourceId = documents.AssetId(created); - ShaderGraphNodeInterface nodeInterface = ShaderGraphNodes.ReadInterface( - child, documents.serialization, documents.context); + ShaderGraphNodeInterface nodeInterface = ShaderGraphNodes.ReadInterface(child, documents.serialization, documents.context); GraphDocument candidate = parent.Clone(); foreach (GraphNodeId id in selected) candidate.RemoveNode(id); @@ -317,24 +301,16 @@ protected override void Execute(EditorActionContext context) call.SetValue(ShaderGraphNodes.interfaceKey, ShaderGraphDocument.Encode(nodeInterface, documents.serialization, documents.context)); candidate.AddNode(call); for (int index = 0; index < inputGroups.Length; index++) - candidate.AddEdge(new( - new(Guid.NewGuid().ToString("N")), - inputGroups[index].Key, - new(call.id, new(inputs[index].id)))); + candidate.AddEdge(new(new(Guid.NewGuid().ToString("N")), inputGroups[index].Key, new(call.id, new(inputs[index].id)))); for (int index = 0; index < outputGroups.Length; index++) foreach (GraphEdgeRecord edge in outputGroups[index]) - candidate.AddEdge(new( - new(Guid.NewGuid().ToString("N")), - new(call.id, new(outputs[index].id)), - edge.input)); - + candidate.AddEdge(new(new(Guid.NewGuid().ToString("N")), new(call.id, new(outputs[index].id)), edge.input)); ShaderCanvasGroup[] groups = GroupShaderNodes.Read(documents, candidate).Select(group => { group.nodes = group.nodes.Where(id => !selected.Contains(new(id))).ToArray(); return group; }).Where(static group => group.nodes.Length != 0).ToArray(); - candidate.SetMetadata(GroupShaderNodes.C_GROUPS, - ShaderGraphDocument.Encode(groups, documents.serialization, documents.context)); + candidate.SetMetadata(GroupShaderNodes.C_GROUPS, ShaderGraphDocument.Encode(groups, documents.serialization, documents.context)); controller.ReplaceDocument(candidate, "Collapse Shader Subgraph"); transaction.Commit(); draft.canvas.SelectNodes([call.id]); @@ -346,9 +322,12 @@ private static ShaderGraphNodePortDefinition Port( string id, ShaderSourceType type, bool required - ) - => new() { id = id, type = ShaderGraphType.Capture(type), required = required }; - + ) => new() + { + id = id, + type = ShaderGraphType.Capture(type), + required = required + }; private static string Unique( string candidate, string fallback, @@ -366,12 +345,11 @@ private static string SuggestedName( ShaderEditorDocuments.Draft draft, HashSet selected ) { - ShaderCanvasGroup? group = GroupShaderNodes.Read(documents, documents.Controller(draft).document) - .FirstOrDefault(value => value.nodes.Length == selected.Count - && value.nodes.All(id => selected.Contains(new(id)))); - return group is ShaderCanvasGroup exact && !string.IsNullOrWhiteSpace(exact.title) - ? exact.title - : "Shader Node"; + ShaderCanvasGroup? group = GroupShaderNodes.Read( + documents, + documents.Controller(draft).document + ).FirstOrDefault(value => value.nodes.Length == selected.Count && value.nodes.All(id => selected.Contains(new(id)))); + return group is ShaderCanvasGroup exact && !string.IsNullOrWhiteSpace(exact.title) ? exact.title : "Shader Node"; } private static AssetPath UniquePath( @@ -395,13 +373,14 @@ ShaderEditorDocuments documents [EditorAction("shader/reveal-source", ShaderEditorCanvas.C_AREA)] internal sealed class RevealShaderFunction( ShaderEditorDocuments documents, - AssetEditorModule browser + AssetBrowserModule browser ) : ShaderSelectionAction(documents) { /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; + /// /// Evaluates whether the requested change can be applied to the current generation. /// @@ -411,33 +390,31 @@ AssetEditorModule browser /// /// The validated editor action state that represents the completed operation. /// -protected override EditorActionState Query(EditorActionContext context) + protected override EditorActionState Query(EditorActionContext context) { if (!base.Query(context).isEnabled) return EditorActionState.disabled; var draft = documents.Open(context.target); - string? definition = draft.canvas.selectedNodes.Count == 1 - ? documents.Controller(draft).document.FindNode(draft.canvas.selectedNodes.First())?.definitionId - : null; - return definition is "inno.shader.source" or ShaderGraphNodes.callDefinitionId - ? EditorActionState.enabled - : EditorActionState.disabled; + string? definition = draft.canvas.selectedNodes.Count == 1 ? documents.Controller(draft).document.FindNode(draft.canvas.selectedNodes.First())?.definitionId : null; + return definition is "inno.shader.source" or ShaderGraphNodes.callDefinitionId ? EditorActionState.enabled : EditorActionState.disabled; } + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); GraphNodeRecord node = documents.Controller(draft).document.FindNode(draft.canvas.selectedNodes.First())!; Reveal(documents, browser, ShaderGraphDocument.Read(node, "sourceId", Guid.Empty, documents.serialization, documents.context)); } + internal static void Reveal( ShaderEditorDocuments documents, - AssetEditorModule browser, + AssetBrowserModule browser, Guid sourceId ) { if (!documents.assets.TryGetInfo(sourceId, out AssetInfo? info) || info is null) @@ -454,24 +431,25 @@ Guid sourceId [EditorAction("shader/reveal-shader", ShaderEditorCanvas.C_AREA)] internal sealed class RevealShaderAsset( ShaderEditorDocuments documents, - AssetEditorModule browser + AssetBrowserModule browser ) : ShaderSelectionAction(documents) { /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); string parent = Path.GetDirectoryName(draft.path.localPath)?.Replace('\\', '/') ?? string.Empty; @@ -489,41 +467,43 @@ internal sealed class ShowShaderDiagnostics(ShaderEditorDocuments documents) : S /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) => documents.ShowCheck(documents.Open(context.target)); + protected override void Execute(EditorActionContext context) => documents.ShowCheck(documents.Open(context.target)); } [EditorAction("shader/copy-to-project", ShaderEditorCanvas.C_AREA)] internal sealed class CopyShaderToProject( ShaderEditorDocuments documents, - AssetEditorModule browser + AssetBrowserModule browser ) : ShaderSelectionAction(documents) { /// /// Gets whether writes is active for the current instance. /// -protected override bool writes => false; + protected override bool writes => false; /// /// Gets whether needs selection is active for the current instance. /// -protected override bool needsSelection => false; + protected override bool needsSelection => false; + /// /// Executes the prepared operation and publishes only a completed result. /// /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void Execute(EditorActionContext context) + protected override void Execute(EditorActionContext context) { var draft = documents.Open(context.target); AssetSourceMount mount = documents.assets.sourceMounts.Single(value => value.id == AssetSourceId.project); @@ -531,8 +511,9 @@ protected override void Execute(EditorActionContext context) AssetPath destination = AssetPath.Project(name + ".ishader"); for (int index = 2; File.Exists(mount.Resolve(destination.localPath)) || File.Exists(mount.Resolve(destination.localPath) + ".imeta"); index++) destination = AssetPath.Project(name + " " + index + ".ishader"); - AssetFileEntry entry = browser.CreateSource(destination, GraphDocumentCodec.Encode(documents.Controller(draft).document, documents.serialization)); - browser.BeginCreatedSourceRename(entry); + AssetFileEntry entry = browser.edits.CreateSource(destination, GraphDocumentCodec.Encode(documents.Controller(draft).document, documents.serialization)); + _ = context.interactions.For("panel/asset.file-browser", entry).Select(); + _ = context.interactions.For("panel/asset.file-browser", entry).Execute("file-browser/rename"); context.interactions.OpenPanel("rendering.shader-editor"); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Presentation/ShaderCheckModal.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Presentation/ShaderCheckModal.cs index b288d7f55..e590b3d71 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Presentation/ShaderCheckModal.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/Presentation/ShaderCheckModal.cs @@ -1,3 +1,4 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using Inno.Core.Diagnostics; @@ -8,7 +9,6 @@ using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - /// /// Displays transient draft-check progress and publishes completed diagnostics to the shared Console. /// @@ -20,11 +20,10 @@ internal sealed class ShaderCheckModal(ShaderEditorDocuments documents) : Editor { private const string C_DIAGNOSTIC_GROUP = "Shader Check"; private string m_status = "Checking the current Shader draft…"; - /// /// Gets whether this value is visible. /// -public override bool isVisible => documents.TryGetCheckDraft(out _); + public override bool isVisible => documents.TryGetCheckDraft(out _); /// /// Renders this feature using the current editor presentation context. @@ -32,7 +31,7 @@ internal sealed class ShaderCheckModal(ShaderEditorDocuments documents) : Editor /// /// The operation scope that provides state, services, and ownership boundaries. /// -protected override void OnDraw(EditorContext context) + protected override void OnDraw(EditorContext context) { _ = context; if (documents.TryGetCheckDraft(out ShaderEditorDocuments.Draft? draft)) @@ -45,16 +44,14 @@ protected override void OnDraw(EditorContext context) else { Publish(draft, snapshot); - m_status = snapshot.state == EditorShaderCompilationState.Succeeded - ? "Shader check completed." - : "Shader check failed. See Console diagnostics."; + m_status = snapshot.state == EditorShaderCompilationState.Succeeded ? "Shader check completed." : "Shader check failed. See Console diagnostics."; documents.CloseCheck(); } } // Default modal policy is shared with script compilation: the same fixed width, // centered placement, blocking behavior, fade timing and content-sized height. - ImGuiWidget.WrappedText(m_status); + EditorUI.current.widgets.WrappedText(m_status); } private static void Publish( @@ -65,24 +62,20 @@ EditorShaderDraftCompilationSnapshot snapshot for (int index = 0; index < snapshot.diagnostics.Count; index++) { ShaderDiagnostic shader = snapshot.diagnostics[index]; - DiagnosticLocation? location = shader.location is ShaderSourceLocation source - ? new(source.assetPath, source.line, source.column) - : null; + DiagnosticLocation? location = shader.location is ShaderSourceLocation source ? new(source.assetPath, source.line, source.column) : null; diagnostics.Add(shader.severity switch { DiagnosticSeverity.Info => Diagnostic.Info(shader.code, shader.message, location), DiagnosticSeverity.Warning => Diagnostic.Warning(shader.code, shader.message, location), DiagnosticSeverity.Error => Diagnostic.Error(shader.code, shader.message, location), - _ => Diagnostic.Error(shader.code, shader.message, location) - }); + _ => Diagnostic.Error(shader.code, shader.message, location)}); } if (diagnostics.Count == 0) { - diagnostics.Add(snapshot.state == EditorShaderCompilationState.Succeeded - ? Diagnostic.Info("SHADER_CHECK_SUCCEEDED", "Shader check completed without diagnostics.") - : Diagnostic.Error("SHADER_CHECK_FAILED", "Shader check failed without a compiler diagnostic.")); + diagnostics.Add(snapshot.state == EditorShaderCompilationState.Succeeded ? Diagnostic.Info("SHADER_CHECK_SUCCEEDED", "Shader check completed without diagnostics.") : Diagnostic.Error("SHADER_CHECK_FAILED", "Shader check failed without a compiler diagnostic.")); } + Diagnostics.Set(draft.id, C_DIAGNOSTIC_GROUP, diagnostics, draft.path.ToString()); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/ShaderEditorPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/ShaderEditorPanel.cs index 04372735b..f958f61a8 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/ShaderEditorPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.ShaderEditor/ShaderEditorPanel.cs @@ -1,4 +1,4 @@ -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; using Inno.Assets; @@ -8,11 +8,10 @@ using Inno.Editor.Shaders; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Rendering; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Assets; namespace Inno.Editor.Panel.ShaderEditor; - [EditorPanel("rendering.shader-editor", "Shader Editor", order: 230, menuPath: "Authoring")] internal sealed class ShaderEditorPanel(ShaderEditorDocuments documents) : EditorPanel { @@ -28,6 +27,7 @@ internal sealed class ShaderEditorPanel(ShaderEditorDocuments documents) : Edito /// Gets the preferred initial window size in logical editor units. /// public override Vector2 initialSize => new(960f, 640f); + /// /// Draws this feature using the current editor presentation context. /// @@ -40,24 +40,31 @@ protected override void OnDraw(EditorContext context) if (documents.interactions.selection.selectedTarget is ShaderAsset shader) _ = documents.assets.TryGetFileSystemEntry(shader.assetPath, out entry); if (documents.interactions.selection.selectedTarget is ShaderInspectionSelection selection - && documents.assets.TryGetInfo(selection.assetId, out AssetInfo? info) && info is not null) + && documents.assets.TryGetInfo( + selection.assetId, + out AssetInfo? info + ) + && info is not null) _ = documents.assets.TryGetFileSystemEntry(info.assetPath, out entry); if (entry is null || entry.isDirectory || !entry.assetPath.localPath.EndsWith(".ishader", StringComparison.OrdinalIgnoreCase)) { ShaderEditorCanvas.DrawHeader(documents, null); - ImGuiWidget.CenteredWrappedText( + EditorUI.current.widgets.CenteredWrappedText( "Select a Shader (.ishader) in the File Browser.", - Vector2.Max(Vector2.One, NativeImGui.GetContentRegionAvail()), - new Vector2(40f, 28f)); + Vector2.Max(Vector2.One, Ui.GetContentRegionAvailable()), + new Vector2(40f, 28f) + ); return; } + try { new ShaderEditorCanvas(documents, documents.Open(entry)).Draw(); } - catch (Exception failure) when ((failure is System.IO.IOException or InvalidOperationException or FormatException) && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) + catch (Exception failure) when ((failure is System.IO.IOException or InvalidOperationException or FormatException) + && Inno.Core.Execution.RetirementPendingException.Find(failure) is null) { - EditorWidget.WrappedText(failure.Message); + EditorUI.current.widgets.WrappedText(failure.Message); } } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Stats/FrameStatisticsSampler.cs b/src/composition/editor/panels/Inno.Editor.Panel.Stats/FrameStatisticsSampler.cs index c19b0b129..81d04bdf5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Stats/FrameStatisticsSampler.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Stats/FrameStatisticsSampler.cs @@ -1,49 +1,76 @@ using System; -using System.Collections.Generic; namespace Inno.Editor.Panel.Stats; internal sealed class FrameStatisticsSampler { - private const float C_SAMPLE_WINDOW_SECONDS = 0.5f; + private const int C_SAMPLE_CAPACITY = 120; + private const float C_SAMPLE_INTERVAL_SECONDS = 0.5f; private const float C_SAMPLE_RESET_SECONDS = 1f; - - private readonly Queue m_frameDurations = []; - private double m_sampleDuration; - private float m_lastSampleTime = float.NaN; + private const float C_SAMPLE_WINDOW_SECONDS = 60f; + private readonly float[] m_frameTimes = new float[C_SAMPLE_CAPACITY]; + private readonly float[] m_frameRates = new float[C_SAMPLE_CAPACITY]; + private readonly float[] m_sampleTimes = new float[C_SAMPLE_CAPACITY]; + private float m_lastFrameTime = float.NaN; + private float m_nextSampleTime; + private double m_duration; + private int m_frames; + private int m_count; internal float deltaTime { get; private set; } internal float framesPerSecond { get; private set; } + internal ReadOnlySpan frameTimes => m_frameTimes.AsSpan(0, m_count); + internal ReadOnlySpan frameRates => m_frameRates.AsSpan(0, m_count); - internal void Update( + internal bool Update( float totalTime, float frameDeltaTime ) { - if (!float.IsFinite(frameDeltaTime) || frameDeltaTime <= 0f) - return; - - if (float.IsFinite(m_lastSampleTime) && totalTime - m_lastSampleTime > C_SAMPLE_RESET_SECONDS) + if (!float.IsFinite(totalTime) || !float.IsFinite(frameDeltaTime) || frameDeltaTime <= 0f) + return false; + if (float.IsFinite(m_lastFrameTime) + && (totalTime < m_lastFrameTime || totalTime - m_lastFrameTime > C_SAMPLE_RESET_SECONDS + || totalTime - m_lastFrameTime > frameDeltaTime * 1.5f + 0.0001f)) Reset(); - m_lastSampleTime = totalTime; - - m_frameDurations.Enqueue(frameDeltaTime); - m_sampleDuration += frameDeltaTime; - while (m_frameDurations.Count > 1 && - m_sampleDuration - m_frameDurations.Peek() >= C_SAMPLE_WINDOW_SECONDS) - { - m_sampleDuration -= m_frameDurations.Dequeue(); - } - - double averageFrameDuration = m_sampleDuration / m_frameDurations.Count; - deltaTime = (float)averageFrameDuration; - framesPerSecond = averageFrameDuration > 0d ? (float)(1d / averageFrameDuration) : 0f; + m_lastFrameTime = totalTime; + int expired = 0; + while (expired < m_count && m_sampleTimes[expired] < totalTime - C_SAMPLE_WINDOW_SECONDS) + expired++; + RemoveOldest(expired); + m_duration += frameDeltaTime; + m_frames++; + if (m_count > 0 && totalTime < m_nextSampleTime) + return false; + deltaTime = (float)(m_duration / m_frames); + framesPerSecond = (float)(m_frames / m_duration); + if (m_count == C_SAMPLE_CAPACITY) + RemoveOldest(1); + m_frameTimes[m_count] = deltaTime * 1000f; + m_frameRates[m_count] = framesPerSecond; + m_sampleTimes[m_count++] = totalTime; + m_nextSampleTime = totalTime + C_SAMPLE_INTERVAL_SECONDS; + m_frames = 0; + m_duration = 0d; + return true; } - private void Reset() + internal void Reset() { - m_frameDurations.Clear(); - m_sampleDuration = 0d; + m_count = 0; + m_lastFrameTime = float.NaN; + m_frames = 0; + m_duration = 0d; deltaTime = 0f; framesPerSecond = 0f; } + + private void RemoveOldest(int count) + { + if (count == 0) + return; + m_count -= count; + Array.Copy(m_frameTimes, count, m_frameTimes, 0, m_count); + Array.Copy(m_frameRates, count, m_frameRates, 0, m_count); + Array.Copy(m_sampleTimes, count, m_sampleTimes, 0, m_count); + } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Stats/Inno.Editor.Panel.Stats.csproj b/src/composition/editor/panels/Inno.Editor.Panel.Stats/Inno.Editor.Panel.Stats.csproj index 12de0d3dc..eff0d64ca 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Stats/Inno.Editor.Panel.Stats.csproj +++ b/src/composition/editor/panels/Inno.Editor.Panel.Stats/Inno.Editor.Panel.Stats.csproj @@ -11,12 +11,6 @@ - - diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Stats/StatsPanel.cs b/src/composition/editor/panels/Inno.Editor.Panel.Stats/StatsPanel.cs index 63623af64..3839f690c 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Stats/StatsPanel.cs +++ b/src/composition/editor/panels/Inno.Editor.Panel.Stats/StatsPanel.cs @@ -1,27 +1,29 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using System; using System.Collections.Generic; using System.Linq; - using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.Panel.Stats; - /// /// Shows lightweight runtime metrics. /// [EditorPanel("diagnostics.stats", "Stats", order: 500, menuPath: "Diagnostics")] internal sealed class StatsPanel : EditorPanel { - private readonly FrameStatisticsSampler m_statistics = new(); + private const float C_ROW_PADDING = 3f; + private readonly FrameStatisticsSampler m_statistics = new(); + private IReadOnlyList? m_previousStatistics; + private StatisticGroup[] m_groups = []; + private (string label, string value)[] m_frameValues = []; /// /// Gets whether use window padding is active for the current instance. /// -public override bool useWindowPadding => false; + public override bool useWindowPadding => false; /// /// Creates the panel. @@ -38,74 +40,85 @@ internal StatsPanel() /// protected override void OnDraw(EditorContext context) { - m_statistics.Update(context.frame.totalTime, context.frame.deltaTime); - - DrawGroup("Frame Timing", - [ - ("Time", $"{context.frame.totalTime:F2} s"), - ("Delta", $"{m_statistics.deltaTime * 1000f:F2} ms"), - ("FPS", $"{m_statistics.framesPerSecond:F1}") - ]); + if (m_statistics.Update(context.frame.totalTime, context.frame.deltaTime)) + { + m_frameValues = + [ + ("Time", $"{context.frame.totalTime:F2} s"), + ("Delta", $"{m_statistics.deltaTime * 1000f:F2} ms"), + ("FPS", $"{m_statistics.framesPerSecond:F1}") + ]; + } + DrawGroup("Frame Timing", m_frameValues); + EditorUI.current.widgets.Trend("Frame time (ms)", m_statistics.frameTimes); + EditorUI.current.widgets.Trend("Frames per second", m_statistics.frameRates); IReadOnlyList statistics = context.statistics.GetSnapshot(); - if (statistics.Count == 0) + if (!ReferenceEquals(statistics, m_previousStatistics)) { - EditorWidget.Hint("No feature statistics were published for the current frame."); - return; + m_groups = statistics.GroupBy(static statistic => statistic.groupId) + .Select(static group => new StatisticGroup(group.First().groupName, + group.Select(static value => (value.label, value.value)).ToArray())).ToArray(); + m_previousStatistics = statistics; } - - foreach (IGrouping group in statistics.GroupBy( - static statistic => statistic.groupId)) + if (m_groups.Length == 0) { - DrawGroup( - group.First().groupName, - group.Select(static statistic => (statistic.label, statistic.value))); + EditorUI.current.widgets.Hint("No feature statistics were published for the current frame."); + return; } + foreach (StatisticGroup group in m_groups) + DrawGroup(group.title, group.values); + } + + /// + protected override void OnDetach(EditorContext context) + { + m_statistics.Reset(); + m_previousStatistics = null; + m_groups = []; + m_frameValues = []; } private static void DrawGroup( string title, - IEnumerable<(string label, string value)> values + ReadOnlySpan<(string label, string value)> values ) { - EditorWidget.CollectionSectionHeader(title); - - System.Numerics.Vector2 padding = EditorWidget.style.inspectorSectionPadding; - ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp - | ImGuiTableFlags.RowBg - | ImGuiTableFlags.NoSavedSettings - | ImGuiTableFlags.PadOuterX; - NativeImGui.PushStyleVar( - (int)(ImGuiStyleVar.CellPadding), - new System.Numerics.Vector2( - padding.X, - EditorWidget.style.statisticRowPadding)); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableRowBg), EditorPalette.collectionRow); - NativeImGui.PushStyleColor((int)(ImGuiCol.TableRowBgAlt), EditorPalette.collectionRowAlternate); + EditorUI.current.widgets.CollectionSectionHeader(title); + System.Numerics.Vector2 padding = EditorUI.current.widgets.style.sectionPadding; + ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.PadOuterX; + Ui.PushStyleVar((ImGuiStyleVar.CellPadding), new System.Numerics.Vector2(padding.X, C_ROW_PADDING * EditorUI.current.widgets.style.zoom)); + Ui.PushStyleColor((ImGuiCol.TableRowBg), EditorPalette.collectionRow); + Ui.PushStyleColor((ImGuiCol.TableRowBgAlt), EditorPalette.collectionRowAlternate); try { - if (!NativeImGui.BeginTable($"##stats_{title}", 2, (int)(flags), default(System.Numerics.Vector2), 0f)) + if (!Ui.BeginTable($"##stats_{title}", 2, flags, default(System.Numerics.Vector2), 0f)) return; try { - NativeImGui.TableSetupColumn("##label", (int)(ImGuiTableColumnFlags.WidthStretch), 0.42f); - NativeImGui.TableSetupColumn("##value", (int)(ImGuiTableColumnFlags.WidthStretch), 0.58f); + Ui.TableSetupColumn("##label", (ImGuiTableColumnFlags.WidthStretch), 0.42f); + Ui.TableSetupColumn("##value", (ImGuiTableColumnFlags.WidthStretch), 0.58f); foreach ((string label, string value) in values) { - NativeImGui.TableNextRow(); - NativeImGui.TableSetColumnIndex(0); - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); - NativeImGui.TableSetColumnIndex(1); - EditorWidget.WrappedText(value); + Ui.TableNextRow(); + Ui.TableSetColumnIndex(0); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, label); + Ui.TableSetColumnIndex(1); + EditorUI.current.widgets.WrappedText(value); } } finally { - NativeImGui.EndTable(); + Ui.EndTable(); } } finally { - NativeImGui.PopStyleColor(2); - NativeImGui.PopStyleVar(); + Ui.PopStyleColor(2); + Ui.PopStyleVar(); } } + + private sealed record StatisticGroup( + string title, + (string label, string value)[] values + ); } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs similarity index 94% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs rename to src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs index 342a8253f..ea598c569 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconAttribute.cs +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconAttribute.cs @@ -1,7 +1,8 @@ +using Inno.Editor.Assets; +using Inno.Editor.Core; using System; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets.ImGui; /// /// Declares the icon used by the Asset Browser for an imported asset type or source extension. /// @@ -9,14 +10,7 @@ namespace Inno.Editor.Panel.FileBrowser; /// Apply one or more declarations to a container type. The container has no runtime responsibility; /// it only keeps related icon declarations together so the type catalog can discover them. /// -[AttributeUsage( - AttributeTargets.Class | - AttributeTargets.Struct | - AttributeTargets.Interface | - AttributeTargets.Enum | - AttributeTargets.Delegate, - AllowMultiple = true, - Inherited = false)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate, AllowMultiple = true, Inherited = false)] public sealed class AssetIconAttribute : Attribute { /// @@ -83,24 +77,20 @@ public AssetIconAttribute( /// an extension declaration. /// public Type? assetType { get; } - /// /// Gets the normalized source extension represented by this declaration, or /// for a type declaration. /// public string? extension { get; } - /// /// Gets the Editor icon glyph to render. /// public string icon { get; } - /// /// Gets whether a type declaration may also represent derived asset types. Extension declarations /// always return . /// public bool useForChildren { get; } - /// /// Gets the tie-breaking priority after target specificity. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconRegistry.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconRegistry.cs similarity index 60% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconRegistry.cs rename to src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconRegistry.cs index 8f8dbad20..8428eff48 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/AssetIconRegistry.cs +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/AssetIconRegistry.cs @@ -1,22 +1,20 @@ +using Inno.Editor.Assets; +using Inno.Editor.Core; using System; using System.Collections.Generic; using System.Linq; - using Inno.Assets; using Inno.Extensibility.Types; using Inno.Editor.Settings; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets.ImGui; internal sealed class AssetIconRegistry : TypeRegistry { private readonly EditorSettings m_settings; - internal AssetIconRegistry( EditorSettings settings, TypeCatalog types - ) - : base(types) + ) : base(types) { m_settings = settings ?? throw new ArgumentNullException(nameof(settings)); } @@ -35,10 +33,12 @@ out string icon } catch (ArgumentException) { - // Extension-defined attributes may still contain a literal glyph. + // Extension-defined attributes may still contain a literal glyph. } + return true; } + return false; } @@ -55,35 +55,31 @@ protected override Snapshot Build(TypeCacheSnapshot types) { var typeRegistrations = new List(); var extensionRegistrations = new List(); - foreach (Type declarationType in types.types - .Select(typeRef => typeRef.Resolve(types)) - .OrderBy(static type => type.Assembly.GetName().Name, StringComparer.Ordinal) - .ThenBy(static type => type.FullName, StringComparer.Ordinal)) + foreach (Type declarationType in types.types.Select(typeRef => typeRef.Resolve(types)).OrderBy( + static type => type.Assembly.GetName().Name, + StringComparer.Ordinal + ).ThenBy( + static type => type.FullName, + StringComparer.Ordinal + )) { - foreach (AssetIconAttribute attribute in declarationType - .GetCustomAttributes(typeof(AssetIconAttribute), inherit: false) - .Cast() - .OrderBy(static value => value.assetType?.FullName ?? value.extension, StringComparer.Ordinal) - .ThenBy(static value => value.priority)) + foreach (AssetIconAttribute attribute in declarationType.GetCustomAttributes( + typeof(AssetIconAttribute), + inherit: false + ).Cast().OrderBy( + static value => value.assetType?.FullName ?? value.extension, + StringComparer.Ordinal + ).ThenBy(static value => value.priority)) { if (attribute.assetType is Type assetType) { ValidateType(attribute, declarationType, assetType, typeRegistrations); - typeRegistrations.Add(new TypeRegistration( - assetType, - attribute.useForChildren, - attribute.priority, - declarationType, - attribute.icon)); + typeRegistrations.Add(new TypeRegistration(assetType, attribute.useForChildren, attribute.priority, declarationType, attribute.icon)); } else if (attribute.extension is string extension) { ValidateExtension(attribute, declarationType, extension, extensionRegistrations); - extensionRegistrations.Add(new ExtensionRegistration( - extension, - attribute.priority, - declarationType, - attribute.icon)); + extensionRegistrations.Add(new ExtensionRegistration(extension, attribute.priority, declarationType, attribute.icon)); } } } @@ -101,15 +97,12 @@ out string icon { foreach (TypeRegistration registration in current.typeRegistrations) { - if (!AssetTypeDistance.TryGet(assetType, registration.assetType, out int distance) || - distance > 0 && !registration.useForChildren) + if (!EditorTypeSpecificity.TryGet(assetType, registration.assetType, out int distance) || distance > 0 && !registration.useForChildren) { continue; } - if (best is null || - distance < bestDistance || - distance == bestDistance && registration.priority > best.priority) + if (best is null || distance < bestDistance || distance == bestDistance && registration.priority > best.priority) { best = registration; bestDistance = distance; @@ -130,9 +123,10 @@ out string icon { if (!relativePath.EndsWith(registration.extension, StringComparison.OrdinalIgnoreCase)) continue; - if (best is null || - registration.extension.Length > best.extension.Length || - registration.extension.Length == best.extension.Length && registration.priority > best.priority) + if (best is null + || registration.extension.Length > best.extension.Length + || registration.extension.Length == best.extension.Length + && registration.priority > best.priority) { best = registration; } @@ -150,20 +144,12 @@ IReadOnlyList registrations ) { if (!typeof(AssetObject).IsAssignableFrom(assetType)) { - throw new InvalidOperationException( - $"Asset icon declaration on '{declarationType.FullName}' targets " + - $"'{assetType.FullName}', " + - $"which does not derive from '{typeof(AssetObject).FullName}'."); + throw new InvalidOperationException($"Asset icon declaration on '{declarationType.FullName}' targets " + $"'{assetType.FullName}', " + $"which does not derive from '{typeof(AssetObject).FullName}'."); } - if (registrations.Any(value => - value.assetType == assetType && - value.useForChildren == attribute.useForChildren && - value.priority == attribute.priority)) + if (registrations.Any(value => value.assetType == assetType && value.useForChildren == attribute.useForChildren && value.priority == attribute.priority)) { - throw new InvalidOperationException( - $"Asset icon registration for '{assetType.FullName}' conflicts at priority " + - $"{attribute.priority}."); + throw new InvalidOperationException($"Asset icon registration for '{assetType.FullName}' conflicts at priority " + $"{attribute.priority}."); } } @@ -173,13 +159,9 @@ private static void ValidateExtension( string extension, IReadOnlyList registrations ) { - if (registrations.Any(value => - string.Equals(value.extension, extension, StringComparison.OrdinalIgnoreCase) && - value.priority == attribute.priority)) + if (registrations.Any(value => string.Equals(value.extension, extension, StringComparison.OrdinalIgnoreCase) && value.priority == attribute.priority)) { - throw new InvalidOperationException( - $"Asset icon declaration on '{declarationType.FullName}' for extension '{extension}' " + - $"conflicts at priority {attribute.priority}."); + throw new InvalidOperationException($"Asset icon declaration on '{declarationType.FullName}' for extension '{extension}' " + $"conflicts at priority {attribute.priority}."); } } @@ -187,7 +169,6 @@ internal sealed record Snapshot( TypeRegistration[] typeRegistrations, ExtensionRegistration[] extensionRegistrations ); - internal sealed record TypeRegistration( Type assetType, bool useForChildren, @@ -195,7 +176,6 @@ internal sealed record TypeRegistration( Type declarationType, string icon ); - internal sealed record ExtensionRegistration( string extension, int priority, diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/BuiltInAssetIcons.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/BuiltInAssetIcons.cs similarity index 88% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/BuiltInAssetIcons.cs rename to src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/BuiltInAssetIcons.cs index 29156b80a..5079b01f9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Icons/BuiltInAssetIcons.cs +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Icons/BuiltInAssetIcons.cs @@ -1,8 +1,9 @@ -using Inno.Adapter.Presentation.ImGui; +using Inno.Editor.ImGui; +using Inno.Editor.Assets; +using Inno.Editor.Core; using Inno.Editor.Settings; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets.ImGui; [AssetIcon(".txt", ImGuiIcon.FileLines)] [AssetIcon(".json", ImGuiIcon.FileLines)] [AssetIcon(".yaml", ImGuiIcon.FileLines)] diff --git a/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inno.Editor.Assets.ImGui.csproj b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inno.Editor.Assets.ImGui.csproj new file mode 100644 index 000000000..601723f9e --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inno.Editor.Assets.ImGui.csproj @@ -0,0 +1,28 @@ + + + net10.0 + 14.0 + disable + enable + true + $(WarningsAsErrors);CS1572;CS1573;CS1591 + Editor + + + + + + + + + + + + + + + + + + + diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetSelectionInspectionDrawer.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inspection/AssetSelectionInspectionDrawer.cs similarity index 76% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetSelectionInspectionDrawer.cs rename to src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inspection/AssetSelectionInspectionDrawer.cs index 5b81ce30a..27a7a6550 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/AssetEditors/AssetSelectionInspectionDrawer.cs +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Inspection/AssetSelectionInspectionDrawer.cs @@ -1,21 +1,18 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Assets; using System; - using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.Inspection; using Inno.Editor.Interactions; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Adapter.Presentation.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; - -namespace Inno.Editor.Panel.FileBrowser; +namespace Inno.Editor.Assets.ImGui; [InspectionDrawer(typeof(AssetFileEntry))] internal sealed class AssetSelectionInspectionDrawer : InspectionDrawer { - private readonly AssetEditorModule m_assets; - + private readonly AssetBrowserModule m_assets; /// /// Creates an Asset source drawer that shares the Asset Browser presentation registry. /// @@ -25,7 +22,7 @@ internal sealed class AssetSelectionInspectionDrawer : InspectionDrawer /// Thrown when is . /// - internal AssetSelectionInspectionDrawer(AssetEditorModule assets) + internal AssetSelectionInspectionDrawer(AssetBrowserModule assets) { m_assets = assets ?? throw new ArgumentNullException(nameof(assets)); } @@ -51,7 +48,6 @@ protected override string GetIcon( InspectionDrawContext context, AssetFileEntry target ) => m_assets.GetIcon(target); - /// /// Binds a caller-visible label to the current inspection target. /// @@ -67,9 +63,7 @@ AssetFileEntry target protected override (string name, Action? setter) BindName( InspectionDrawContext context, AssetFileEntry target - ) - => (target.nameWithoutExtension, null); - + ) => (target.nameWithoutExtension, null); /// /// Renders the header presentation for the current editor frame. /// @@ -82,9 +76,7 @@ AssetFileEntry target protected override void DrawHeader( InspectionDrawContext context, AssetFileEntry target - ) - => EditorWidget.ColoredText(EditorPalette.assetBreadcrumbText, target.assetPath.ToString()); - + ) => EditorUI.current.widgets.ColoredText(EditorPalette.contextText, target.assetPath.ToString()); /// /// Renders the value presentation for the current editor frame. /// @@ -98,22 +90,16 @@ protected override void Draw( InspectionDrawContext context, AssetFileEntry entry ) { - if (!EditorWidget.SectionHeader( - "Asset", - "This view describes the selected source entry. Structured assets may replace it with a dedicated authoring drawer.")) + if (!EditorUI.current.widgets.SectionHeader("Asset", "This view describes the selected source entry. Structured assets may replace it with a dedicated authoring drawer.")) { return; } DrawMetadata( "Type", - m_assets.IsPluginRoot(entry) - ? "IPlugin" - : entry.isDirectory && AssetSample.HasSampleDirectoryName(entry.assetPath) - ? "ISample" - : entry.isDirectory - ? "Directory" - : "File"); + m_assets.edits.IsPluginRoot(entry) ? "IPlugin" : entry.isDirectory + && AssetSample.HasSampleDirectoryName(entry.assetPath) ? "ISample" : entry.isDirectory ? "Directory" : "File" + ); if (!entry.isDirectory) { DrawMetadata("Extension", string.IsNullOrEmpty(entry.extension) ? "" : entry.extension); @@ -124,9 +110,6 @@ private static void DrawMetadata( string label, string value ) { - EditorWidget.PropertyRow( - "asset.metadata." + label, - label, - () => EditorWidget.Hint(value)); + EditorUI.current.widgets.PropertyRow("asset.metadata." + label, label, () => EditorUI.current.widgets.Hint(value)); } } diff --git a/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs new file mode 100644 index 000000000..bfbe2a40e --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserModule.cs @@ -0,0 +1,150 @@ +using Inno.Editor.ImGui; +using System; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Editor.Assets; +using Inno.Editor.Core; +using Inno.Editor.Inspection; +using Inno.Editor.Interactions; +using Inno.Editor.Settings; +using Inno.Extensibility.Types; + +namespace Inno.Editor.Assets.ImGui; +/// +/// Owns asset navigation and icon presentation independently from source mutations. +/// +[EditorModule("asset-browser", order: 100)] +public sealed class AssetBrowserModule : EditorModule, IInspectionIconProvider +{ + private readonly EditorSettings m_settings; + private readonly AssetIconRegistry m_icons; + /// + /// Creates the asset presentation module for the current editor generation. + /// + /// + /// The independent authoritative asset editing service. + /// + /// + /// The editor selection and action owner. + /// + /// + /// The editor settings that supply semantic icon choices. + /// + /// + /// The active extension type catalog. + /// + /// + /// A required owner is null. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public AssetBrowserModule( + AssetEditingModule edits, + EditorInteractions interactions, + EditorSettings settings, + TypeCatalog types + ) { + this.edits = edits ?? throw new ArgumentNullException(nameof(edits)); + m_settings = settings ?? throw new ArgumentNullException(nameof(settings)); + m_icons = new AssetIconRegistry(settings, types); + browser = new AssetBrowserState(interactions, edits.pipeline); + } + + /// + /// Gets the source editing service; this module does not implement source transactions. + /// + public AssetEditingModule edits { get; } + /// + /// Gets navigation state; object selection belongs to the shared interaction owner. + /// + public AssetBrowserState browser { get; } + /// + /// Gets the configured folder glyph used by asset navigation. + /// + public string folderIcon => m_settings.Get("Editor/Appearance/Icons/Folder").GetAsString("value", Inno.Editor.ImGui.ImGuiIcon.Folder)!; + + /// + /// Captures an immutable snapshot of the current observable state. + /// + /// + /// The lifecycle or domain state applied by this operation. + /// + protected override void Capture(EditorState state) + { + state.Set("root", browser.root.ToString()); + state.Set("assetsDirectory", browser.GetDirectory(AssetBrowserRoot.Assets)); + state.Set("pluginsDirectory", browser.GetDirectory(AssetBrowserRoot.Plugins)); + } + + /// + /// Restores the supplied snapshot while preserving current invariants. + /// + /// + /// The lifecycle or domain state applied by this operation. + /// + protected override void Restore(EditorState state) + { + AssetBrowserRoot restoredRoot = Enum.TryParse(state.Get("root", string.Empty), out AssetBrowserRoot parsedRoot) ? parsedRoot : AssetBrowserRoot.Assets; + string assetsDirectory = RestoreDirectory(AssetBrowserRoot.Assets, state.Get("assetsDirectory", string.Empty)); + string pluginsDirectory = RestoreDirectory(AssetBrowserRoot.Plugins, state.Get("pluginsDirectory", string.Empty)); + browser.Restore(restoredRoot, assetsDirectory, pluginsDirectory); + } + + private string RestoreDirectory( + AssetBrowserRoot root, + string path + ) { + string directory = AssetPath.Parse(path).ToString(); + if (string.IsNullOrEmpty(directory)) + return string.Empty; + AssetPath isolated = AssetPath.Parse(directory); + if ((root == AssetBrowserRoot.Assets) != (isolated.source == AssetSourceId.project)) + return string.Empty; + while (!IsAvailableDirectory(directory)) + { + string parent = ParentDirectory(directory); + if (string.Equals(parent, directory, StringComparison.Ordinal)) + return string.Empty; + directory = parent; + } + + return directory; + } + + private bool IsAvailableDirectory(string path) => edits.pipeline.TryGetFileSystemEntry(AssetPath.Parse(path), out AssetFileEntry entry) && entry.isDirectory; + /// + /// Resolves the presentation icon registered for an asset type or source extension. + /// + /// + /// The source entry whose presentation icon should be resolved. + /// + /// + /// The most specific registered icon, or the built-in directory or file icon when no + /// registration matches the entry. + /// + /// + /// Thrown when is . + /// + public string GetIcon(AssetFileEntry entry) + { + ArgumentNullException.ThrowIfNull(entry); + if (entry.isDirectory) + return folderIcon; + _ = edits.pipeline.TryGetAssetType(entry.assetPath, out Type? assetType); + if (m_icons.TryResolve(assetType, entry.assetPath.ToString(), out string icon)) + { + return icon; + } + + return m_settings.Get("Editor/Appearance/Icons/File").GetAsString("value", Inno.Editor.ImGui.ImGuiIcon.File)!; + } + + /// + protected override void OnDispose() => m_icons.Dispose(); + private static string ParentDirectory(string value) + { + AssetPath path = AssetPath.Parse(value); + string local = path.localPath; + int slash = local.LastIndexOf('/'); + return new AssetPath(path.source, slash < 0 ? string.Empty : local[..slash]).ToString(); + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs similarity index 71% rename from src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs rename to src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs index 35c008cd8..edc2981d1 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.FileBrowser/Browser/AssetBrowserState.cs +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Navigation/AssetBrowserState.cs @@ -1,15 +1,13 @@ - +using Inno.Editor.Assets; using System; using System.Linq; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Editor.Core; using Inno.Editor.Interactions; using Inno.Editor.Inspection; -namespace Inno.Editor.Panel.FileBrowser; - +namespace Inno.Editor.Assets.ImGui; /// /// Identifies the authoring or installed-content root displayed by the Asset Browser. /// @@ -19,7 +17,6 @@ public enum AssetBrowserRoot /// The writable project Assets authoring root. /// Assets, - /// /// The read-only Plugins installation root. /// @@ -35,7 +32,6 @@ public sealed class AssetBrowserState private readonly AssetPipeline m_assets; private string m_assetsDirectory = string.Empty; private string m_pluginsDirectory = string.Empty; - /// /// Creates Asset Browser navigation state. /// @@ -60,14 +56,10 @@ AssetPipeline assets /// Gets the root currently displayed by the Asset Browser. /// public AssetBrowserRoot root { get; private set; } - /// /// Gets the current directory inside . An empty value identifies that root's overview. /// - public string currentDirectory => root == AssetBrowserRoot.Assets - ? m_assetsDirectory - : m_pluginsDirectory; - + public string currentDirectory => root == AssetBrowserRoot.Assets ? m_assetsDirectory : m_pluginsDirectory; /// /// Gets the most recently visited writable project directory, independently of the displayed root. /// @@ -88,41 +80,39 @@ AssetPipeline assets public string? GetSelectedPath(EditorContext context) { ArgumentNullException.ThrowIfNull(context); - if (m_interactions.selection.selectedTarget is AssetInspectionSelection group - && m_assets.TryGetInfo(group.primaryAssetId, out AssetInfo? info) && info is not null) - return info.assetPath.ToString(); return (m_interactions.selection.selectedTarget as Inno.Assets.Pipeline.AssetFileEntry)?.assetPath.ToString(); } - internal bool IsSelected( + /// + /// Checks whether the source entry belongs to the current asset selection. + /// + /// + /// The current editor callback context. + /// + /// + /// The indexed source identity to inspect. + /// + /// + /// True when the entry is selected; otherwise false. + /// + public bool IsSelected( EditorContext context, AssetFileEntry entry - ) - => m_interactions.selection.selectedTarget is AssetInspectionSelection group - ? m_assets.TryGetInfo(entry.assetPath, out AssetInfo? info) && info is not null && group.assetIds.Contains(info.persistentId) - : string.Equals(GetSelectedPath(context), entry.assetPath.ToString(), StringComparison.Ordinal); - - internal void ToggleSelection( + ) => m_interactions.selection.Contains(entry); + /// + /// Adds or removes an asset from the current selection without retaining runtime asset objects. + /// + /// + /// The current editor callback context. + /// + /// + /// The indexed source identity to toggle. + /// + public void ToggleSelection( EditorContext context, AssetFileEntry entry ) { - if (entry.isDirectory || !m_assets.TryGetInfo(entry.assetPath, out AssetInfo? info) || info is null) - { - Select(context, entry.assetPath.ToString()); - return; - } - var ids = m_interactions.selection.selectedTarget is AssetInspectionSelection group ? group.assetIds.ToList() : new System.Collections.Generic.List(); - if (ids.Count == 0 && m_interactions.selection.selectedTarget is AssetFileEntry selected && !selected.isDirectory - && m_assets.TryGetInfo(selected.assetPath, out AssetInfo? previous) && previous is not null) - ids.Add(previous.persistentId); - if (!ids.Remove(info.persistentId)) - ids.Add(info.persistentId); - if (ids.Count == 0) - Select(context, null); - else if (ids.Count == 1 && m_assets.TryGetInfo(ids[0], out AssetInfo? remaining) && remaining is not null) - Select(context, remaining.assetPath.ToString()); - else - m_interactions.SetSelection(new AssetInspectionSelection(ids)); + m_interactions.ToggleSelection(entry); } /// @@ -132,7 +122,6 @@ AssetFileEntry entry /// The authoring or installed-content root to display. /// public void SetRoot(AssetBrowserRoot value) => root = value; - /// /// Sets the current Asset Browser directory and infers its root from the isolated source identity. /// @@ -149,9 +138,7 @@ public void SetCurrentDirectory(string relativePath) } AssetPath path = AssetPath.Parse(normalized); - AssetBrowserRoot targetRoot = path.source == AssetSourceId.project - ? AssetBrowserRoot.Assets - : AssetBrowserRoot.Plugins; + AssetBrowserRoot targetRoot = path.source == AssetSourceId.project ? AssetBrowserRoot.Assets : AssetBrowserRoot.Plugins; SetDirectory(targetRoot, normalized); root = targetRoot; } @@ -176,21 +163,25 @@ public void Select( object? target = null; if (!string.IsNullOrWhiteSpace(relativePath)) { - if (m_assets.TryGetFileSystemEntry( - Inno.Assets.AssetPath.Parse(Normalize(relativePath)), - out Inno.Assets.Pipeline.AssetFileEntry entry)) + if (m_assets.TryGetFileSystemEntry(Inno.Assets.AssetPath.Parse(Normalize(relativePath)), out Inno.Assets.Pipeline.AssetFileEntry entry)) { target = entry; } } - _ = m_interactions.For(FileBrowserInteractionIds.C_AREA, target).Select(); - } - internal string GetDirectory(AssetBrowserRoot targetRoot) - => targetRoot == AssetBrowserRoot.Assets - ? m_assetsDirectory - : m_pluginsDirectory; + m_interactions.SetSelection(target); + } + /// + /// Gets the last visited isolated directory for a navigation root. + /// + /// + /// The root whose independent navigation position is requested. + /// + /// + /// The source-relative directory, or an empty string for the root. + /// + public string GetDirectory(AssetBrowserRoot targetRoot) => targetRoot == AssetBrowserRoot.Assets ? m_assetsDirectory : m_pluginsDirectory; internal void Restore( AssetBrowserRoot restoredRoot, string assetsDirectory, @@ -201,7 +192,19 @@ string pluginsDirectory root = restoredRoot; } - internal void SetLocation( + /// + /// Changes the displayed source root and its isolated navigation directory. + /// + /// + /// The navigation root that owns the requested directory. + /// + /// + /// The isolated source-relative directory; empty selects the root. + /// + /// + /// The directory belongs to a different source root. + /// + public void SetLocation( AssetBrowserRoot targetRoot, string directory ) { @@ -212,9 +215,7 @@ string directory bool isProject = path.source == AssetSourceId.project; if ((targetRoot == AssetBrowserRoot.Assets) != isProject) { - throw new ArgumentException( - $"Directory '{directory}' does not belong to the '{targetRoot}' root.", - nameof(directory)); + throw new ArgumentException($"Directory '{directory}' does not belong to the '{targetRoot}' root.", nameof(directory)); } } diff --git a/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Properties/ScriptingApi.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Properties/ScriptingApi.cs new file mode 100644 index 000000000..747e04fa4 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Properties/ScriptingApi.cs @@ -0,0 +1,8 @@ +using Inno.Editor.Assets.ImGui; +using Inno.Scripting.Api; + +[assembly: ScriptingApiNamespace("InnoEditor.Assets", "Inno.Editor.Assets.ImGui", ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetBrowserRoot), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetBrowserState), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetBrowserStyle), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(AssetIconAttribute), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs new file mode 100644 index 000000000..5b2d2b7f2 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Assets.ImGui/Styling/AssetBrowserStyle.cs @@ -0,0 +1,158 @@ +using System; +using System.Numerics; +using Inno.Editor.ImGui; + +namespace Inno.Editor.Assets.ImGui; + +/// +/// Freezes Browser-specific layout values from the shared UI scale without retaining a session. +/// The default value uses the standard 100 percent scale. +/// +public readonly struct AssetBrowserStyle +{ + private readonly float m_zoom; + + /// + /// Captures the current shared scale for one Browser presentation pass. + /// + /// + /// The existing session metrics; later mutations do not change this value. + /// + /// + /// The metrics are null. + /// + public AssetBrowserStyle(EditorStyleMetrics metrics) + { + ArgumentNullException.ThrowIfNull(metrics); + m_zoom = metrics.zoom; + } + + /// + /// Gets asset browser window padding. + /// + public Vector2 windowPadding => ScaleVector(new(2f, 1f)); + + /// + /// Gets asset browser cell padding. + /// + public Vector2 cellPadding => ScaleVector(new(5f, 2f)); + + /// + /// Gets asset browser item spacing. + /// + public Vector2 itemSpacing => ScaleVector(new(2f, 2f)); + + /// + /// Gets asset browser frame rounding. + /// + public float frameRounding => Scale(1f); + + /// + /// Gets asset breadcrumb bar height. + /// + public float breadcrumbHeight => Scale(25f); + + /// + /// Gets minimum asset grid cell size. + /// + public float gridMinimumCellSize => Scale(32f); + + /// + /// Gets asset grid cell padding. + /// + public float gridCellPadding => Scale(2f); + + /// + /// Gets default asset grid scale. + /// + public float gridDefaultScale => 3f; + + /// + /// Gets minimum asset grid scale. + /// + public float gridMinimumScale => 1f; + + /// + /// Gets maximum asset grid scale. + /// + public float gridMaximumScale => 10f; + + /// + /// Gets spacing between asset list rows. + /// + public float listRowSpacing => Scale(2f); + + /// + /// Gets the default normalized position of the asset list name/type separator. + /// + public float listNameSeparatorPosition => 0.4f; + + /// + /// Gets the default normalized position of the asset list type/source separator. + /// + public float listTypeSeparatorPosition => 0.7f; + + /// + /// Gets the minimum normalized width reserved for each asset list column. + /// + public float listMinimumColumnRatio => 0.1f; + + /// + /// Gets the horizontal hit width of an asset list column separator. + /// + public float listSeparatorHitWidth => Scale(8f); + + /// + /// Gets the horizontal inset between an asset list separator and column content. + /// + public float listContentHorizontalPadding => Scale(6f); + + /// + /// Gets horizontal padding for asset grid labels. + /// + public float gridLabelHorizontalPadding => Scale(6f); + + /// + /// Gets bottom padding for asset grid labels. + /// + public float gridLabelBottomPadding => Scale(3f); + + /// + /// Gets the additional vertical spacing between asset grid label lines. + /// + public float gridLabelLineSpacing => Scale(-2f); + + /// + /// Gets fixed padding added to calculated asset grid cells. + /// + public float gridFixedCellPadding => Scale(8f); + + /// + /// Gets the scale bias added to asset grid icons. + /// + public float gridScaleBias => 2f; + + /// + /// Gets the top inset reserved above an asset grid icon. + /// + public float gridIconTopPadding => Scale(6f); + + /// + /// Gets the horizontal inset that constrains an asset grid icon inside its card. + /// + public float gridIconHorizontalPadding => Scale(6f); + + /// + /// Gets the vertical spacing between an asset grid icon and its label. + /// + public float gridIconLabelSpacing => Scale(4f); + + /// + /// Gets spacing around breadcrumb separators. + /// + public float breadcrumbSpacing => Scale(4f); + + private float Scale(float value) => value * (m_zoom == 0f ? 1f : m_zoom); + + private Vector2 ScaleVector(Vector2 value) => value * (m_zoom == 0f ? 1f : m_zoom); +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs new file mode 100644 index 000000000..64eb02377 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasLayout.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Inno.Core.Graphs; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Owns a document's neutral node display snapshot, independently of viewport transforms and domain instances. +/// +public sealed class GraphCanvasLayout +{ + private IReadOnlyList m_nodes = Array.Empty(); + private Dictionary m_byId = []; + + /// + /// Gets nodes in draw order; reverse order determines overlapping-node hit priority. + /// + public IReadOnlyList nodes => m_nodes; + + /// + /// Atomically replaces a complete display snapshot, reusing unchanged node geometry by value. + /// + /// + /// The current neutral node descriptions in draw order. + /// + /// + /// The number of inserted, changed or removed nodes. Ordering changes do not rebuild geometry. + /// + /// + /// The collection or one of its nodes is null. + /// + /// + /// A node identity occurs more than once; the previous snapshot remains active. + /// + public int SetNodes(IEnumerable nodes) + { + ArgumentNullException.ThrowIfNull(nodes); + GraphCanvasNode[] candidate = nodes.ToArray(); + var byId = new Dictionary(candidate.Length); + int changes = 0; + for (int index = 0; index < candidate.Length; index++) + { + GraphCanvasNode node = candidate[index]; + ArgumentNullException.ThrowIfNull(node); + if (m_byId.TryGetValue(node.id, out GraphCanvasNode? current) && Equivalent(current, node)) + candidate[index] = node = current; + else + changes++; + if (!byId.TryAdd(node.id, node)) + throw new ArgumentException("A graph layout cannot contain duplicate node identities.", nameof(nodes)); + } + changes += m_byId.Keys.Count(id => !byId.ContainsKey(id)); + if (changes == 0 && m_nodes.SequenceEqual(candidate)) + return 0; + m_nodes = Array.AsReadOnly(candidate); + m_byId = byId; + return changes; + } + + /// + /// Resolves a node in this display snapshot without consulting domain or live-object registries. + /// + /// + /// The graph node identity to resolve. + /// + /// + /// The current neutral node, or null when it is absent. + /// + /// + /// True when the current snapshot contains the node. + /// + public bool TryGetNode( + GraphNodeId id, + [NotNullWhen(true)] out GraphCanvasNode? node + ) => m_byId.TryGetValue(id, out node); + + private static bool Equivalent( + GraphCanvasNode left, + GraphCanvasNode right + ) => ReferenceEquals(left, right) || left.position == right.position && left.size == right.size + && left.headerHeight == right.headerHeight && left.title == right.title && left.ports.SequenceEqual(right.ports); +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs new file mode 100644 index 000000000..61df6f786 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasNode.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Inno.Core.Graphs; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Freezes one node's geometric and textual presentation without owning its domain record. +/// +public sealed class GraphCanvasNode +{ + /// + /// Creates immutable display data that can safely outlive the extension that described it. + /// + /// + /// The existing graph node identity. + /// + /// + /// The node's document position in graph coordinates. + /// + /// + /// Finite positive width and height in graph coordinates. + /// + /// + /// The header extent, within the node height. + /// + /// + /// A display title already interpreted by its domain owner. + /// + /// + /// Immutable neutral endpoint descriptions; the collection is copied. + /// + /// + /// The title or port collection is null. + /// + /// + /// A node or port identity is invalid, duplicated or owned by a different node. + /// + /// + /// The node's geometry is nonfinite or has an invalid extent. + /// + public GraphCanvasNode( + GraphNodeId id, + GraphPosition position, + Vector2 size, + float headerHeight, + string title, + IEnumerable ports + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(id.value); + ArgumentNullException.ThrowIfNull(title); + ArgumentNullException.ThrowIfNull(ports); + if (!float.IsFinite(position.x) || !float.IsFinite(position.y)) + throw new ArgumentOutOfRangeException(nameof(position)); + if (!float.IsFinite(size.X) || !float.IsFinite(size.Y) || size.X <= 0f || size.Y <= 0f) + throw new ArgumentOutOfRangeException(nameof(size)); + if (!float.IsFinite(headerHeight) || headerHeight < 0f || headerHeight > size.Y) + throw new ArgumentOutOfRangeException(nameof(headerHeight)); + GraphCanvasPort[] frozen = ports.ToArray(); + if (frozen.Any(port => port.endpoint.nodeId != id) + || frozen.Select(static port => (port.endpoint, port.direction)).Distinct().Count() != frozen.Length) + throw new ArgumentException("Node ports require unique endpoint/direction pairs owned by the same node.", nameof(ports)); + this.id = id; + this.position = position; + this.size = size; + this.headerHeight = headerHeight; + this.title = title; + this.ports = Array.AsReadOnly(frozen); + } + + /// + /// Gets the stable graph identity represented by this display snapshot. + /// + public GraphNodeId id { get; } + + /// + /// Gets the document position, before transient gesture previews. + /// + public GraphPosition position { get; } + + /// + /// Gets the unscaled node extent. + /// + public Vector2 size { get; } + + /// + /// Gets the unscaled header extent used for node movement hit testing. + /// + public float headerHeight { get; } + + /// + /// Gets the domain-supplied display title. + /// + public string title { get; } + + /// + /// Gets copied neutral port descriptions; no callback or domain object is retained. + /// + public IReadOnlyList ports { get; } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs new file mode 100644 index 000000000..9dc09c562 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerController.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Coordinates neutral selection, movement previews, box selection and connection intents for one canvas. +/// +public sealed class GraphCanvasPointerController +{ + private readonly GraphCanvasState m_state; + private readonly Dictionary m_starts = []; + private readonly Dictionary m_preview = []; + private readonly IReadOnlyDictionary m_previewView; + private Vector2 m_pointerStart; + private bool m_dragging; + private bool m_boxSelecting; + private GraphNodeId[] m_boxSelection = []; + + /// + /// Creates a pointer owner over existing document navigation and selection state. + /// + /// + /// The existing canvas state, which remains the selection and pending-connection authority. + /// + /// + /// The state is null. + /// + public GraphCanvasPointerController(GraphCanvasState state) + { + ArgumentNullException.ThrowIfNull(state); + m_state = state; + m_previewView = new ReadOnlyDictionary(m_preview); + } + + /// + /// Gets frame-local movement previews; callers must not persist this changing view. + /// + public IReadOnlyDictionary previewPositions => m_previewView; + + /// + /// Gets whether a node movement preview is active. + /// + public bool isMoving => m_dragging; + + /// + /// Gets whether a selection box is active. + /// + public bool isBoxSelecting => m_boxSelecting; + + /// + /// Gets the initial screen-space corner of the current selection box. + /// + public Vector2 boxStart => m_pointerStart; + + /// + /// Cancels transient presentation state without applying or undoing document changes. + /// + public void Cancel() + { + m_dragging = m_boxSelecting = false; + m_starts.Clear(); + m_preview.Clear(); + m_boxSelection = []; + m_state.CancelConnection(); + } + + /// + /// Starts movement for explicit node IDs, supporting domain-owned group-header composition. + /// + /// + /// The neutral snapshot supplying initial positions. + /// + /// + /// Selected stable graph identities; absent nodes remain selected but are not moved. + /// + /// + /// The screen-space gesture start. + /// + public void BeginMove( + GraphCanvasLayout layout, + IEnumerable nodes, + Vector2 pointer + ) { + ArgumentNullException.ThrowIfNull(layout); + ArgumentNullException.ThrowIfNull(nodes); + var starts = new Dictionary(); + foreach (GraphNodeId id in nodes) + if (layout.TryGetNode(id, out GraphCanvasNode? node)) + starts.TryAdd(id, node.position); + Cancel(); + foreach (var entry in starts) + m_starts.Add(entry.Key, entry.Value); + m_dragging = m_starts.Count != 0; + m_pointerStart = pointer; + } + + /// + /// Consumes one routed frame and returns data-editing intents without invoking domain code. + /// + /// + /// The current canvas projection. + /// + /// + /// The immutable neutral node snapshot used by drawing and hit testing. + /// + /// + /// Current pointer input after the caller's focus, navigation and overlay routing. + /// + /// + /// Completed neutral intents. The feature owns validation, atomic mutation and History recording. + /// + /// + /// The pointer position is nonfinite. + /// + public GraphCanvasPointerResult Step( + GraphCanvasView view, + GraphCanvasLayout layout, + GraphCanvasPointerInput input + ) { + ArgumentNullException.ThrowIfNull(layout); + if (!float.IsFinite(input.position.X) || !float.IsFinite(input.position.Y)) + throw new ArgumentOutOfRangeException(nameof(input)); + if (input.cancel) + { + Cancel(); + return default; + } + if (input.readOnly && (m_dragging || m_state.pendingConnection is not null)) + Cancel(); + GraphNodeId? pressed = null; + if (input.isPressed && input.isHovered) + { + GraphCanvasPort? port = view.HitPort(layout, input.position); + if (!input.readOnly && port is { isMissing: false } endpoint) + m_state.BeginConnection(endpoint.endpoint, endpoint.direction); + else + { + GraphCanvasNode? node = view.HitNode(layout, input.position); + if (node is not null && input.position.Y <= view.NodeBounds(node).minimum.Y + node.headerHeight * view.zoom) + { + pressed = node.id; + if (input.shift) + m_state.ToggleNode(node.id); + else if (!m_state.selectedNodes.Contains(node.id)) + m_state.SelectNodes([node.id]); + if (!input.readOnly) + BeginMove(layout, m_state.selectedNodes, input.position); + } + else if (node is null) + { + if (!input.shift) + m_state.ClearSelection(); + m_boxSelecting = true; + m_boxSelection = input.shift ? m_state.selectedNodes.ToArray() : []; + m_pointerStart = input.position; + } + } + } + if (m_dragging && input.isDown) + { + GraphPosition start = view.ToGraph(m_pointerStart); + GraphPosition current = view.ToGraph(input.position); + foreach ((GraphNodeId id, GraphPosition position) in m_starts) + m_preview[id] = new(position.x + current.x - start.x, position.y + current.y - start.y); + } + if (!input.isReleased) + return new(pressed, null, null, null, null); + + IReadOnlyDictionary? moved = null; + if (m_dragging && m_preview.Any(entry => m_starts[entry.Key] != entry.Value)) + moved = new ReadOnlyDictionary(new Dictionary(m_preview)); + if (m_boxSelecting) + m_state.SelectNodes(m_boxSelection.Concat(view.BoxNodes(layout, m_pointerStart, input.position)).Distinct()); + GraphEndpoint? output = null; + GraphEndpoint? targetInput = null; + GraphEndpoint? released = null; + if (!input.readOnly && input.isHovered && m_state.pendingConnection is GraphEndpoint source + && m_state.pendingConnectionDirection is GraphPortDirection direction) + { + GraphCanvasPort? destination = view.HitPort(layout, input.position); + if (destination is { isMissing: false } target && target.direction != direction) + { + output = direction == GraphPortDirection.Output ? source : target.endpoint; + targetInput = direction == GraphPortDirection.Input ? source : target.endpoint; + } + else if (destination is null && direction == GraphPortDirection.Output + && input.isHovered && view.HitNode(layout, input.position) is null) + released = source; + } + Cancel(); + return new(pressed, moved, output, targetInput, released); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs new file mode 100644 index 000000000..9581dd6fd --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerInput.cs @@ -0,0 +1,49 @@ +using System.Numerics; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Carries one frame's already-routed pointer input without depending on a native backend. +/// +public readonly struct GraphCanvasPointerInput +{ + /// + /// Gets the screen-space pointer position. + /// + public Vector2 position { get; init; } + + /// + /// Gets whether this canvas owns the pointer for starting an interaction. + /// + public bool isHovered { get; init; } + + /// + /// Gets whether the primary pointer button was pressed this frame. + /// + public bool isPressed { get; init; } + + /// + /// Gets whether the primary pointer button remains held. + /// + public bool isDown { get; init; } + + /// + /// Gets whether the primary pointer button was released this frame. + /// + public bool isReleased { get; init; } + + /// + /// Gets whether selection extension is requested. + /// + public bool shift { get; init; } + + /// + /// Gets whether the current preview must be canceled without modifying document data. + /// + public bool cancel { get; init; } + + /// + /// Gets whether selection is allowed while data-editing intents are prohibited. + /// + public bool readOnly { get; init; } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs new file mode 100644 index 000000000..3ffdca15c --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPointerResult.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Inno.Core.Graphs; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Reports neutral interaction intents for the feature controller to validate and apply through History. +/// +public readonly struct GraphCanvasPointerResult +{ + internal GraphCanvasPointerResult( + GraphNodeId? nodePressed, + IReadOnlyDictionary? movedNodes, + GraphEndpoint? output, + GraphEndpoint? input, + GraphEndpoint? releasedOutput + ) { + this.nodePressed = nodePressed; + this.movedNodes = movedNodes; + this.output = output; + this.input = input; + this.releasedOutput = releasedOutput; + } + + /// + /// Gets the pressed node header, or null when no header was pressed. + /// + public GraphNodeId? nodePressed { get; } + + /// + /// Gets completed movement positions, or null when no data edit was requested. + /// + public IReadOnlyDictionary? movedNodes { get; } + + /// + /// Gets a requested connection's output endpoint; its domain type still requires validation. + /// + public GraphEndpoint? output { get; } + + /// + /// Gets a requested connection's input endpoint, paired with the output endpoint. + /// + public GraphEndpoint? input { get; } + + /// + /// Gets an output released over empty canvas, allowing the feature to offer a creation workflow. + /// + public GraphEndpoint? releasedOutput { get; } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs new file mode 100644 index 000000000..59fe75f61 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasPort.cs @@ -0,0 +1,108 @@ +using System; +using System.Numerics; +using Inno.Core.Graphs; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Describes a neutral port's node-relative geometry and display state without retaining extensions. +/// +public readonly record struct GraphCanvasPort +{ + /// + /// Creates a port presentation whose position is measured in unscaled node coordinates. + /// + /// + /// The existing document endpoint represented by this port. + /// + /// + /// The connection direction, independent of any domain-specific type system. + /// + /// + /// Node-relative graph coordinates of the port center. + /// + /// + /// The current display label, without domain objects or callbacks. + /// + /// + /// Whether to distinguish an optional input visually. + /// + /// + /// Whether retained document structure currently lacks its domain implementation. + /// + /// + /// Optional node-relative label bounds used by drawing and row hit testing; null limits hits to the socket. + /// + /// + /// An endpoint identity is empty. + /// + /// + /// The label is null. + /// + /// + /// The position or direction is invalid. + /// + public GraphCanvasPort( + GraphEndpoint endpoint, + GraphPortDirection direction, + Vector2 position, + string label, + bool isOptional = false, + bool isMissing = false, + (Vector2 minimum, Vector2 maximum)? labelBounds = null + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(endpoint.nodeId.value); + ArgumentException.ThrowIfNullOrWhiteSpace(endpoint.portId.value); + ArgumentNullException.ThrowIfNull(label); + if (!float.IsFinite(position.X) || !float.IsFinite(position.Y)) + throw new ArgumentOutOfRangeException(nameof(position)); + if (direction is not GraphPortDirection.Input and not GraphPortDirection.Output) + throw new ArgumentOutOfRangeException(nameof(direction)); + if (labelBounds is { } bounds && (!float.IsFinite(bounds.minimum.X) || !float.IsFinite(bounds.minimum.Y) + || !float.IsFinite(bounds.maximum.X) || !float.IsFinite(bounds.maximum.Y) + || bounds.maximum.X < bounds.minimum.X || bounds.maximum.Y < bounds.minimum.Y)) + throw new ArgumentOutOfRangeException(nameof(labelBounds)); + this.endpoint = endpoint; + this.direction = direction; + this.position = position; + this.label = label; + this.isOptional = isOptional; + this.isMissing = isMissing; + this.labelBounds = labelBounds; + } + + /// + /// Gets the document endpoint; it does not identify a live engine object. + /// + public GraphEndpoint endpoint { get; } + + /// + /// Gets the neutral connection direction. + /// + public GraphPortDirection direction { get; } + + /// + /// Gets the node-relative center in graph coordinates. + /// + public Vector2 position { get; } + + /// + /// Gets the current display label. + /// + public string label { get; } + + /// + /// Gets whether the input is visually optional. + /// + public bool isOptional { get; } + + /// + /// Gets whether this retained endpoint currently lacks an implementation. + /// + public bool isMissing { get; } + + /// + /// Gets optional node-relative label geometry shared by rendering and row hit testing. + /// + public (Vector2 minimum, Vector2 maximum)? labelBounds { get; } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs new file mode 100644 index 000000000..60371d316 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.Nodes.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.ImGui; +using CanvasDrawing = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Graph.ImGui; + +/// +/// Draws and hit-tests neutral graph nodes using the frame's frozen viewport projection. +/// +public readonly partial struct GraphCanvasView +{ + /// + /// Projects neutral node geometry, optionally substituting a transient drag position. + /// + /// + /// The immutable display node. + /// + /// + /// An uncommitted gesture position, or null to use the document position. + /// + /// + /// The screen-space node bounds. + /// + public (Vector2 minimum, Vector2 maximum) NodeBounds( + GraphCanvasNode node, + GraphPosition? previewPosition = null + ) { + ArgumentNullException.ThrowIfNull(node); + Vector2 minimum = ToScreen(previewPosition ?? node.position); + return (minimum, minimum + node.size * zoom); + } + + /// + /// Finds the frontmost node beneath a pointer using the same geometry as drawing. + /// + /// + /// The current document presentation snapshot. + /// + /// + /// The screen-space pointer. + /// + /// + /// The frontmost neutral node, or null over empty canvas. + /// + public GraphCanvasNode? HitNode( + GraphCanvasLayout layout, + Vector2 point + ) { + ArgumentNullException.ThrowIfNull(layout); + for (int index = layout.nodes.Count - 1; index >= 0; index--) + { + GraphCanvasNode node = layout.nodes[index]; + var bounds = NodeBounds(node); + if (Contains(bounds, point)) + return node; + } + return null; + } + + /// + /// Finds a port by its displayed socket or label row, without interpreting its domain type. + /// + /// + /// The current document presentation snapshot. + /// + /// + /// The screen-space pointer. + /// + /// + /// The nonnegative minimum socket hit radius in screen pixels. + /// + /// + /// The hit neutral port, including Missing state, or null over non-port content. + /// + /// + /// The radius is negative or nonfinite. + /// + public GraphCanvasPort? HitPort( + GraphCanvasLayout layout, + Vector2 point, + float radius = 8f + ) { + ArgumentNullException.ThrowIfNull(layout); + if (!float.IsFinite(radius) || radius < 0f) + throw new ArgumentOutOfRangeException(nameof(radius)); + float hitRadius = MathF.Max(radius, 7f * zoom); + for (int index = layout.nodes.Count - 1; index >= 0; index--) + { + GraphCanvasNode node = layout.nodes[index]; + Vector2 minimum = ToScreen(node.position); + foreach (GraphCanvasPort port in node.ports) + if (Vector2.DistanceSquared(point, minimum + port.position * m_zoom) <= hitRadius * hitRadius) + return port; + if (!Contains(NodeBounds(node), point)) + continue; + foreach (GraphCanvasPort port in node.ports) + if (port.labelBounds is { } label && Contains( + (ToScreen(node.position) + label.minimum * m_zoom, ToScreen(node.position) + label.maximum * m_zoom), point)) + return port; + // Covered nodes must not receive interaction through the frontmost node's body. + return null; + } + return null; + } + + /// + /// Finds nodes intersecting a screen-space selection rectangle in document draw order. + /// + /// + /// The current neutral display snapshot. + /// + /// + /// One corner of the selection box. + /// + /// + /// The opposite corner, in any drag direction. + /// + /// + /// Stable graph node IDs; no live domain objects are returned. + /// + public IReadOnlyList BoxNodes( + GraphCanvasLayout layout, + Vector2 first, + Vector2 second + ) { + ArgumentNullException.ThrowIfNull(layout); + Vector2 minimum = Vector2.Min(first, second); + Vector2 maximum = Vector2.Max(first, second); + var nodes = new List(); + foreach (GraphCanvasNode node in layout.nodes) + { + var bounds = NodeBounds(node); + if (bounds.maximum.X >= minimum.X && bounds.minimum.X <= maximum.X + && bounds.maximum.Y >= minimum.Y && bounds.minimum.Y <= maximum.Y) + nodes.Add(node.id); + } + return nodes.AsReadOnly(); + } + + /// + /// Draws a visible neutral node and its ports, borrowing optional content only for this call. + /// + /// + /// The current window's frame-local drawing borrow; it is neither retained nor released by this call. + /// + /// + /// The immutable geometric and textual node input. + /// + /// + /// The domain-selected packed header color; the presenter does not classify node types. + /// + /// + /// Whether to display the current selection outline. + /// + /// + /// Optional frame-local custom drawing. Exceptions propagate after restoring the node clip. + /// + /// + /// An uncommitted movement position, or null to draw the document position. + /// + /// + /// True when the node intersects the canvas and effective window clip and was submitted; false when culled. + /// + /// + /// The node is null. + /// + /// + /// The canvas is uninitialized or a visible node uses an expired or mismatched drawing borrow. + /// + public bool DrawNode( + EditorDrawList draw, + GraphCanvasNode node, + uint headerColor, + bool selected, + Action? drawContent = null, + GraphPosition? previewPosition = null + ) { + var bounds = NodeBounds(node, previewPosition); + Vector2 minimum = bounds.minimum; + Vector2 maximum = bounds.maximum; + if (m_size.X == 0f || m_size.Y == 0f || maximum.X < m_origin.X || minimum.X > m_origin.X + m_size.X + || maximum.Y < m_origin.Y || minimum.Y > m_origin.Y + m_size.Y) + return false; + var clip = CanvasDrawing.GetClipBounds(); + if (maximum.X < clip.minimum.X || minimum.X > clip.maximum.X || maximum.Y < clip.minimum.Y || minimum.Y > clip.maximum.Y) + return false; + float zoom = this.zoom; + draw.PushClip(Vector2.Max(m_origin, minimum - new Vector2(6f * zoom)), + Vector2.Min(m_origin + m_size, maximum + new Vector2(6f * zoom)), true); + try + { + draw.FilledRectangle(minimum + new Vector2(3, 5), maximum + new Vector2(3, 5), Pack(0f, 0f, 0f, EditorPalette.opacityMuted), 6f * zoom, false); + draw.FilledRectangle(minimum, maximum, Pack(0.115f, 0.12f, 0.14f), 6f * zoom, false); + draw.FilledRectangle(minimum, new(maximum.X, minimum.Y + node.headerHeight * zoom), headerColor, 6f * zoom, true); + draw.Rectangle(minimum, maximum, selected ? Pack(0.65f, 0.47f, 0.88f) : Pack(0.26f, 0.27f, 0.31f), 6f * zoom, selected ? 2f : 1f); + draw.Text(minimum + new Vector2(10f, 7f) * zoom, CanvasDrawing.GetStyleColor(ImGuiCol.Text), node.title, zoom); + foreach (GraphCanvasPort port in node.ports) + { + Vector2 point = minimum + port.position * zoom; + uint color = port.isMissing ? Pack(0.95f, 0.35f, 0.3f) : Pack(0.60f, 0.48f, 0.84f); + draw.FilledCircle(point, 5f * zoom, color); + if (port.isOptional && !port.isMissing) + draw.FilledCircle(point, 2.75f * zoom, Pack(0.115f, 0.12f, 0.14f)); + if (port.labelBounds is not { } labelBounds) + continue; + Vector2 labelMinimum = minimum + labelBounds.minimum * zoom; + Vector2 labelMaximum = minimum + labelBounds.maximum * zoom; + float padding = 12f * zoom; + var label = FitLabel(port.label, labelMaximum.X - labelMinimum.X - padding * 2f, zoom); + float textX = port.direction == GraphPortDirection.Input ? labelMinimum.X + padding + : labelMaximum.X - padding - label.width; + draw.PushClip(labelMinimum, labelMaximum, true); + try + { + draw.Text(new Vector2(textX, point.Y - 8f * zoom), + CanvasDrawing.ToPackedColor(port.isOptional ? EditorPalette.textDisabled : EditorPalette.text), label.text, zoom); + } + finally + { + draw.PopClip(); + } + } + drawContent?.Invoke(); + } + finally + { + draw.PopClip(); + } + return true; + } + + private static (string text, float width) FitLabel( + string label, + float width, + float zoom + ) { + float measuredWidth = CanvasDrawing.MeasureText(label).X * zoom; + if (measuredWidth <= width) + return (label, measuredWidth); + const string ellipsis = "…"; + if (CanvasDrawing.MeasureText(ellipsis).X * zoom > width) + return (string.Empty, 0f); + int low = 0; + int high = label.Length; + while (low < high) + { + int length = (low + high + 1) / 2; + if (CanvasDrawing.MeasureText(label[..length] + ellipsis).X * zoom <= width) + low = length; + else + high = length - 1; + } + // Avoid splitting a UTF-16 surrogate pair when a label contains supplementary characters. + if (low > 0 && low < label.Length && char.IsHighSurrogate(label[low - 1])) + low--; + string text = label[..low] + ellipsis; + return (text, CanvasDrawing.MeasureText(text).X * zoom); + } + + private static bool Contains( + (Vector2 minimum, Vector2 maximum) bounds, + Vector2 point + ) => point.X >= bounds.minimum.X && point.X <= bounds.maximum.X + && point.Y >= bounds.minimum.Y && point.Y <= bounds.maximum.Y; + + private static uint Pack( + float red, + float green, + float blue, + float alpha = 1f + ) => CanvasDrawing.ToPackedColor(new Vector4(red, green, blue, alpha)); +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs new file mode 100644 index 000000000..13c531469 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Canvas/GraphCanvasView.cs @@ -0,0 +1,218 @@ +using System; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.ImGui; +using CanvasDrawing = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Graph.ImGui; +/// +/// Projects and presents an arbitrary graph viewport without owning document mutations or domain semantics. +/// +public readonly partial struct GraphCanvasView +{ + private const int C_MAX_GRID_LINES_PER_AXIS = 2048; + private readonly GraphPosition m_pan; + private readonly float m_zoom; + private readonly Vector2 m_origin; + private readonly Vector2 m_size; + /// + /// Creates a frame-local graph presenter over navigation owned by its document. + /// + /// + /// The existing neutral navigation and selection state. + /// + /// + /// The screen-space canvas origin. + /// + /// + /// The finite nonnegative canvas extent. + /// + /// + /// The document-owned navigation state is null. + /// + /// + /// The canvas geometry is invalid. + /// + public GraphCanvasView( + GraphCanvasState state, + Vector2 origin, + Vector2 size + ) { + ArgumentNullException.ThrowIfNull(state); + if (!float.IsFinite(origin.X) || !float.IsFinite(origin.Y)) + throw new ArgumentOutOfRangeException(nameof(origin)); + if (!float.IsFinite(size.X) || !float.IsFinite(size.Y) || size.X < 0f || size.Y < 0f) + throw new ArgumentOutOfRangeException(nameof(size)); + m_pan = state.pan; + m_zoom = state.zoom; + m_origin = origin; + m_size = size; + } + + /// + /// Gets the graph-to-screen zoom captured for this frame, independently of later navigation changes. + /// + /// + /// The view was not initialized through its constructor. + /// + public float zoom => m_zoom > 0f ? m_zoom : throw new InvalidOperationException("A graph canvas view requires an initialized projection."); + + /// + /// Projects a graph-space location into the current screen rectangle. + /// + /// + /// The domain-independent graph coordinate. + /// + /// + /// The corresponding screen coordinate. + /// + public Vector2 ToScreen(GraphPosition point) => m_origin + new Vector2(m_pan.x, m_pan.y) + new Vector2(point.x, point.y) * zoom; + /// + /// Converts a screen location back into graph coordinates. + /// + /// + /// The screen coordinate to project. + /// + /// + /// The graph coordinate under the pointer. + /// + public GraphPosition ToGraph(Vector2 point) => new((point.X - m_origin.X - m_pan.x) / zoom, (point.Y - m_origin.Y - m_pan.y) / zoom); + /// + /// Draws a bounded graph-space grid through the current UI drawing context. + /// + /// + /// The packed presentation color. + /// + /// + /// Positive graph-space grid spacing. + /// + /// + /// Spacing is nonpositive or nonfinite. + /// + public void DrawGrid( + uint color, + float spacing = 32f + ) { + if (!float.IsFinite(spacing) || spacing <= 0f) + throw new ArgumentOutOfRangeException(nameof(spacing)); + float step = MathF.Max(MathF.Max(4f, spacing * zoom), MathF.Max(m_size.X, m_size.Y) / C_MAX_GRID_LINES_PER_AXIS); + for (float x = ((m_pan.x % step) + step) % step; x < m_size.X; x += step) + CanvasDrawing.DrawLine(m_origin + new Vector2(x, 0f), m_origin + new Vector2(x, m_size.Y), color); + for (float y = ((m_pan.y % step) + step) % step; y < m_size.Y; y += step) + CanvasDrawing.DrawLine(m_origin + new Vector2(0f, y), m_origin + new Vector2(m_size.X, y), color); + } + + /// + /// Draws a horizontal-tangent connection using the same curve used for hit testing. + /// + /// + /// The current window's frame-local drawing borrow; it is neither retained nor released by this call. + /// + /// + /// The output endpoint in screen space. + /// + /// + /// The input endpoint in screen space. + /// + /// + /// The packed connection color. + /// + /// + /// The positive screen-space stroke width. + /// + /// + /// An endpoint or derived control point is nonfinite, or the stroke width is not positive and finite. + /// + /// + /// The canvas is uninitialized or a visible connection uses an expired or mismatched drawing borrow. + /// + public void DrawConnection( + EditorDrawList draw, + Vector2 output, + Vector2 input, + uint color, + float thickness = 2f + ) { + _ = zoom; + if (!float.IsFinite(thickness) || thickness <= 0f) + throw new ArgumentOutOfRangeException(nameof(thickness)); + if (!float.IsFinite(output.X) || !float.IsFinite(output.Y) || !float.IsFinite(input.X) || !float.IsFinite(input.Y)) + throw new ArgumentOutOfRangeException(nameof(output), "Connection endpoints must be finite screen coordinates."); + if (m_size.X == 0f || m_size.Y == 0f) + return; + float tangent = Tangent(output, input); + Vector2 first = output + new Vector2(tangent, 0f); + Vector2 second = input - new Vector2(tangent, 0f); + if (!float.IsFinite(first.X) || !float.IsFinite(second.X)) + throw new ArgumentOutOfRangeException(nameof(input), "Connection controls exceed finite screen coordinates."); + var clip = CanvasDrawing.GetClipBounds(); + Vector2 minimum = Vector2.Min(Vector2.Min(output, input), Vector2.Min(first, second)) - new Vector2(thickness); + Vector2 maximum = Vector2.Max(Vector2.Max(output, input), Vector2.Max(first, second)) + new Vector2(thickness); + if (maximum.X < MathF.Max(m_origin.X, clip.minimum.X) || minimum.X > MathF.Min(m_origin.X + m_size.X, clip.maximum.X) + || maximum.Y < MathF.Max(m_origin.Y, clip.minimum.Y) || minimum.Y > MathF.Min(m_origin.Y + m_size.Y, clip.maximum.Y)) + return; + draw.PushClip(m_origin, m_origin + m_size, true); + try + { + CanvasDrawing.DrawCubicCurve(output, first, second, input, color, thickness); + } + finally + { + draw.PopClip(); + } + } + + /// + /// Checks pointer distance against the displayed cubic connection without any UI or document access. + /// + /// + /// The screen-space pointer location. + /// + /// + /// The displayed output endpoint. + /// + /// + /// The displayed input endpoint. + /// + /// + /// Nonnegative screen-space acceptance radius. + /// + /// + /// True when the pointer is within the curve's hit area, including its endpoints. + /// + /// + /// Radius is negative or nonfinite. + /// + public static bool HitConnection( + Vector2 point, + Vector2 output, + Vector2 input, + float radius = 6f + ) { + if (!float.IsFinite(radius) || radius < 0f) + throw new ArgumentOutOfRangeException(nameof(radius)); + float tangent = Tangent(output, input); + Vector2 first = output + new Vector2(tangent, 0f); + Vector2 second = input - new Vector2(tangent, 0f); + Vector2 previous = output; + for (int segment = 1; segment <= 32; segment++) + { + float t = segment / 32f; + float s = 1f - t; + Vector2 next = s * s * s * output + 3f * s * s * t * first + 3f * s * t * t * second + t * t * t * input; + Vector2 line = next - previous; + float projection = Math.Clamp(Vector2.Dot(point - previous, line) / MathF.Max(0.001f, line.LengthSquared()), 0f, 1f); + if (Vector2.DistanceSquared(point, previous + projection * line) <= radius * radius) + return true; + previous = next; + } + + return false; + } + + private static float Tangent( + Vector2 output, + Vector2 input + ) => MathF.Max(36f, MathF.Abs(input.X - output.X) * 0.45f); +} diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Inno.Editor.Graph.ImGui.csproj b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Inno.Editor.Graph.ImGui.csproj new file mode 100644 index 000000000..af7c814c8 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Inno.Editor.Graph.ImGui.csproj @@ -0,0 +1,23 @@ + + + net10.0 + 14.0 + disable + enable + true + $(WarningsAsErrors);CS1572;CS1573;CS1591 + Editor + + + + + + + + + + + + + + diff --git a/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Properties/ScriptingApi.cs b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Properties/ScriptingApi.cs new file mode 100644 index 000000000..0fc54eb12 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Graph.ImGui/Properties/ScriptingApi.cs @@ -0,0 +1,11 @@ +using Inno.Scripting.Api; +using Inno.Editor.Graph.ImGui; + +[assembly: ScriptingApiNamespace("InnoEditor.Graph", "Inno.Editor.Graph.ImGui", ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasView), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasNode), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasPort), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasLayout), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasPointerInput), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasPointerResult), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(GraphCanvasPointerController), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs new file mode 100644 index 000000000..c7ac2ca73 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Controls.cs @@ -0,0 +1,138 @@ +using System.Numerics; +using Inno.Native.ImGui; +using RawImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Provides pointer-free controls within the active Editor UI session. +/// +public static partial class ImGui +{ + /// + /// Submits an explicitly drawn control interaction rectangle. + /// + /// + /// The visible label and optional stable ID suffix. + /// + /// + /// The explicit two-dimensional extent. + /// + /// + /// The explicit control and interaction options. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool InvisibleButton( + string label, + Vector2 size, + ImGuiButtonFlags flags = ImGuiButtonFlags.None + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.InvisibleButton(label, size, (int)flags); + } + + /// + /// Draws a menu item with explicit availability and checked state. + /// + /// + /// The visible label and optional stable ID suffix. + /// + /// + /// A display label only; execution remains with the Action router. + /// + /// + /// Whether the item is currently selected. + /// + /// + /// Whether the control can accept input. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool MenuItem( + string label, + string shortcut = "", + bool selected = false, + bool enabled = true + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.MenuItem(label, shortcut, selected, enabled); + } + + /// + /// Edits an integer within explicit inclusive bounds. + /// + /// + /// The visible label and optional stable ID suffix. + /// + /// + /// The caller-owned value updated only after an accepted edit. + /// + /// + /// The inclusive lower bound or upper-left corner. + /// + /// + /// The inclusive upper bound or lower-right corner. + /// + /// + /// The numeric display format. + /// + /// + /// The explicit control and interaction options. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool SliderInt( + string label, + ref int value, + int minimum, + int maximum, + string format = "%d", + ImGuiSliderFlags flags = ImGuiSliderFlags.None + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.SliderInt(label, ref value, minimum, maximum, format, (int)flags); + } + + /// + /// Edits an integer by pointer drag with explicit speed and optional bounds. + /// + /// + /// The visible label and optional stable ID suffix. + /// + /// + /// The caller-owned value updated only after an accepted edit. + /// + /// + /// The pointer movement multiplier. + /// + /// + /// The inclusive lower bound or upper-left corner. + /// + /// + /// The inclusive upper bound or lower-right corner. + /// + /// + /// The numeric display format. + /// + /// + /// The explicit control and interaction options. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool DragInt( + string label, + ref int value, + float speed = 1f, + int minimum = 0, + int maximum = 0, + string format = "%d", + ImGuiSliderFlags flags = ImGuiSliderFlags.None + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.DragInt(label, ref value, speed, minimum, maximum, format, (int)flags); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs new file mode 100644 index 000000000..0345dfa35 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Drawing.cs @@ -0,0 +1,70 @@ +using System.Numerics; +using Inno.Native.ImGui; +using RawImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Provides frame-bounded drawing access and copied presentation values. +/// +public static partial class ImGui +{ + /// + /// Gets the current style color including the active alpha. + /// + /// + /// The packed RGBA color or semantic style slot. + /// + /// + /// A copied value from the active presentation context. + /// + public static uint GetStyleColor(ImGuiCol color) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetColorU32((int)color); + } + + /// + /// Derives a control identity from the current window and ID stack. + /// + /// + /// The visible label and optional stable ID suffix. + /// + /// + /// A copied value from the active presentation context. + /// + public static uint GetControlId(string label) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetID(label); + } + + /// + /// Clips subsequent controls and drawing to a screen rectangle. + /// + /// + /// The inclusive lower bound or upper-left corner. + /// + /// + /// The inclusive upper bound or lower-right corner. + /// + /// + /// Whether to intersect the enclosing clip. + /// + public static void PushClipRect( + Vector2 minimum, + Vector2 maximum, + bool intersect + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushClipRect(minimum, maximum, intersect); + } + + /// + /// Restores the enclosing control clip after a matching push. + /// + public static void PopClipRect() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PopClipRect(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs new file mode 100644 index 000000000..bf8a53a6a --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Input.cs @@ -0,0 +1,278 @@ +using System.Numerics; +using Inno.Native.ImGui; +using RawImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Reads copied input values from the active Editor UI session. +/// +public static partial class ImGui +{ + /// + /// Gets the current pointer position in screen-space UI units. + /// + /// + /// A copied value from the active presentation context. + /// + public static Vector2 GetMousePos() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetMousePos(); + } + + /// + /// Checks a pointer-button press in the current frame. + /// + /// + /// The pointer button to inspect. + /// + /// + /// Whether a held key or button may produce repeats. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsMouseClicked( + ImGuiMouseButton button, + bool repeat = false + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsMouseClicked((int)button, repeat); + } + + /// + /// Checks a pointer-button release in the current frame. + /// + /// + /// The pointer button to inspect. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsMouseReleased(ImGuiMouseButton button) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsMouseReleased((int)button); + } + + /// + /// Checks the backend-classified double-click event. + /// + /// + /// The pointer button to inspect. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsMouseDoubleClicked(ImGuiMouseButton button) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsMouseDoubleClicked((int)button); + } + + /// + /// Checks whether a presentation key remains held. + /// + /// + /// The presentation key to inspect. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsKeyDown(ImGuiKey key) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsKeyDown((Inno.Native.ImGui.ImGuiKey)key); + } + + /// + /// Checks a presentation-key press with explicit repeat policy. + /// + /// + /// The presentation key to inspect. + /// + /// + /// Whether a held key or button may produce repeats. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsKeyPressed( + ImGuiKey key, + bool repeat = true + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsKeyPressed((Inno.Native.ImGui.ImGuiKey)key, repeat); + } + + /// + /// Resets the consumed pointer-drag distance. + /// + /// + /// The pointer button to inspect. + /// + public static void ResetMouseDragDelta(ImGuiMouseButton button) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.ResetMouseDragDelta((int)button); + } + + /// + /// Requests the semantic cursor for the hovered control. + /// + /// + /// The semantic pointer shape. + /// + public static void SetMouseCursor(ImGuiMouseCursor cursor) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetMouseCursor((int)cursor); + } + + /// + /// Checks pointer containment in a screen rectangle with optional window clipping. + /// + /// + /// The inclusive lower bound or upper-left corner. + /// + /// + /// The inclusive upper bound or lower-right corner. + /// + /// + /// Whether the current clipping rectangle participates. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsMouseHoveringRect( + Vector2 minimum, + Vector2 maximum, + bool clip = true + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsMouseHoveringRect(minimum, maximum, clip); + } + + /// + /// Checks window hover under the declared interaction policy. + /// + /// + /// The explicit control and interaction options. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsWindowHovered(ImGuiHoveredFlags flags = ImGuiHoveredFlags.None) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsWindowHovered((int)flags); + } + + /// + /// Checks keyboard focus within the declared window scope. + /// + /// + /// The explicit control and interaction options. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsWindowFocused(ImGuiFocusedFlags flags = ImGuiFocusedFlags.None) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsWindowFocused((int)flags); + } + + /// + /// Checks whether a submitted control currently owns hover. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsAnyItemHovered() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsAnyItemHovered(); + } + + /// + /// Checks whether a submitted control owns an active interaction. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsAnyItemActive() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsAnyItemActive(); + } + + /// + /// Checks the first frame of the last control interaction. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsItemActivated() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsItemActivated(); + } + + /// + /// Checks the completion frame of an edited control. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool IsItemDeactivatedAfterEdit() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsItemDeactivatedAfterEdit(); + } + + /// + /// Requests focus for a relative control position. + /// + /// + /// The relative focus offset from the next submitted control. + /// + public static void SetKeyboardFocusHere(int offset = 0) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetKeyboardFocusHere(offset); + } + + /// + /// Chooses the last item as the default selection in an appearing popup. + /// + public static void SetItemDefaultFocus() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetItemDefaultFocus(); + } + + /// + /// Copies literal text through the host clipboard adapter. + /// + /// + /// Literal text; no formatting interpretation is applied. + /// + public static void SetClipboardText(string text) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetClipboardText(text); + } + + /// + /// Gets the current native frame identity for transient diagnostics. + /// + /// + /// A copied value from the active presentation context. + /// + public static int GetFrameCount() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetFrameCount(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs new file mode 100644 index 000000000..0143391de --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Layout.cs @@ -0,0 +1,282 @@ +using System.Numerics; +using Inno.Native.ImGui; +using RawImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Provides scoped layout operations without exposing native context storage. +/// +public static partial class ImGui +{ + /// + /// Places the next control at a screen-space location. + /// + /// + /// The requested coordinate in UI units. + /// + public static void SetCursorScreenPosition(Vector2 position) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetCursorScreenPos(position); + } + + /// + /// Changes only the local horizontal layout cursor. + /// + /// + /// The requested coordinate in UI units. + /// + public static void SetCursorPosX(float position) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetCursorPosX(position); + } + + /// + /// Changes only the local vertical layout cursor. + /// + /// + /// The requested coordinate in UI units. + /// + public static void SetCursorPosY(float position) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetCursorPosY(position); + } + + /// + /// Gets the local horizontal layout cursor. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetCursorPosX() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetCursorPosX(); + } + + /// + /// Gets the local vertical layout cursor. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetCursorPosY() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetCursorPosY(); + } + + /// + /// Gets the current window screen-space origin. + /// + /// + /// A copied value from the active presentation context. + /// + public static Vector2 GetWindowPos() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetWindowPos(); + } + + /// + /// Gets the current window size in UI units. + /// + /// + /// A copied value from the active presentation context. + /// + public static Vector2 GetWindowSize() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetWindowSize(); + } + + /// + /// Gets the active font size in UI units. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetFontSize() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetFontSize(); + } + + /// + /// Gets the standard control height including frame padding. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetFrameHeight() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetFrameHeight(); + } + + /// + /// Gets the active font line height without spacing. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetTextLineHeight() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetTextLineHeight(); + } + + /// + /// Gets the standard control height and following vertical spacing. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetFrameHeightWithSpacing() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetFrameHeightWithSpacing(); + } + + /// + /// Gets the native viewport scale for the current window. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetWindowDpiScale() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetWindowDpiScale(); + } + + /// + /// Aligns the next text baseline with framed controls. + /// + public static void AlignTextToFramePadding() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.AlignTextToFramePadding(); + } + + /// + /// Allows explicitly layered controls to share the next item bounds. + /// + public static void SetNextItemAllowOverlap() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetNextItemAllowOverlap(); + } + + /// + /// Requests a size for the next window under an explicit layout condition. + /// + /// + /// The explicit two-dimensional extent. + /// + /// + /// The condition controlling when the request is applied. + /// + public static void SetNextWindowSize( + Vector2 size, + ImGuiCond condition = ImGuiCond.None + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetNextWindowSize(size, (int)condition); + } + + /// + /// Declares virtual content size for the next scrollable window. + /// + /// + /// The explicit two-dimensional extent. + /// + public static void SetNextWindowContentSize(Vector2 size) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetNextWindowContentSize(size); + } + + /// + /// Indents subsequent controls by an explicit amount or the standard step. + /// + /// + /// The requested layout distance. + /// + public static void Indent(float amount = 0f) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.Indent(amount); + } + + /// + /// Restores a matching indentation step. + /// + /// + /// The requested layout distance. + /// + public static void Unindent(float amount = 0f) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.Unindent(amount); + } + + /// + /// Restricts subsequent text wrapping to a local horizontal boundary. + /// + /// + /// The requested coordinate in UI units. + /// + public static void PushTextWrapPos(float position = 0f) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushTextWrapPos(position); + } + + /// + /// Restores the enclosing text-wrap boundary. + /// + public static void PopTextWrapPos() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PopTextWrapPos(); + } + + /// + /// Gets the current vertical scroll position. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetScrollY() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetScrollY(); + } + + /// + /// Gets the current vertical scroll range. + /// + /// + /// A copied value from the active presentation context. + /// + public static float GetScrollMaxY() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetScrollMaxY(); + } + + /// + /// Scrolls the most recently submitted control into the requested relative position. + /// + /// + /// The relative alignment from zero through one. + /// + public static void SetScrollHereY(float ratio = 0.5f) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetScrollHereY(ratio); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Tables.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Tables.cs new file mode 100644 index 000000000..8ecf5a21e --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/ImGui.Tables.cs @@ -0,0 +1,46 @@ +using System.Numerics; +using Inno.Native.ImGui; +using RawImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Presents tables through the active Editor UI context. +/// +public static partial class ImGui +{ + /// + /// Selects a declared table column for subsequent controls. + /// + /// + /// The zero-based item index. + /// + /// + /// True when the documented control event or condition is present; otherwise false. + /// + public static bool TableSetColumnIndex(int index) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.TableSetColumnIndex(index); + } + + /// + /// Sets a table row or cell background through the current table scope. + /// + /// + /// The table background role. + /// + /// + /// The packed RGBA color or semantic style slot. + /// + /// + /// The target column, or -1 for the current row. + /// + public static void TableSetBgColor( + ImGuiTableBgTarget target, + uint color, + int column = -1 + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TableSetBgColor((int)target, color, column); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs new file mode 100644 index 000000000..791a9be6f --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiButtonFlags.cs @@ -0,0 +1,34 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported button flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiButtonFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiButtonFlags.None, + /// + /// React on left mouse button (default) + /// + MouseButtonLeft = (int)Inno.Native.ImGui.ImGuiButtonFlags.MouseButtonLeft, + /// + /// React on right mouse button + /// + MouseButtonRight = (int)Inno.Native.ImGui.ImGuiButtonFlags.MouseButtonRight, + /// + /// React on center mouse button + /// + MouseButtonMiddle = (int)Inno.Native.ImGui.ImGuiButtonFlags.MouseButtonMiddle, + /// + /// Selects the mouse button mask presentation behavior. + /// + MouseButtonMask = (int)Inno.Native.ImGui.ImGuiButtonFlags.MouseButtonMask, + /// + /// InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. + /// + EnableNav = (int)Inno.Native.ImGui.ImGuiButtonFlags.EnableNav, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs new file mode 100644 index 000000000..47cb3cc84 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiChildFlags.cs @@ -0,0 +1,50 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported child flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiChildFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiChildFlags.None, + /// + /// Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason) + /// + Borders = (int)Inno.Native.ImGui.ImGuiChildFlags.Borders, + /// + /// Pad with style.WindowPadding even if no border are drawn (no padding by default for non-bordered child windows because it makes more sense) + /// + AlwaysUseWindowPadding = (int)Inno.Native.ImGui.ImGuiChildFlags.AlwaysUseWindowPadding, + /// + /// Allow resize from right border (layout direction). Enable .ini saving (unless ImGuiWindowFlags_NoSavedSettings passed to window flags) + /// + ResizeX = (int)Inno.Native.ImGui.ImGuiChildFlags.ResizeX, + /// + /// Allow resize from bottom border (layout direction). " + /// + ResizeY = (int)Inno.Native.ImGui.ImGuiChildFlags.ResizeY, + /// + /// Enable auto-resizing width. Read "IMPORTANT: Size measurement" details above. + /// + AutoResizeX = (int)Inno.Native.ImGui.ImGuiChildFlags.AutoResizeX, + /// + /// Enable auto-resizing height. Read "IMPORTANT: Size measurement" details above. + /// + AutoResizeY = (int)Inno.Native.ImGui.ImGuiChildFlags.AutoResizeY, + /// + /// Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. + /// + AlwaysAutoResize = (int)Inno.Native.ImGui.ImGuiChildFlags.AlwaysAutoResize, + /// + /// Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. + /// + FrameStyle = (int)Inno.Native.ImGui.ImGuiChildFlags.FrameStyle, + /// + /// Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. + /// + NavFlattened = (int)Inno.Native.ImGui.ImGuiChildFlags.NavFlattened, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs new file mode 100644 index 000000000..eeee906c8 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCol.cs @@ -0,0 +1,261 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported col choices at the Editor presentation boundary. +/// +public enum ImGuiCol : int +{ + /// + /// Selects the text presentation behavior. + /// + Text = (int)Inno.Native.ImGui.ImGuiCol.Text, + /// + /// Selects the text disabled presentation behavior. + /// + TextDisabled = (int)Inno.Native.ImGui.ImGuiCol.TextDisabled, + /// + /// Selects the window bg presentation behavior. + /// + WindowBg = (int)Inno.Native.ImGui.ImGuiCol.WindowBg, + /// + /// Selects the child bg presentation behavior. + /// + ChildBg = (int)Inno.Native.ImGui.ImGuiCol.ChildBg, + /// + /// Selects the popup bg presentation behavior. + /// + PopupBg = (int)Inno.Native.ImGui.ImGuiCol.PopupBg, + /// + /// Selects the border presentation behavior. + /// + Border = (int)Inno.Native.ImGui.ImGuiCol.Border, + /// + /// Selects the border shadow presentation behavior. + /// + BorderShadow = (int)Inno.Native.ImGui.ImGuiCol.BorderShadow, + /// + /// Selects the frame bg presentation behavior. + /// + FrameBg = (int)Inno.Native.ImGui.ImGuiCol.FrameBg, + /// + /// Selects the frame bg hovered presentation behavior. + /// + FrameBgHovered = (int)Inno.Native.ImGui.ImGuiCol.FrameBgHovered, + /// + /// Selects the frame bg active presentation behavior. + /// + FrameBgActive = (int)Inno.Native.ImGui.ImGuiCol.FrameBgActive, + /// + /// Selects the title bg presentation behavior. + /// + TitleBg = (int)Inno.Native.ImGui.ImGuiCol.TitleBg, + /// + /// Selects the title bg active presentation behavior. + /// + TitleBgActive = (int)Inno.Native.ImGui.ImGuiCol.TitleBgActive, + /// + /// Selects the title bg collapsed presentation behavior. + /// + TitleBgCollapsed = (int)Inno.Native.ImGui.ImGuiCol.TitleBgCollapsed, + /// + /// Selects the menu bar bg presentation behavior. + /// + MenuBarBg = (int)Inno.Native.ImGui.ImGuiCol.MenuBarBg, + /// + /// Selects the scrollbar bg presentation behavior. + /// + ScrollbarBg = (int)Inno.Native.ImGui.ImGuiCol.ScrollbarBg, + /// + /// Selects the scrollbar grab presentation behavior. + /// + ScrollbarGrab = (int)Inno.Native.ImGui.ImGuiCol.ScrollbarGrab, + /// + /// Selects the scrollbar grab hovered presentation behavior. + /// + ScrollbarGrabHovered = (int)Inno.Native.ImGui.ImGuiCol.ScrollbarGrabHovered, + /// + /// Selects the scrollbar grab active presentation behavior. + /// + ScrollbarGrabActive = (int)Inno.Native.ImGui.ImGuiCol.ScrollbarGrabActive, + /// + /// Selects the check mark presentation behavior. + /// + CheckMark = (int)Inno.Native.ImGui.ImGuiCol.CheckMark, + /// + /// Selects the slider grab presentation behavior. + /// + SliderGrab = (int)Inno.Native.ImGui.ImGuiCol.SliderGrab, + /// + /// Selects the slider grab active presentation behavior. + /// + SliderGrabActive = (int)Inno.Native.ImGui.ImGuiCol.SliderGrabActive, + /// + /// Selects the button presentation behavior. + /// + Button = (int)Inno.Native.ImGui.ImGuiCol.Button, + /// + /// Selects the button hovered presentation behavior. + /// + ButtonHovered = (int)Inno.Native.ImGui.ImGuiCol.ButtonHovered, + /// + /// Selects the button active presentation behavior. + /// + ButtonActive = (int)Inno.Native.ImGui.ImGuiCol.ButtonActive, + /// + /// Selects the header presentation behavior. + /// + Header = (int)Inno.Native.ImGui.ImGuiCol.Header, + /// + /// Selects the header hovered presentation behavior. + /// + HeaderHovered = (int)Inno.Native.ImGui.ImGuiCol.HeaderHovered, + /// + /// Selects the header active presentation behavior. + /// + HeaderActive = (int)Inno.Native.ImGui.ImGuiCol.HeaderActive, + /// + /// Selects the separator presentation behavior. + /// + Separator = (int)Inno.Native.ImGui.ImGuiCol.Separator, + /// + /// Selects the separator hovered presentation behavior. + /// + SeparatorHovered = (int)Inno.Native.ImGui.ImGuiCol.SeparatorHovered, + /// + /// Selects the separator active presentation behavior. + /// + SeparatorActive = (int)Inno.Native.ImGui.ImGuiCol.SeparatorActive, + /// + /// Selects the resize grip presentation behavior. + /// + ResizeGrip = (int)Inno.Native.ImGui.ImGuiCol.ResizeGrip, + /// + /// Selects the resize grip hovered presentation behavior. + /// + ResizeGripHovered = (int)Inno.Native.ImGui.ImGuiCol.ResizeGripHovered, + /// + /// Selects the resize grip active presentation behavior. + /// + ResizeGripActive = (int)Inno.Native.ImGui.ImGuiCol.ResizeGripActive, + /// + /// Selects the input text cursor presentation behavior. + /// + InputTextCursor = (int)Inno.Native.ImGui.ImGuiCol.InputTextCursor, + /// + /// Selects the tab hovered presentation behavior. + /// + TabHovered = (int)Inno.Native.ImGui.ImGuiCol.TabHovered, + /// + /// Selects the tab presentation behavior. + /// + Tab = (int)Inno.Native.ImGui.ImGuiCol.Tab, + /// + /// Selects the tab selected presentation behavior. + /// + TabSelected = (int)Inno.Native.ImGui.ImGuiCol.TabSelected, + /// + /// Selects the tab selected overline presentation behavior. + /// + TabSelectedOverline = (int)Inno.Native.ImGui.ImGuiCol.TabSelectedOverline, + /// + /// Selects the tab dimmed presentation behavior. + /// + TabDimmed = (int)Inno.Native.ImGui.ImGuiCol.TabDimmed, + /// + /// Selects the tab dimmed selected presentation behavior. + /// + TabDimmedSelected = (int)Inno.Native.ImGui.ImGuiCol.TabDimmedSelected, + /// + /// Selects the tab dimmed selected overline presentation behavior. + /// + TabDimmedSelectedOverline = (int)Inno.Native.ImGui.ImGuiCol.TabDimmedSelectedOverline, + /// + /// Selects the docking preview presentation behavior. + /// + DockingPreview = (int)Inno.Native.ImGui.ImGuiCol.DockingPreview, + /// + /// Selects the docking empty bg presentation behavior. + /// + DockingEmptyBg = (int)Inno.Native.ImGui.ImGuiCol.DockingEmptyBg, + /// + /// Selects the plot lines presentation behavior. + /// + PlotLines = (int)Inno.Native.ImGui.ImGuiCol.PlotLines, + /// + /// Selects the plot lines hovered presentation behavior. + /// + PlotLinesHovered = (int)Inno.Native.ImGui.ImGuiCol.PlotLinesHovered, + /// + /// Selects the plot histogram presentation behavior. + /// + PlotHistogram = (int)Inno.Native.ImGui.ImGuiCol.PlotHistogram, + /// + /// Selects the plot histogram hovered presentation behavior. + /// + PlotHistogramHovered = (int)Inno.Native.ImGui.ImGuiCol.PlotHistogramHovered, + /// + /// Selects the table header bg presentation behavior. + /// + TableHeaderBg = (int)Inno.Native.ImGui.ImGuiCol.TableHeaderBg, + /// + /// Selects the table border strong presentation behavior. + /// + TableBorderStrong = (int)Inno.Native.ImGui.ImGuiCol.TableBorderStrong, + /// + /// Selects the table border light presentation behavior. + /// + TableBorderLight = (int)Inno.Native.ImGui.ImGuiCol.TableBorderLight, + /// + /// Selects the table row bg presentation behavior. + /// + TableRowBg = (int)Inno.Native.ImGui.ImGuiCol.TableRowBg, + /// + /// Selects the table row bg alt presentation behavior. + /// + TableRowBgAlt = (int)Inno.Native.ImGui.ImGuiCol.TableRowBgAlt, + /// + /// Selects the text link presentation behavior. + /// + TextLink = (int)Inno.Native.ImGui.ImGuiCol.TextLink, + /// + /// Selects the text selected bg presentation behavior. + /// + TextSelectedBg = (int)Inno.Native.ImGui.ImGuiCol.TextSelectedBg, + /// + /// Selects the tree lines presentation behavior. + /// + TreeLines = (int)Inno.Native.ImGui.ImGuiCol.TreeLines, + /// + /// Selects the drag drop target presentation behavior. + /// + DragDropTarget = (int)Inno.Native.ImGui.ImGuiCol.DragDropTarget, + /// + /// Selects the drag drop target bg presentation behavior. + /// + DragDropTargetBg = (int)Inno.Native.ImGui.ImGuiCol.DragDropTargetBg, + /// + /// Selects the unsaved marker presentation behavior. + /// + UnsavedMarker = (int)Inno.Native.ImGui.ImGuiCol.UnsavedMarker, + /// + /// Selects the nav cursor presentation behavior. + /// + NavCursor = (int)Inno.Native.ImGui.ImGuiCol.NavCursor, + /// + /// Selects the nav windowing highlight presentation behavior. + /// + NavWindowingHighlight = (int)Inno.Native.ImGui.ImGuiCol.NavWindowingHighlight, + /// + /// Selects the nav windowing dim bg presentation behavior. + /// + NavWindowingDimBg = (int)Inno.Native.ImGui.ImGuiCol.NavWindowingDimBg, + /// + /// Selects the modal window dim bg presentation behavior. + /// + ModalWindowDimBg = (int)Inno.Native.ImGui.ImGuiCol.ModalWindowDimBg, + /// + /// Marks the exclusive end of the valid option range. + /// + Count = (int)Inno.Native.ImGui.ImGuiCol.Count, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs new file mode 100644 index 000000000..6db099ef5 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiColorEditFlags.cs @@ -0,0 +1,138 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported color edit flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiColorEditFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiColorEditFlags.None, + /// + /// // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (will only read 3 components from the input pointer). + /// + NoAlpha = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoAlpha, + /// + /// // ColorEdit: disable picker when clicking on color square. + /// + NoPicker = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoPicker, + /// + /// // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + /// + NoOptions = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoOptions, + /// + /// // ColorEdit, ColorPicker: disable color square preview next to the inputs. (e.g. to show only the inputs) + /// + NoSmallPreview = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoSmallPreview, + /// + /// // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview color square). + /// + NoInputs = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoInputs, + /// + /// // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + /// + NoTooltip = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoTooltip, + /// + /// // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + /// + NoLabel = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoLabel, + /// + /// // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. + /// + NoSidePreview = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoSidePreview, + /// + /// // ColorEdit: disable drag and drop target/source. ColorButton: disable drag and drop source. + /// + NoDragDrop = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoDragDrop, + /// + /// // ColorButton: disable border (which is enforced by default) + /// + NoBorder = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoBorder, + /// + /// // ColorEdit: disable rendering R/G/B/A color marker. May also be disabled globally by setting style.ColorMarkerSize = 0. + /// + NoColorMarkers = (int)Inno.Native.ImGui.ImGuiColorEditFlags.NoColorMarkers, + /// + /// // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. + /// + AlphaOpaque = (int)Inno.Native.ImGui.ImGuiColorEditFlags.AlphaOpaque, + /// + /// // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. + /// + AlphaNoBg = (int)Inno.Native.ImGui.ImGuiColorEditFlags.AlphaNoBg, + /// + /// // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. + /// + AlphaPreviewHalf = (int)Inno.Native.ImGui.ImGuiColorEditFlags.AlphaPreviewHalf, + /// + /// // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + /// + AlphaBar = (int)Inno.Native.ImGui.ImGuiColorEditFlags.AlphaBar, + /// + /// Selects the hdr presentation behavior. + /// + Hdr = (int)Inno.Native.ImGui.ImGuiColorEditFlags.Hdr, + /// + /// Selects the display rgb presentation behavior. + /// + DisplayRgb = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DisplayRgb, + /// + /// Selects the display hsv presentation behavior. + /// + DisplayHsv = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DisplayHsv, + /// + /// // " + /// + DisplayHex = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DisplayHex, + /// + /// // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + /// + Uint8 = (int)Inno.Native.ImGui.ImGuiColorEditFlags.Uint8, + /// + /// // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + /// + Float = (int)Inno.Native.ImGui.ImGuiColorEditFlags.Float, + /// + /// // ColorPicker: bar for Hue, rectangle for Sat/Value. + /// + PickerHueBar = (int)Inno.Native.ImGui.ImGuiColorEditFlags.PickerHueBar, + /// + /// // ColorPicker: wheel for Hue, triangle for Sat/Value. + /// + PickerHueWheel = (int)Inno.Native.ImGui.ImGuiColorEditFlags.PickerHueWheel, + /// + /// Selects the input rgb presentation behavior. + /// + InputRgb = (int)Inno.Native.ImGui.ImGuiColorEditFlags.InputRgb, + /// + /// Selects the input hsv presentation behavior. + /// + InputHsv = (int)Inno.Native.ImGui.ImGuiColorEditFlags.InputHsv, + /// + /// Selects the default options presentation behavior. + /// + DefaultOptions = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DefaultOptions, + /// + /// Selects the alpha mask presentation behavior. + /// + AlphaMask = (int)Inno.Native.ImGui.ImGuiColorEditFlags.AlphaMask, + /// + /// Selects the display mask presentation behavior. + /// + DisplayMask = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DisplayMask, + /// + /// Selects the data type mask presentation behavior. + /// + DataTypeMask = (int)Inno.Native.ImGui.ImGuiColorEditFlags.DataTypeMask, + /// + /// Selects the picker mask presentation behavior. + /// + PickerMask = (int)Inno.Native.ImGui.ImGuiColorEditFlags.PickerMask, + /// + /// Selects the input mask presentation behavior. + /// + InputMask = (int)Inno.Native.ImGui.ImGuiColorEditFlags.InputMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs new file mode 100644 index 000000000..535401069 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiComboFlags.cs @@ -0,0 +1,50 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported combo flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiComboFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiComboFlags.None, + /// + /// Align the popup toward the left by default + /// + PopupAlignLeft = (int)Inno.Native.ImGui.ImGuiComboFlags.PopupAlignLeft, + /// + /// Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + /// + HeightSmall = (int)Inno.Native.ImGui.ImGuiComboFlags.HeightSmall, + /// + /// Max ~8 items visible (default) + /// + HeightRegular = (int)Inno.Native.ImGui.ImGuiComboFlags.HeightRegular, + /// + /// Max ~20 items visible + /// + HeightLarge = (int)Inno.Native.ImGui.ImGuiComboFlags.HeightLarge, + /// + /// As many fitting items as possible + /// + HeightLargest = (int)Inno.Native.ImGui.ImGuiComboFlags.HeightLargest, + /// + /// Display on the preview box without the square arrow button + /// + NoArrowButton = (int)Inno.Native.ImGui.ImGuiComboFlags.NoArrowButton, + /// + /// Display only a square arrow button + /// + NoPreview = (int)Inno.Native.ImGui.ImGuiComboFlags.NoPreview, + /// + /// Width dynamically calculated from preview contents + /// + WidthFitPreview = (int)Inno.Native.ImGui.ImGuiComboFlags.WidthFitPreview, + /// + /// Selects the height mask presentation behavior. + /// + HeightMask = (int)Inno.Native.ImGui.ImGuiComboFlags.HeightMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs new file mode 100644 index 000000000..67cbbf90e --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiCond.cs @@ -0,0 +1,30 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported cond choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiCond : int +{ + /// + /// No condition (always set the variable), same as _Always + /// + None = (int)Inno.Native.ImGui.ImGuiCond.None, + /// + /// No condition (always set the variable), same as _None + /// + Always = (int)Inno.Native.ImGui.ImGuiCond.Always, + /// + /// Set the variable once per runtime session (only the first call will succeed) + /// + Once = (int)Inno.Native.ImGui.ImGuiCond.Once, + /// + /// Set the variable if the object/window has no persistently saved data (no entry in .ini file) + /// + FirstUseEver = (int)Inno.Native.ImGui.ImGuiCond.FirstUseEver, + /// + /// Set the variable if the object/window is appearing after being hidden/inactive (or the first time) + /// + Appearing = (int)Inno.Native.ImGui.ImGuiCond.Appearing, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs new file mode 100644 index 000000000..04bcc642c --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiFocusedFlags.cs @@ -0,0 +1,38 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported focused flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiFocusedFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiFocusedFlags.None, + /// + /// Return true if any children of the window is focused + /// + ChildWindows = (int)Inno.Native.ImGui.ImGuiFocusedFlags.ChildWindows, + /// + /// Test from root window (top most parent of the current hierarchy) + /// + RootWindow = (int)Inno.Native.ImGui.ImGuiFocusedFlags.RootWindow, + /// + /// Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ! + /// + AnyWindow = (int)Inno.Native.ImGui.ImGuiFocusedFlags.AnyWindow, + /// + /// Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) + /// + NoPopupHierarchy = (int)Inno.Native.ImGui.ImGuiFocusedFlags.NoPopupHierarchy, + /// + /// Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) + /// + DockHierarchy = (int)Inno.Native.ImGui.ImGuiFocusedFlags.DockHierarchy, + /// + /// Selects the root and child windows presentation behavior. + /// + RootAndChildWindows = (int)Inno.Native.ImGui.ImGuiFocusedFlags.RootAndChildWindows, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs new file mode 100644 index 000000000..0a8025156 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiHoveredFlags.cs @@ -0,0 +1,94 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported hovered flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiHoveredFlags : int +{ + /// + /// Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + /// + None = (int)Inno.Native.ImGui.ImGuiHoveredFlags.None, + /// + /// IsWindowHovered() only: Return true if any children of the window is hovered + /// + ChildWindows = (int)Inno.Native.ImGui.ImGuiHoveredFlags.ChildWindows, + /// + /// IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + /// + RootWindow = (int)Inno.Native.ImGui.ImGuiHoveredFlags.RootWindow, + /// + /// IsWindowHovered() only: Return true if any window is hovered + /// + AnyWindow = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AnyWindow, + /// + /// IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) + /// + NoPopupHierarchy = (int)Inno.Native.ImGui.ImGuiHoveredFlags.NoPopupHierarchy, + /// + /// IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) + /// + DockHierarchy = (int)Inno.Native.ImGui.ImGuiHoveredFlags.DockHierarchy, + /// + /// Return true even if a popup window is normally blocking access to this item/window + /// + AllowWhenBlockedByPopup = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByPopup, + /// + /// Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + /// + AllowWhenBlockedByActiveItem = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, + /// + /// IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item. + /// + AllowWhenOverlappedByItem = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByItem, + /// + /// IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window. + /// + AllowWhenOverlappedByWindow = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenOverlappedByWindow, + /// + /// IsItemHovered() only: Return true even if the item is disabled + /// + AllowWhenDisabled = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenDisabled, + /// + /// IsItemHovered() only: Disable using keyboard/gamepad navigation state when active, always query mouse + /// + NoNavOverride = (int)Inno.Native.ImGui.ImGuiHoveredFlags.NoNavOverride, + /// + /// Selects the allow when overlapped presentation behavior. + /// + AllowWhenOverlapped = (int)Inno.Native.ImGui.ImGuiHoveredFlags.AllowWhenOverlapped, + /// + /// Selects the rect only presentation behavior. + /// + RectOnly = (int)Inno.Native.ImGui.ImGuiHoveredFlags.RectOnly, + /// + /// Selects the root and child windows presentation behavior. + /// + RootAndChildWindows = (int)Inno.Native.ImGui.ImGuiHoveredFlags.RootAndChildWindows, + /// + /// Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence. + /// + ForTooltip = (int)Inno.Native.ImGui.ImGuiHoveredFlags.ForTooltip, + /// + /// Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can move on same item/window. Using the stationary test tends to reduces the need for a long delay. + /// + Stationary = (int)Inno.Native.ImGui.ImGuiHoveredFlags.Stationary, + /// + /// IsItemHovered() only: Return true immediately (default). As this is the default you generally ignore this. + /// + DelayNone = (int)Inno.Native.ImGui.ImGuiHoveredFlags.DelayNone, + /// + /// IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). + /// + DelayShort = (int)Inno.Native.ImGui.ImGuiHoveredFlags.DelayShort, + /// + /// IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). + /// + DelayNormal = (int)Inno.Native.ImGui.ImGuiHoveredFlags.DelayNormal, + /// + /// IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) + /// + NoSharedDelay = (int)Inno.Native.ImGui.ImGuiHoveredFlags.NoSharedDelay, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs new file mode 100644 index 000000000..57a99f283 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiInputTextFlags.cs @@ -0,0 +1,114 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported input text flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiInputTextFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiInputTextFlags.None, + /// + /// Allow 0123456789.+-*/ + /// + CharsDecimal = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CharsDecimal, + /// + /// Allow 0123456789ABCDEFabcdef + /// + CharsHexadecimal = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CharsHexadecimal, + /// + /// Allow 0123456789.+-*/eE (Scientific notation input) + /// + CharsScientific = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CharsScientific, + /// + /// Turn a..z into A..Z + /// + CharsUppercase = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CharsUppercase, + /// + /// Filter out spaces, tabs + /// + CharsNoBlank = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CharsNoBlank, + /// + /// Pressing TAB input a '\t' character into the text field + /// + AllowTabInput = (int)Inno.Native.ImGui.ImGuiInputTextFlags.AllowTabInput, + /// + /// Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! + /// + EnterReturnsTrue = (int)Inno.Native.ImGui.ImGuiInputTextFlags.EnterReturnsTrue, + /// + /// Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + /// + EscapeClearsAll = (int)Inno.Native.ImGui.ImGuiInputTextFlags.EscapeClearsAll, + /// + /// In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). + /// + CtrlEnterForNewLine = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CtrlEnterForNewLine, + /// + /// Read-only mode + /// + ReadOnly = (int)Inno.Native.ImGui.ImGuiInputTextFlags.ReadOnly, + /// + /// Password mode, display all characters as '*', disable copy + /// + Password = (int)Inno.Native.ImGui.ImGuiInputTextFlags.Password, + /// + /// Overwrite mode + /// + AlwaysOverwrite = (int)Inno.Native.ImGui.ImGuiInputTextFlags.AlwaysOverwrite, + /// + /// Select entire text when first taking mouse focus + /// + AutoSelectAll = (int)Inno.Native.ImGui.ImGuiInputTextFlags.AutoSelectAll, + /// + /// InputFloat(), InputInt(), InputScalar() etc. only: parse empty string as zero value. + /// + ParseEmptyRefVal = (int)Inno.Native.ImGui.ImGuiInputTextFlags.ParseEmptyRefVal, + /// + /// InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. + /// + DisplayEmptyRefVal = (int)Inno.Native.ImGui.ImGuiInputTextFlags.DisplayEmptyRefVal, + /// + /// Disable following the cursor horizontally + /// + NoHorizontalScroll = (int)Inno.Native.ImGui.ImGuiInputTextFlags.NoHorizontalScroll, + /// + /// Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + /// + NoUndoRedo = (int)Inno.Native.ImGui.ImGuiInputTextFlags.NoUndoRedo, + /// + /// When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! + /// + ElideLeft = (int)Inno.Native.ImGui.ImGuiInputTextFlags.ElideLeft, + /// + /// Callback on pressing TAB (for completion handling) + /// + CallbackCompletion = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackCompletion, + /// + /// Callback on pressing Up/Down arrows (for history handling) + /// + CallbackHistory = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackHistory, + /// + /// Callback on each iteration. User code may query cursor position, modify text buffer. + /// + CallbackAlways = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackAlways, + /// + /// Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + /// + CallbackCharFilter = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackCharFilter, + /// + /// Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + /// + CallbackResize = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackResize, + /// + /// Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. + /// + CallbackEdit = (int)Inno.Native.ImGui.ImGuiInputTextFlags.CallbackEdit, + /// + /// InputTextMultiline(): word-wrap lines that are too long. + /// + WordWrap = (int)Inno.Native.ImGui.ImGuiInputTextFlags.WordWrap, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs new file mode 100644 index 000000000..0120f2fa9 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiKey.cs @@ -0,0 +1,669 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported key choices at the Editor presentation boundary. +/// +public enum ImGuiKey : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiKey.None, + /// + /// Identifies the NamedKeyBegin presentation key or modifier. + /// + NamedKeyBegin = (int)Inno.Native.ImGui.ImGuiKey.NamedKeyBegin, + /// + /// == ImGuiKey_NamedKey_BEGIN + /// + Tab = (int)Inno.Native.ImGui.ImGuiKey.Tab, + /// + /// Identifies the LeftArrow presentation key or modifier. + /// + LeftArrow = (int)Inno.Native.ImGui.ImGuiKey.LeftArrow, + /// + /// Identifies the RightArrow presentation key or modifier. + /// + RightArrow = (int)Inno.Native.ImGui.ImGuiKey.RightArrow, + /// + /// Identifies the UpArrow presentation key or modifier. + /// + UpArrow = (int)Inno.Native.ImGui.ImGuiKey.UpArrow, + /// + /// Identifies the DownArrow presentation key or modifier. + /// + DownArrow = (int)Inno.Native.ImGui.ImGuiKey.DownArrow, + /// + /// Identifies the PageUp presentation key or modifier. + /// + PageUp = (int)Inno.Native.ImGui.ImGuiKey.PageUp, + /// + /// Identifies the PageDown presentation key or modifier. + /// + PageDown = (int)Inno.Native.ImGui.ImGuiKey.PageDown, + /// + /// Identifies the Home presentation key or modifier. + /// + Home = (int)Inno.Native.ImGui.ImGuiKey.Home, + /// + /// Identifies the End presentation key or modifier. + /// + End = (int)Inno.Native.ImGui.ImGuiKey.End, + /// + /// Identifies the Insert presentation key or modifier. + /// + Insert = (int)Inno.Native.ImGui.ImGuiKey.Insert, + /// + /// Identifies the Delete presentation key or modifier. + /// + Delete = (int)Inno.Native.ImGui.ImGuiKey.Delete, + /// + /// Identifies the Backspace presentation key or modifier. + /// + Backspace = (int)Inno.Native.ImGui.ImGuiKey.Backspace, + /// + /// Identifies the Space presentation key or modifier. + /// + Space = (int)Inno.Native.ImGui.ImGuiKey.Space, + /// + /// Identifies the Enter presentation key or modifier. + /// + Enter = (int)Inno.Native.ImGui.ImGuiKey.Enter, + /// + /// Identifies the Escape presentation key or modifier. + /// + Escape = (int)Inno.Native.ImGui.ImGuiKey.Escape, + /// + /// Identifies the LeftCtrl presentation key or modifier. + /// + LeftCtrl = (int)Inno.Native.ImGui.ImGuiKey.LeftCtrl, + /// + /// Identifies the LeftShift presentation key or modifier. + /// + LeftShift = (int)Inno.Native.ImGui.ImGuiKey.LeftShift, + /// + /// Identifies the LeftAlt presentation key or modifier. + /// + LeftAlt = (int)Inno.Native.ImGui.ImGuiKey.LeftAlt, + /// + /// Identifies the LeftSuper presentation key or modifier. + /// + LeftSuper = (int)Inno.Native.ImGui.ImGuiKey.LeftSuper, + /// + /// Identifies the RightCtrl presentation key or modifier. + /// + RightCtrl = (int)Inno.Native.ImGui.ImGuiKey.RightCtrl, + /// + /// Identifies the RightShift presentation key or modifier. + /// + RightShift = (int)Inno.Native.ImGui.ImGuiKey.RightShift, + /// + /// Identifies the RightAlt presentation key or modifier. + /// + RightAlt = (int)Inno.Native.ImGui.ImGuiKey.RightAlt, + /// + /// Identifies the RightSuper presentation key or modifier. + /// + RightSuper = (int)Inno.Native.ImGui.ImGuiKey.RightSuper, + /// + /// Identifies the Menu presentation key or modifier. + /// + Menu = (int)Inno.Native.ImGui.ImGuiKey.Menu, + /// + /// Identifies the _0 presentation key or modifier. + /// + _0 = (int)Inno.Native.ImGui.ImGuiKey._0, + /// + /// Identifies the _1 presentation key or modifier. + /// + _1 = (int)Inno.Native.ImGui.ImGuiKey._1, + /// + /// Identifies the _2 presentation key or modifier. + /// + _2 = (int)Inno.Native.ImGui.ImGuiKey._2, + /// + /// Identifies the _3 presentation key or modifier. + /// + _3 = (int)Inno.Native.ImGui.ImGuiKey._3, + /// + /// Identifies the _4 presentation key or modifier. + /// + _4 = (int)Inno.Native.ImGui.ImGuiKey._4, + /// + /// Identifies the _5 presentation key or modifier. + /// + _5 = (int)Inno.Native.ImGui.ImGuiKey._5, + /// + /// Identifies the _6 presentation key or modifier. + /// + _6 = (int)Inno.Native.ImGui.ImGuiKey._6, + /// + /// Identifies the _7 presentation key or modifier. + /// + _7 = (int)Inno.Native.ImGui.ImGuiKey._7, + /// + /// Identifies the _8 presentation key or modifier. + /// + _8 = (int)Inno.Native.ImGui.ImGuiKey._8, + /// + /// Identifies the _9 presentation key or modifier. + /// + _9 = (int)Inno.Native.ImGui.ImGuiKey._9, + /// + /// Identifies the A presentation key or modifier. + /// + A = (int)Inno.Native.ImGui.ImGuiKey.A, + /// + /// Identifies the B presentation key or modifier. + /// + B = (int)Inno.Native.ImGui.ImGuiKey.B, + /// + /// Identifies the C presentation key or modifier. + /// + C = (int)Inno.Native.ImGui.ImGuiKey.C, + /// + /// Identifies the D presentation key or modifier. + /// + D = (int)Inno.Native.ImGui.ImGuiKey.D, + /// + /// Identifies the E presentation key or modifier. + /// + E = (int)Inno.Native.ImGui.ImGuiKey.E, + /// + /// Identifies the F presentation key or modifier. + /// + F = (int)Inno.Native.ImGui.ImGuiKey.F, + /// + /// Identifies the G presentation key or modifier. + /// + G = (int)Inno.Native.ImGui.ImGuiKey.G, + /// + /// Identifies the H presentation key or modifier. + /// + H = (int)Inno.Native.ImGui.ImGuiKey.H, + /// + /// Identifies the I presentation key or modifier. + /// + I = (int)Inno.Native.ImGui.ImGuiKey.I, + /// + /// Identifies the J presentation key or modifier. + /// + J = (int)Inno.Native.ImGui.ImGuiKey.J, + /// + /// Identifies the K presentation key or modifier. + /// + K = (int)Inno.Native.ImGui.ImGuiKey.K, + /// + /// Identifies the L presentation key or modifier. + /// + L = (int)Inno.Native.ImGui.ImGuiKey.L, + /// + /// Identifies the M presentation key or modifier. + /// + M = (int)Inno.Native.ImGui.ImGuiKey.M, + /// + /// Identifies the N presentation key or modifier. + /// + N = (int)Inno.Native.ImGui.ImGuiKey.N, + /// + /// Identifies the O presentation key or modifier. + /// + O = (int)Inno.Native.ImGui.ImGuiKey.O, + /// + /// Identifies the P presentation key or modifier. + /// + P = (int)Inno.Native.ImGui.ImGuiKey.P, + /// + /// Identifies the Q presentation key or modifier. + /// + Q = (int)Inno.Native.ImGui.ImGuiKey.Q, + /// + /// Identifies the R presentation key or modifier. + /// + R = (int)Inno.Native.ImGui.ImGuiKey.R, + /// + /// Identifies the S presentation key or modifier. + /// + S = (int)Inno.Native.ImGui.ImGuiKey.S, + /// + /// Identifies the T presentation key or modifier. + /// + T = (int)Inno.Native.ImGui.ImGuiKey.T, + /// + /// Identifies the U presentation key or modifier. + /// + U = (int)Inno.Native.ImGui.ImGuiKey.U, + /// + /// Identifies the V presentation key or modifier. + /// + V = (int)Inno.Native.ImGui.ImGuiKey.V, + /// + /// Identifies the W presentation key or modifier. + /// + W = (int)Inno.Native.ImGui.ImGuiKey.W, + /// + /// Identifies the X presentation key or modifier. + /// + X = (int)Inno.Native.ImGui.ImGuiKey.X, + /// + /// Identifies the Y presentation key or modifier. + /// + Y = (int)Inno.Native.ImGui.ImGuiKey.Y, + /// + /// Identifies the Z presentation key or modifier. + /// + Z = (int)Inno.Native.ImGui.ImGuiKey.Z, + /// + /// Identifies the F1 presentation key or modifier. + /// + F1 = (int)Inno.Native.ImGui.ImGuiKey.F1, + /// + /// Identifies the F2 presentation key or modifier. + /// + F2 = (int)Inno.Native.ImGui.ImGuiKey.F2, + /// + /// Identifies the F3 presentation key or modifier. + /// + F3 = (int)Inno.Native.ImGui.ImGuiKey.F3, + /// + /// Identifies the F4 presentation key or modifier. + /// + F4 = (int)Inno.Native.ImGui.ImGuiKey.F4, + /// + /// Identifies the F5 presentation key or modifier. + /// + F5 = (int)Inno.Native.ImGui.ImGuiKey.F5, + /// + /// Identifies the F6 presentation key or modifier. + /// + F6 = (int)Inno.Native.ImGui.ImGuiKey.F6, + /// + /// Identifies the F7 presentation key or modifier. + /// + F7 = (int)Inno.Native.ImGui.ImGuiKey.F7, + /// + /// Identifies the F8 presentation key or modifier. + /// + F8 = (int)Inno.Native.ImGui.ImGuiKey.F8, + /// + /// Identifies the F9 presentation key or modifier. + /// + F9 = (int)Inno.Native.ImGui.ImGuiKey.F9, + /// + /// Identifies the F10 presentation key or modifier. + /// + F10 = (int)Inno.Native.ImGui.ImGuiKey.F10, + /// + /// Identifies the F11 presentation key or modifier. + /// + F11 = (int)Inno.Native.ImGui.ImGuiKey.F11, + /// + /// Identifies the F12 presentation key or modifier. + /// + F12 = (int)Inno.Native.ImGui.ImGuiKey.F12, + /// + /// Identifies the F13 presentation key or modifier. + /// + F13 = (int)Inno.Native.ImGui.ImGuiKey.F13, + /// + /// Identifies the F14 presentation key or modifier. + /// + F14 = (int)Inno.Native.ImGui.ImGuiKey.F14, + /// + /// Identifies the F15 presentation key or modifier. + /// + F15 = (int)Inno.Native.ImGui.ImGuiKey.F15, + /// + /// Identifies the F16 presentation key or modifier. + /// + F16 = (int)Inno.Native.ImGui.ImGuiKey.F16, + /// + /// Identifies the F17 presentation key or modifier. + /// + F17 = (int)Inno.Native.ImGui.ImGuiKey.F17, + /// + /// Identifies the F18 presentation key or modifier. + /// + F18 = (int)Inno.Native.ImGui.ImGuiKey.F18, + /// + /// Identifies the F19 presentation key or modifier. + /// + F19 = (int)Inno.Native.ImGui.ImGuiKey.F19, + /// + /// Identifies the F20 presentation key or modifier. + /// + F20 = (int)Inno.Native.ImGui.ImGuiKey.F20, + /// + /// Identifies the F21 presentation key or modifier. + /// + F21 = (int)Inno.Native.ImGui.ImGuiKey.F21, + /// + /// Identifies the F22 presentation key or modifier. + /// + F22 = (int)Inno.Native.ImGui.ImGuiKey.F22, + /// + /// Identifies the F23 presentation key or modifier. + /// + F23 = (int)Inno.Native.ImGui.ImGuiKey.F23, + /// + /// Identifies the F24 presentation key or modifier. + /// + F24 = (int)Inno.Native.ImGui.ImGuiKey.F24, + /// + /// Identifies the Apostrophe presentation key or modifier. + /// + Apostrophe = (int)Inno.Native.ImGui.ImGuiKey.Apostrophe, + /// + /// Identifies the Comma presentation key or modifier. + /// + Comma = (int)Inno.Native.ImGui.ImGuiKey.Comma, + /// + /// Identifies the Minus presentation key or modifier. + /// + Minus = (int)Inno.Native.ImGui.ImGuiKey.Minus, + /// + /// Identifies the Period presentation key or modifier. + /// + Period = (int)Inno.Native.ImGui.ImGuiKey.Period, + /// + /// Identifies the Slash presentation key or modifier. + /// + Slash = (int)Inno.Native.ImGui.ImGuiKey.Slash, + /// + /// Identifies the Semicolon presentation key or modifier. + /// + Semicolon = (int)Inno.Native.ImGui.ImGuiKey.Semicolon, + /// + /// Identifies the Equal presentation key or modifier. + /// + Equal = (int)Inno.Native.ImGui.ImGuiKey.Equal, + /// + /// Identifies the LeftBracket presentation key or modifier. + /// + LeftBracket = (int)Inno.Native.ImGui.ImGuiKey.LeftBracket, + /// + /// Identifies the Backslash presentation key or modifier. + /// + Backslash = (int)Inno.Native.ImGui.ImGuiKey.Backslash, + /// + /// Identifies the RightBracket presentation key or modifier. + /// + RightBracket = (int)Inno.Native.ImGui.ImGuiKey.RightBracket, + /// + /// Identifies the GraveAccent presentation key or modifier. + /// + GraveAccent = (int)Inno.Native.ImGui.ImGuiKey.GraveAccent, + /// + /// Identifies the CapsLock presentation key or modifier. + /// + CapsLock = (int)Inno.Native.ImGui.ImGuiKey.CapsLock, + /// + /// Identifies the ScrollLock presentation key or modifier. + /// + ScrollLock = (int)Inno.Native.ImGui.ImGuiKey.ScrollLock, + /// + /// Identifies the NumLock presentation key or modifier. + /// + NumLock = (int)Inno.Native.ImGui.ImGuiKey.NumLock, + /// + /// Identifies the PrintScreen presentation key or modifier. + /// + PrintScreen = (int)Inno.Native.ImGui.ImGuiKey.PrintScreen, + /// + /// Identifies the Pause presentation key or modifier. + /// + Pause = (int)Inno.Native.ImGui.ImGuiKey.Pause, + /// + /// Identifies the Keypad0 presentation key or modifier. + /// + Keypad0 = (int)Inno.Native.ImGui.ImGuiKey.Keypad0, + /// + /// Identifies the Keypad1 presentation key or modifier. + /// + Keypad1 = (int)Inno.Native.ImGui.ImGuiKey.Keypad1, + /// + /// Identifies the Keypad2 presentation key or modifier. + /// + Keypad2 = (int)Inno.Native.ImGui.ImGuiKey.Keypad2, + /// + /// Identifies the Keypad3 presentation key or modifier. + /// + Keypad3 = (int)Inno.Native.ImGui.ImGuiKey.Keypad3, + /// + /// Identifies the Keypad4 presentation key or modifier. + /// + Keypad4 = (int)Inno.Native.ImGui.ImGuiKey.Keypad4, + /// + /// Identifies the Keypad5 presentation key or modifier. + /// + Keypad5 = (int)Inno.Native.ImGui.ImGuiKey.Keypad5, + /// + /// Identifies the Keypad6 presentation key or modifier. + /// + Keypad6 = (int)Inno.Native.ImGui.ImGuiKey.Keypad6, + /// + /// Identifies the Keypad7 presentation key or modifier. + /// + Keypad7 = (int)Inno.Native.ImGui.ImGuiKey.Keypad7, + /// + /// Identifies the Keypad8 presentation key or modifier. + /// + Keypad8 = (int)Inno.Native.ImGui.ImGuiKey.Keypad8, + /// + /// Identifies the Keypad9 presentation key or modifier. + /// + Keypad9 = (int)Inno.Native.ImGui.ImGuiKey.Keypad9, + /// + /// Identifies the KeypadDecimal presentation key or modifier. + /// + KeypadDecimal = (int)Inno.Native.ImGui.ImGuiKey.KeypadDecimal, + /// + /// Identifies the KeypadDivide presentation key or modifier. + /// + KeypadDivide = (int)Inno.Native.ImGui.ImGuiKey.KeypadDivide, + /// + /// Identifies the KeypadMultiply presentation key or modifier. + /// + KeypadMultiply = (int)Inno.Native.ImGui.ImGuiKey.KeypadMultiply, + /// + /// Identifies the KeypadSubtract presentation key or modifier. + /// + KeypadSubtract = (int)Inno.Native.ImGui.ImGuiKey.KeypadSubtract, + /// + /// Identifies the KeypadAdd presentation key or modifier. + /// + KeypadAdd = (int)Inno.Native.ImGui.ImGuiKey.KeypadAdd, + /// + /// Identifies the KeypadEnter presentation key or modifier. + /// + KeypadEnter = (int)Inno.Native.ImGui.ImGuiKey.KeypadEnter, + /// + /// Identifies the KeypadEqual presentation key or modifier. + /// + KeypadEqual = (int)Inno.Native.ImGui.ImGuiKey.KeypadEqual, + /// + /// Identifies the AppBack presentation key or modifier. + /// + AppBack = (int)Inno.Native.ImGui.ImGuiKey.AppBack, + /// + /// Identifies the AppForward presentation key or modifier. + /// + AppForward = (int)Inno.Native.ImGui.ImGuiKey.AppForward, + /// + /// Identifies the Oem102 presentation key or modifier. + /// + Oem102 = (int)Inno.Native.ImGui.ImGuiKey.Oem102, + /// + /// Identifies the GamepadStart presentation key or modifier. + /// + GamepadStart = (int)Inno.Native.ImGui.ImGuiKey.GamepadStart, + /// + /// Identifies the GamepadBack presentation key or modifier. + /// + GamepadBack = (int)Inno.Native.ImGui.ImGuiKey.GamepadBack, + /// + /// Identifies the GamepadFaceLeft presentation key or modifier. + /// + GamepadFaceLeft = (int)Inno.Native.ImGui.ImGuiKey.GamepadFaceLeft, + /// + /// Identifies the GamepadFaceRight presentation key or modifier. + /// + GamepadFaceRight = (int)Inno.Native.ImGui.ImGuiKey.GamepadFaceRight, + /// + /// Identifies the GamepadFaceUp presentation key or modifier. + /// + GamepadFaceUp = (int)Inno.Native.ImGui.ImGuiKey.GamepadFaceUp, + /// + /// Identifies the GamepadFaceDown presentation key or modifier. + /// + GamepadFaceDown = (int)Inno.Native.ImGui.ImGuiKey.GamepadFaceDown, + /// + /// Identifies the GamepadDpadLeft presentation key or modifier. + /// + GamepadDpadLeft = (int)Inno.Native.ImGui.ImGuiKey.GamepadDpadLeft, + /// + /// Identifies the GamepadDpadRight presentation key or modifier. + /// + GamepadDpadRight = (int)Inno.Native.ImGui.ImGuiKey.GamepadDpadRight, + /// + /// Identifies the GamepadDpadUp presentation key or modifier. + /// + GamepadDpadUp = (int)Inno.Native.ImGui.ImGuiKey.GamepadDpadUp, + /// + /// Identifies the GamepadDpadDown presentation key or modifier. + /// + GamepadDpadDown = (int)Inno.Native.ImGui.ImGuiKey.GamepadDpadDown, + /// + /// Identifies the GamepadL1 presentation key or modifier. + /// + GamepadL1 = (int)Inno.Native.ImGui.ImGuiKey.GamepadL1, + /// + /// Identifies the GamepadR1 presentation key or modifier. + /// + GamepadR1 = (int)Inno.Native.ImGui.ImGuiKey.GamepadR1, + /// + /// Identifies the GamepadL2 presentation key or modifier. + /// + GamepadL2 = (int)Inno.Native.ImGui.ImGuiKey.GamepadL2, + /// + /// Identifies the GamepadR2 presentation key or modifier. + /// + GamepadR2 = (int)Inno.Native.ImGui.ImGuiKey.GamepadR2, + /// + /// Identifies the GamepadL3 presentation key or modifier. + /// + GamepadL3 = (int)Inno.Native.ImGui.ImGuiKey.GamepadL3, + /// + /// Identifies the GamepadR3 presentation key or modifier. + /// + GamepadR3 = (int)Inno.Native.ImGui.ImGuiKey.GamepadR3, + /// + /// Identifies the GamepadLStickLeft presentation key or modifier. + /// + GamepadLStickLeft = (int)Inno.Native.ImGui.ImGuiKey.GamepadLStickLeft, + /// + /// Identifies the GamepadLStickRight presentation key or modifier. + /// + GamepadLStickRight = (int)Inno.Native.ImGui.ImGuiKey.GamepadLStickRight, + /// + /// Identifies the GamepadLStickUp presentation key or modifier. + /// + GamepadLStickUp = (int)Inno.Native.ImGui.ImGuiKey.GamepadLStickUp, + /// + /// Identifies the GamepadLStickDown presentation key or modifier. + /// + GamepadLStickDown = (int)Inno.Native.ImGui.ImGuiKey.GamepadLStickDown, + /// + /// Identifies the GamepadRStickLeft presentation key or modifier. + /// + GamepadRStickLeft = (int)Inno.Native.ImGui.ImGuiKey.GamepadRStickLeft, + /// + /// Identifies the GamepadRStickRight presentation key or modifier. + /// + GamepadRStickRight = (int)Inno.Native.ImGui.ImGuiKey.GamepadRStickRight, + /// + /// Identifies the GamepadRStickUp presentation key or modifier. + /// + GamepadRStickUp = (int)Inno.Native.ImGui.ImGuiKey.GamepadRStickUp, + /// + /// Identifies the GamepadRStickDown presentation key or modifier. + /// + GamepadRStickDown = (int)Inno.Native.ImGui.ImGuiKey.GamepadRStickDown, + /// + /// Identifies the MouseLeft presentation key or modifier. + /// + MouseLeft = (int)Inno.Native.ImGui.ImGuiKey.MouseLeft, + /// + /// Identifies the MouseRight presentation key or modifier. + /// + MouseRight = (int)Inno.Native.ImGui.ImGuiKey.MouseRight, + /// + /// Identifies the MouseMiddle presentation key or modifier. + /// + MouseMiddle = (int)Inno.Native.ImGui.ImGuiKey.MouseMiddle, + /// + /// Identifies the MouseX1 presentation key or modifier. + /// + MouseX1 = (int)Inno.Native.ImGui.ImGuiKey.MouseX1, + /// + /// Identifies the MouseX2 presentation key or modifier. + /// + MouseX2 = (int)Inno.Native.ImGui.ImGuiKey.MouseX2, + /// + /// Identifies the MouseWheelX presentation key or modifier. + /// + MouseWheelX = (int)Inno.Native.ImGui.ImGuiKey.MouseWheelX, + /// + /// Identifies the MouseWheelY presentation key or modifier. + /// + MouseWheelY = (int)Inno.Native.ImGui.ImGuiKey.MouseWheelY, + /// + /// Identifies the ReservedForModCtrl presentation key or modifier. + /// + ReservedForModCtrl = (int)Inno.Native.ImGui.ImGuiKey.ReservedForModCtrl, + /// + /// Identifies the ReservedForModShift presentation key or modifier. + /// + ReservedForModShift = (int)Inno.Native.ImGui.ImGuiKey.ReservedForModShift, + /// + /// Identifies the ReservedForModAlt presentation key or modifier. + /// + ReservedForModAlt = (int)Inno.Native.ImGui.ImGuiKey.ReservedForModAlt, + /// + /// Identifies the ReservedForModSuper presentation key or modifier. + /// + ReservedForModSuper = (int)Inno.Native.ImGui.ImGuiKey.ReservedForModSuper, + /// + /// Identifies the NamedKeyEnd presentation key or modifier. + /// + NamedKeyEnd = (int)Inno.Native.ImGui.ImGuiKey.NamedKeyEnd, + /// + /// Identifies the NamedKeyCount presentation key or modifier. + /// + NamedKeyCount = (int)Inno.Native.ImGui.ImGuiKey.NamedKeyCount, + /// + /// Identifies the ImGuiModNone presentation key or modifier. + /// + ImGuiModNone = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModNone, + /// + /// Identifies the ImGuiModCtrl presentation key or modifier. + /// + ImGuiModCtrl = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModCtrl, + /// + /// Identifies the ImGuiModShift presentation key or modifier. + /// + ImGuiModShift = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModShift, + /// + /// Identifies the ImGuiModAlt presentation key or modifier. + /// + ImGuiModAlt = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModAlt, + /// + /// Identifies the ImGuiModSuper presentation key or modifier. + /// + ImGuiModSuper = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModSuper, + /// + /// Identifies the ImGuiModMask presentation key or modifier. + /// + ImGuiModMask = (int)Inno.Native.ImGui.ImGuiKey.ImGuiModMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs new file mode 100644 index 000000000..5afa25489 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseButton.cs @@ -0,0 +1,25 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported mouse button choices at the Editor presentation boundary. +/// +public enum ImGuiMouseButton : int +{ + /// + /// Selects the left presentation behavior. + /// + Left = (int)Inno.Native.ImGui.ImGuiMouseButton.Left, + /// + /// Selects the right presentation behavior. + /// + Right = (int)Inno.Native.ImGui.ImGuiMouseButton.Right, + /// + /// Selects the middle presentation behavior. + /// + Middle = (int)Inno.Native.ImGui.ImGuiMouseButton.Middle, + /// + /// Marks the exclusive end of the valid option range. + /// + Count = (int)Inno.Native.ImGui.ImGuiMouseButton.Count, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs new file mode 100644 index 000000000..86180b833 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiMouseCursor.cs @@ -0,0 +1,61 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported mouse cursor choices at the Editor presentation boundary. +/// +public enum ImGuiMouseCursor : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiMouseCursor.None, + /// + /// Selects the arrow presentation behavior. + /// + Arrow = (int)Inno.Native.ImGui.ImGuiMouseCursor.Arrow, + /// + /// Selects the text input presentation behavior. + /// + TextInput = (int)Inno.Native.ImGui.ImGuiMouseCursor.TextInput, + /// + /// Selects the resize all presentation behavior. + /// + ResizeAll = (int)Inno.Native.ImGui.ImGuiMouseCursor.ResizeAll, + /// + /// Selects the resize ns presentation behavior. + /// + ResizeNs = (int)Inno.Native.ImGui.ImGuiMouseCursor.ResizeNs, + /// + /// Selects the resize ew presentation behavior. + /// + ResizeEw = (int)Inno.Native.ImGui.ImGuiMouseCursor.ResizeEw, + /// + /// Selects the resize nesw presentation behavior. + /// + ResizeNesw = (int)Inno.Native.ImGui.ImGuiMouseCursor.ResizeNesw, + /// + /// Selects the resize nwse presentation behavior. + /// + ResizeNwse = (int)Inno.Native.ImGui.ImGuiMouseCursor.ResizeNwse, + /// + /// Selects the hand presentation behavior. + /// + Hand = (int)Inno.Native.ImGui.ImGuiMouseCursor.Hand, + /// + /// Selects the wait presentation behavior. + /// + Wait = (int)Inno.Native.ImGui.ImGuiMouseCursor.Wait, + /// + /// Selects the progress presentation behavior. + /// + Progress = (int)Inno.Native.ImGui.ImGuiMouseCursor.Progress, + /// + /// Selects the not allowed presentation behavior. + /// + NotAllowed = (int)Inno.Native.ImGui.ImGuiMouseCursor.NotAllowed, + /// + /// Marks the exclusive end of the valid option range. + /// + Count = (int)Inno.Native.ImGui.ImGuiMouseCursor.Count, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs new file mode 100644 index 000000000..017a77d2a --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiPopupFlags.cs @@ -0,0 +1,62 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported popup flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiPopupFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiPopupFlags.None, + /// + /// For BeginPopupContext*(): open on Left Mouse release. Only one button allowed! + /// + MouseButtonLeft = (int)Inno.Native.ImGui.ImGuiPopupFlags.MouseButtonLeft, + /// + /// For BeginPopupContext*(): open on Right Mouse release. Only one button allowed! (default) + /// + MouseButtonRight = (int)Inno.Native.ImGui.ImGuiPopupFlags.MouseButtonRight, + /// + /// For BeginPopupContext*(): open on Middle Mouse release. Only one button allowed! + /// + MouseButtonMiddle = (int)Inno.Native.ImGui.ImGuiPopupFlags.MouseButtonMiddle, + /// + /// For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation) + /// + NoReopen = (int)Inno.Native.ImGui.ImGuiPopupFlags.NoReopen, + /// + /// For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack + /// + NoOpenOverExistingPopup = (int)Inno.Native.ImGui.ImGuiPopupFlags.NoOpenOverExistingPopup, + /// + /// For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space + /// + NoOpenOverItems = (int)Inno.Native.ImGui.ImGuiPopupFlags.NoOpenOverItems, + /// + /// For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. + /// + AnyPopupId = (int)Inno.Native.ImGui.ImGuiPopupFlags.AnyPopupId, + /// + /// For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) + /// + AnyPopupLevel = (int)Inno.Native.ImGui.ImGuiPopupFlags.AnyPopupLevel, + /// + /// Selects the any popup presentation behavior. + /// + AnyPopup = (int)Inno.Native.ImGui.ImGuiPopupFlags.AnyPopup, + /// + /// Selects the mouse button shift presentation behavior. + /// + MouseButtonShift = (int)Inno.Native.ImGui.ImGuiPopupFlags.MouseButtonShift, + /// + /// Selects the mouse button mask presentation behavior. + /// + MouseButtonMask = (int)Inno.Native.ImGui.ImGuiPopupFlags.MouseButtonMask, + /// + /// Selects the invalid mask presentation behavior. + /// + InvalidMask = (int)Inno.Native.ImGui.ImGuiPopupFlags.InvalidMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs new file mode 100644 index 000000000..e8ba440f5 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSelectableFlags.cs @@ -0,0 +1,42 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported selectable flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiSelectableFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiSelectableFlags.None, + /// + /// Clicking this doesn't close parent popup window (overrides ImGuiItemFlags_AutoClosePopups) + /// + NoAutoClosePopups = (int)Inno.Native.ImGui.ImGuiSelectableFlags.NoAutoClosePopups, + /// + /// Frame will span all columns of its container table (text will still fit in current column) + /// + SpanAllColumns = (int)Inno.Native.ImGui.ImGuiSelectableFlags.SpanAllColumns, + /// + /// Generate press events on double clicks too + /// + AllowDoubleClick = (int)Inno.Native.ImGui.ImGuiSelectableFlags.AllowDoubleClick, + /// + /// Cannot be selected, display grayed out text + /// + Disabled = (int)Inno.Native.ImGui.ImGuiSelectableFlags.Disabled, + /// + /// (WIP) Hit testing to allow subsequent widgets to overlap this one + /// + AllowOverlap = (int)Inno.Native.ImGui.ImGuiSelectableFlags.AllowOverlap, + /// + /// Make the item be displayed as if it is hovered + /// + Highlight = (int)Inno.Native.ImGui.ImGuiSelectableFlags.Highlight, + /// + /// Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block. + /// + SelectOnNav = (int)Inno.Native.ImGui.ImGuiSelectableFlags.SelectOnNav, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs new file mode 100644 index 000000000..6eadc7060 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiSliderFlags.cs @@ -0,0 +1,54 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported slider flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiSliderFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiSliderFlags.None, + /// + /// Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. + /// + Logarithmic = (int)Inno.Native.ImGui.ImGuiSliderFlags.Logarithmic, + /// + /// Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits). + /// + NoRoundToFormat = (int)Inno.Native.ImGui.ImGuiSliderFlags.NoRoundToFormat, + /// + /// Disable Ctrl+Click or Enter key allowing to input text directly into the widget. + /// + NoInput = (int)Inno.Native.ImGui.ImGuiSliderFlags.NoInput, + /// + /// Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now. + /// + WrapAround = (int)Inno.Native.ImGui.ImGuiSliderFlags.WrapAround, + /// + /// Clamp value to min/max bounds when input manually with Ctrl+Click. By default Ctrl+Click allows going out of bounds. + /// + ClampOnInput = (int)Inno.Native.ImGui.ImGuiSliderFlags.ClampOnInput, + /// + /// Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. + /// + ClampZeroRange = (int)Inno.Native.ImGui.ImGuiSliderFlags.ClampZeroRange, + /// + /// Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. + /// + NoSpeedTweaks = (int)Inno.Native.ImGui.ImGuiSliderFlags.NoSpeedTweaks, + /// + /// DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. + /// + ColorMarkers = (int)Inno.Native.ImGui.ImGuiSliderFlags.ColorMarkers, + /// + /// Selects the always clamp presentation behavior. + /// + AlwaysClamp = (int)Inno.Native.ImGui.ImGuiSliderFlags.AlwaysClamp, + /// + /// Selects the invalid mask presentation behavior. + /// + InvalidMask = (int)Inno.Native.ImGui.ImGuiSliderFlags.InvalidMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs new file mode 100644 index 000000000..b61d56d6b --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiStyleVar.cs @@ -0,0 +1,177 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported style var choices at the Editor presentation boundary. +/// +public enum ImGuiStyleVar : int +{ + /// + /// Selects the alpha presentation behavior. + /// + Alpha = (int)Inno.Native.ImGui.ImGuiStyleVar.Alpha, + /// + /// Selects the disabled alpha presentation behavior. + /// + DisabledAlpha = (int)Inno.Native.ImGui.ImGuiStyleVar.DisabledAlpha, + /// + /// Selects the window padding presentation behavior. + /// + WindowPadding = (int)Inno.Native.ImGui.ImGuiStyleVar.WindowPadding, + /// + /// Selects the window rounding presentation behavior. + /// + WindowRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.WindowRounding, + /// + /// Selects the window border size presentation behavior. + /// + WindowBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.WindowBorderSize, + /// + /// Selects the window min size presentation behavior. + /// + WindowMinSize = (int)Inno.Native.ImGui.ImGuiStyleVar.WindowMinSize, + /// + /// Selects the window title align presentation behavior. + /// + WindowTitleAlign = (int)Inno.Native.ImGui.ImGuiStyleVar.WindowTitleAlign, + /// + /// Selects the child rounding presentation behavior. + /// + ChildRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.ChildRounding, + /// + /// Selects the child border size presentation behavior. + /// + ChildBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.ChildBorderSize, + /// + /// Selects the popup rounding presentation behavior. + /// + PopupRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.PopupRounding, + /// + /// Selects the popup border size presentation behavior. + /// + PopupBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.PopupBorderSize, + /// + /// Selects the frame padding presentation behavior. + /// + FramePadding = (int)Inno.Native.ImGui.ImGuiStyleVar.FramePadding, + /// + /// Selects the frame rounding presentation behavior. + /// + FrameRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.FrameRounding, + /// + /// Selects the frame border size presentation behavior. + /// + FrameBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.FrameBorderSize, + /// + /// Selects the item spacing presentation behavior. + /// + ItemSpacing = (int)Inno.Native.ImGui.ImGuiStyleVar.ItemSpacing, + /// + /// Selects the item inner spacing presentation behavior. + /// + ItemInnerSpacing = (int)Inno.Native.ImGui.ImGuiStyleVar.ItemInnerSpacing, + /// + /// Selects the indent spacing presentation behavior. + /// + IndentSpacing = (int)Inno.Native.ImGui.ImGuiStyleVar.IndentSpacing, + /// + /// Selects the cell padding presentation behavior. + /// + CellPadding = (int)Inno.Native.ImGui.ImGuiStyleVar.CellPadding, + /// + /// Selects the scrollbar size presentation behavior. + /// + ScrollbarSize = (int)Inno.Native.ImGui.ImGuiStyleVar.ScrollbarSize, + /// + /// Selects the scrollbar rounding presentation behavior. + /// + ScrollbarRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.ScrollbarRounding, + /// + /// Selects the scrollbar padding presentation behavior. + /// + ScrollbarPadding = (int)Inno.Native.ImGui.ImGuiStyleVar.ScrollbarPadding, + /// + /// Selects the grab min size presentation behavior. + /// + GrabMinSize = (int)Inno.Native.ImGui.ImGuiStyleVar.GrabMinSize, + /// + /// Selects the grab rounding presentation behavior. + /// + GrabRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.GrabRounding, + /// + /// Selects the image rounding presentation behavior. + /// + ImageRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.ImageRounding, + /// + /// Selects the image border size presentation behavior. + /// + ImageBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.ImageBorderSize, + /// + /// Selects the tab rounding presentation behavior. + /// + TabRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.TabRounding, + /// + /// Selects the tab border size presentation behavior. + /// + TabBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.TabBorderSize, + /// + /// Selects the tab min width base presentation behavior. + /// + TabMinWidthBase = (int)Inno.Native.ImGui.ImGuiStyleVar.TabMinWidthBase, + /// + /// Selects the tab min width shrink presentation behavior. + /// + TabMinWidthShrink = (int)Inno.Native.ImGui.ImGuiStyleVar.TabMinWidthShrink, + /// + /// Selects the tab bar border size presentation behavior. + /// + TabBarBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.TabBarBorderSize, + /// + /// Selects the tab bar overline size presentation behavior. + /// + TabBarOverlineSize = (int)Inno.Native.ImGui.ImGuiStyleVar.TabBarOverlineSize, + /// + /// Selects the table angled headers angle presentation behavior. + /// + TableAngledHeadersAngle = (int)Inno.Native.ImGui.ImGuiStyleVar.TableAngledHeadersAngle, + /// + /// Selects the table angled headers text align presentation behavior. + /// + TableAngledHeadersTextAlign = (int)Inno.Native.ImGui.ImGuiStyleVar.TableAngledHeadersTextAlign, + /// + /// Selects the tree lines size presentation behavior. + /// + TreeLinesSize = (int)Inno.Native.ImGui.ImGuiStyleVar.TreeLinesSize, + /// + /// Selects the tree lines rounding presentation behavior. + /// + TreeLinesRounding = (int)Inno.Native.ImGui.ImGuiStyleVar.TreeLinesRounding, + /// + /// Selects the button text align presentation behavior. + /// + ButtonTextAlign = (int)Inno.Native.ImGui.ImGuiStyleVar.ButtonTextAlign, + /// + /// Selects the selectable text align presentation behavior. + /// + SelectableTextAlign = (int)Inno.Native.ImGui.ImGuiStyleVar.SelectableTextAlign, + /// + /// Selects the separator text border size presentation behavior. + /// + SeparatorTextBorderSize = (int)Inno.Native.ImGui.ImGuiStyleVar.SeparatorTextBorderSize, + /// + /// Selects the separator text align presentation behavior. + /// + SeparatorTextAlign = (int)Inno.Native.ImGui.ImGuiStyleVar.SeparatorTextAlign, + /// + /// Selects the separator text padding presentation behavior. + /// + SeparatorTextPadding = (int)Inno.Native.ImGui.ImGuiStyleVar.SeparatorTextPadding, + /// + /// Selects the docking separator size presentation behavior. + /// + DockingSeparatorSize = (int)Inno.Native.ImGui.ImGuiStyleVar.DockingSeparatorSize, + /// + /// Marks the exclusive end of the valid option range. + /// + Count = (int)Inno.Native.ImGui.ImGuiStyleVar.Count, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs new file mode 100644 index 000000000..0664d4257 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabBarFlags.cs @@ -0,0 +1,62 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported tab bar flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTabBarFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTabBarFlags.None, + /// + /// Allow manually dragging tabs to re-order them + New tabs are appended at the end of list + /// + Reorderable = (int)Inno.Native.ImGui.ImGuiTabBarFlags.Reorderable, + /// + /// Automatically select new tabs when they appear + /// + AutoSelectNewTabs = (int)Inno.Native.ImGui.ImGuiTabBarFlags.AutoSelectNewTabs, + /// + /// Disable buttons to open the tab list popup + /// + TabListPopupButton = (int)Inno.Native.ImGui.ImGuiTabBarFlags.TabListPopupButton, + /// + /// Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() & & IsMouseClicked(2)) *p_open = false. + /// + NoCloseWithMiddleMouseButton = (int)Inno.Native.ImGui.ImGuiTabBarFlags.NoCloseWithMiddleMouseButton, + /// + /// Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) + /// + NoTabListScrollingButtons = (int)Inno.Native.ImGui.ImGuiTabBarFlags.NoTabListScrollingButtons, + /// + /// Disable tooltips when hovering a tab + /// + NoTooltip = (int)Inno.Native.ImGui.ImGuiTabBarFlags.NoTooltip, + /// + /// Draw selected overline markers over selected tab + /// + DrawSelectedOverline = (int)Inno.Native.ImGui.ImGuiTabBarFlags.DrawSelectedOverline, + /// + /// Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. + /// + FittingPolicyMixed = (int)Inno.Native.ImGui.ImGuiTabBarFlags.FittingPolicyMixed, + /// + /// Shrink down tabs when they don't fit + /// + FittingPolicyShrink = (int)Inno.Native.ImGui.ImGuiTabBarFlags.FittingPolicyShrink, + /// + /// Enable scrolling buttons when tabs don't fit + /// + FittingPolicyScroll = (int)Inno.Native.ImGui.ImGuiTabBarFlags.FittingPolicyScroll, + /// + /// Selects the fitting policy mask presentation behavior. + /// + FittingPolicyMask = (int)Inno.Native.ImGui.ImGuiTabBarFlags.FittingPolicyMask, + /// + /// Selects the fitting policy default presentation behavior. + /// + FittingPolicyDefault = (int)Inno.Native.ImGui.ImGuiTabBarFlags.FittingPolicyDefault, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs new file mode 100644 index 000000000..58d858b6b --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTabItemFlags.cs @@ -0,0 +1,50 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported tab item flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTabItemFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTabItemFlags.None, + /// + /// Display a dot next to the title + set ImGuiTabItemFlags_NoAssumedClosure. + /// + UnsavedDocument = (int)Inno.Native.ImGui.ImGuiTabItemFlags.UnsavedDocument, + /// + /// Trigger flag to programmatically make the tab selected when calling BeginTabItem() + /// + SetSelected = (int)Inno.Native.ImGui.ImGuiTabItemFlags.SetSelected, + /// + /// Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() & & IsMouseClicked(2)) *p_open = false. + /// + NoCloseWithMiddleMouseButton = (int)Inno.Native.ImGui.ImGuiTabItemFlags.NoCloseWithMiddleMouseButton, + /// + /// Don't call PushID()/PopID() on BeginTabItem()/EndTabItem() + /// + NoPushId = (int)Inno.Native.ImGui.ImGuiTabItemFlags.NoPushId, + /// + /// Disable tooltip for the given tab + /// + NoTooltip = (int)Inno.Native.ImGui.ImGuiTabItemFlags.NoTooltip, + /// + /// Disable reordering this tab or having another tab cross over this tab + /// + NoReorder = (int)Inno.Native.ImGui.ImGuiTabItemFlags.NoReorder, + /// + /// Enforce the tab position to the left of the tab bar (after the tab list popup button) + /// + Leading = (int)Inno.Native.ImGui.ImGuiTabItemFlags.Leading, + /// + /// Enforce the tab position to the right of the tab bar (before the scrolling buttons) + /// + Trailing = (int)Inno.Native.ImGui.ImGuiTabItemFlags.Trailing, + /// + /// Tab is selected when trying to close + closure is not immediately assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. + /// + NoAssumedClosure = (int)Inno.Native.ImGui.ImGuiTabItemFlags.NoAssumedClosure, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs new file mode 100644 index 000000000..db6dbba57 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableBgTarget.cs @@ -0,0 +1,25 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported table bg target choices at the Editor presentation boundary. +/// +public enum ImGuiTableBgTarget : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTableBgTarget.None, + /// + /// Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + /// + RowBg0 = (int)Inno.Native.ImGui.ImGuiTableBgTarget.RowBg0, + /// + /// Set row background color 1 (generally used for selection marking) + /// + RowBg1 = (int)Inno.Native.ImGui.ImGuiTableBgTarget.RowBg1, + /// + /// Set cell background color (top-most color) + /// + CellBg = (int)Inno.Native.ImGui.ImGuiTableBgTarget.CellBg, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs new file mode 100644 index 000000000..99b9b6ec3 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableColumnFlags.cs @@ -0,0 +1,122 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported table column flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTableColumnFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.None, + /// + /// Overriding/master disable flag: hide column, won't show in context menu (unlike calling TableSetColumnEnabled() which manipulates the user accessible state) + /// + Disabled = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.Disabled, + /// + /// Default as a hidden/disabled column. + /// + DefaultHide = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.DefaultHide, + /// + /// Default as a sorting column. + /// + DefaultSort = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.DefaultSort, + /// + /// Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp). + /// + WidthStretch = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.WidthStretch, + /// + /// Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable). + /// + WidthFixed = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.WidthFixed, + /// + /// Disable manual resizing. + /// + NoResize = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoResize, + /// + /// Disable manual reordering this column, this will also prevent other columns from crossing over this column. + /// + NoReorder = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoReorder, + /// + /// Disable ability to hide/disable this column. + /// + NoHide = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoHide, + /// + /// Disable clipping for this column (all NoClip columns will render in a same draw command). + /// + NoClip = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoClip, + /// + /// Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + /// + NoSort = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoSort, + /// + /// Disable ability to sort in the ascending direction. + /// + NoSortAscending = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoSortAscending, + /// + /// Disable ability to sort in the descending direction. + /// + NoSortDescending = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoSortDescending, + /// + /// TableHeadersRow() will submit an empty label for this column. Convenient for some small columns. Name will still appear in context menu or in angled headers. You may append into this cell by calling TableSetColumnIndex() right after the TableHeadersRow() call. + /// + NoHeaderLabel = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoHeaderLabel, + /// + /// Disable header text width contribution to automatic column width. + /// + NoHeaderWidth = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoHeaderWidth, + /// + /// Make the initial sort direction Ascending when first sorting on this column (default). + /// + PreferSortAscending = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.PreferSortAscending, + /// + /// Make the initial sort direction Descending when first sorting on this column. + /// + PreferSortDescending = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.PreferSortDescending, + /// + /// Use current Indent value when entering cell (default for column 0). + /// + IndentEnable = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IndentEnable, + /// + /// Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. + /// + IndentDisable = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IndentDisable, + /// + /// TableHeadersRow() will submit an angled header row for this column. Note this will add an extra row. + /// + AngledHeader = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.AngledHeader, + /// + /// Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. + /// + IsEnabled = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IsEnabled, + /// + /// Status: is visible == is enabled AND not clipped by scrolling. + /// + IsVisible = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IsVisible, + /// + /// Status: is currently part of the sort specs + /// + IsSorted = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IsSorted, + /// + /// Status: is hovered by mouse + /// + IsHovered = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IsHovered, + /// + /// Selects the width mask presentation behavior. + /// + WidthMask = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.WidthMask, + /// + /// Selects the indent mask presentation behavior. + /// + IndentMask = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.IndentMask, + /// + /// Selects the status mask presentation behavior. + /// + StatusMask = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.StatusMask, + /// + /// Selects the no direct resize presentation behavior. + /// + NoDirectResize = (int)Inno.Native.ImGui.ImGuiTableColumnFlags.NoDirectResize, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs new file mode 100644 index 000000000..e483ba5fa --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableFlags.cs @@ -0,0 +1,158 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported table flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTableFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTableFlags.None, + /// + /// Enable resizing columns. + /// + Resizable = (int)Inno.Native.ImGui.ImGuiTableFlags.Resizable, + /// + /// Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) + /// + Reorderable = (int)Inno.Native.ImGui.ImGuiTableFlags.Reorderable, + /// + /// Enable hiding/disabling columns in context menu. + /// + Hideable = (int)Inno.Native.ImGui.ImGuiTableFlags.Hideable, + /// + /// Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate. + /// + Sortable = (int)Inno.Native.ImGui.ImGuiTableFlags.Sortable, + /// + /// Disable persisting columns order, width, visibility and sort settings in the .ini file. + /// + NoSavedSettings = (int)Inno.Native.ImGui.ImGuiTableFlags.NoSavedSettings, + /// + /// Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). + /// + ContextMenuInBody = (int)Inno.Native.ImGui.ImGuiTableFlags.ContextMenuInBody, + /// + /// Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) + /// + RowBg = (int)Inno.Native.ImGui.ImGuiTableFlags.RowBg, + /// + /// Draw horizontal borders between rows. + /// + BordersInnerH = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersInnerH, + /// + /// Draw horizontal borders at the top and bottom. + /// + BordersOuterH = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersOuterH, + /// + /// Draw vertical borders between columns. + /// + BordersInnerV = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersInnerV, + /// + /// Draw vertical borders on the left and right sides. + /// + BordersOuterV = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersOuterV, + /// + /// Draw horizontal borders. + /// + BordersH = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersH, + /// + /// Draw vertical borders. + /// + BordersV = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersV, + /// + /// Draw inner borders. + /// + BordersInner = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersInner, + /// + /// Draw outer borders. + /// + BordersOuter = (int)Inno.Native.ImGui.ImGuiTableFlags.BordersOuter, + /// + /// Draw all borders. + /// + Borders = (int)Inno.Native.ImGui.ImGuiTableFlags.Borders, + /// + /// Disable vertical borders in columns Body (borders will always appear in Headers). -> May move to style + /// + NoBordersInBody = (int)Inno.Native.ImGui.ImGuiTableFlags.NoBordersInBody, + /// + /// Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers). -> May move to style + /// + NoBordersInBodyUntilResize = (int)Inno.Native.ImGui.ImGuiTableFlags.NoBordersInBodyUntilResize, + /// + /// Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width. + /// + SizingFixedFit = (int)Inno.Native.ImGui.ImGuiTableFlags.SizingFixedFit, + /// + /// Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible. + /// + SizingFixedSame = (int)Inno.Native.ImGui.ImGuiTableFlags.SizingFixedSame, + /// + /// Columns default to _WidthStretch with default weights proportional to each columns contents widths. + /// + SizingStretchProp = (int)Inno.Native.ImGui.ImGuiTableFlags.SizingStretchProp, + /// + /// Columns default to _WidthStretch with default weights all equal, unless overridden by TableSetupColumn(). + /// + SizingStretchSame = (int)Inno.Native.ImGui.ImGuiTableFlags.SizingStretchSame, + /// + /// Make outer width auto-fit to columns, overriding outer_size.x value. Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. + /// + NoHostExtendX = (int)Inno.Native.ImGui.ImGuiTableFlags.NoHostExtendX, + /// + /// Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible. + /// + NoHostExtendY = (int)Inno.Native.ImGui.ImGuiTableFlags.NoHostExtendY, + /// + /// Disable keeping column always minimally visible when ScrollX is off and table gets too small. Not recommended if columns are resizable. + /// + NoKeepColumnsVisible = (int)Inno.Native.ImGui.ImGuiTableFlags.NoKeepColumnsVisible, + /// + /// Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth. + /// + PreciseWidths = (int)Inno.Native.ImGui.ImGuiTableFlags.PreciseWidths, + /// + /// Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). + /// + NoClip = (int)Inno.Native.ImGui.ImGuiTableFlags.NoClip, + /// + /// Default if BordersOuterV is on. Enable outermost padding. Generally desirable if you have headers. + /// + PadOuterX = (int)Inno.Native.ImGui.ImGuiTableFlags.PadOuterX, + /// + /// Default if BordersOuterV is off. Disable outermost padding. + /// + NoPadOuterX = (int)Inno.Native.ImGui.ImGuiTableFlags.NoPadOuterX, + /// + /// Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off). + /// + NoPadInnerX = (int)Inno.Native.ImGui.ImGuiTableFlags.NoPadInnerX, + /// + /// Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this creates a child window, ScrollY is currently generally recommended when using ScrollX. + /// + ScrollX = (int)Inno.Native.ImGui.ImGuiTableFlags.ScrollX, + /// + /// Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + /// + ScrollY = (int)Inno.Native.ImGui.ImGuiTableFlags.ScrollY, + /// + /// Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). + /// + SortMulti = (int)Inno.Native.ImGui.ImGuiTableFlags.SortMulti, + /// + /// Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). + /// + SortTristate = (int)Inno.Native.ImGui.ImGuiTableFlags.SortTristate, + /// + /// Highlight column headers when hovered (may evolve into a fuller highlight) + /// + HighlightHoveredColumn = (int)Inno.Native.ImGui.ImGuiTableFlags.HighlightHoveredColumn, + /// + /// Selects the sizing mask presentation behavior. + /// + SizingMask = (int)Inno.Native.ImGui.ImGuiTableFlags.SizingMask, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs new file mode 100644 index 000000000..c664e0e69 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTableRowFlags.cs @@ -0,0 +1,18 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported table row flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTableRowFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTableRowFlags.None, + /// + /// Identify header row (set default background color + width of its contents accounted differently for auto column width) + /// + Headers = (int)Inno.Native.ImGui.ImGuiTableRowFlags.Headers, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs new file mode 100644 index 000000000..2179ce729 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiTreeNodeFlags.cs @@ -0,0 +1,98 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported tree node flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiTreeNodeFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.None, + /// + /// Draw as selected + /// + Selected = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.Selected, + /// + /// Draw frame with background (e.g. for CollapsingHeader) + /// + Framed = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.Framed, + /// + /// Hit testing to allow subsequent widgets to overlap this one + /// + AllowOverlap = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.AllowOverlap, + /// + /// Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + /// + NoTreePushOnOpen = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.NoTreePushOnOpen, + /// + /// Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + /// + NoAutoOpenOnLog = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.NoAutoOpenOnLog, + /// + /// Default node to be open + /// + DefaultOpen = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.DefaultOpen, + /// + /// Open on double-click instead of simple click (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. + /// + OpenOnDoubleClick = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.OpenOnDoubleClick, + /// + /// Open when clicking on the arrow part (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. + /// + OpenOnArrow = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.OpenOnArrow, + /// + /// No collapsing, no arrow (use as a convenience for leaf nodes). + /// + Leaf = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.Leaf, + /// + /// Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! + /// + Bullet = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.Bullet, + /// + /// Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. + /// + FramePadding = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.FramePadding, + /// + /// Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. + /// + SpanAvailWidth = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.SpanAvailWidth, + /// + /// Extend hit box to the left-most and right-most edges (cover the indent area). + /// + SpanFullWidth = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.SpanFullWidth, + /// + /// Narrow hit box + narrow hovering highlight, will only cover the label text. + /// + SpanLabelWidth = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.SpanLabelWidth, + /// + /// Frame will span all columns of its container table (label will still fit in current column) + /// + SpanAllColumns = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.SpanAllColumns, + /// + /// Label will span all columns of its container table + /// + LabelSpanAllColumns = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.LabelSpanAllColumns, + /// + /// Nav: left arrow moves back to parent. This is processed in TreePop() when there's an unfulfilled Left nav request remaining. + /// + NavLeftJumpsToParent = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.NavLeftJumpsToParent, + /// + /// Selects the collapsing header presentation behavior. + /// + CollapsingHeader = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.CollapsingHeader, + /// + /// No lines drawn + /// + DrawLinesNone = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.DrawLinesNone, + /// + /// Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. Faster (for large trees). + /// + DrawLinesFull = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.DrawLinesFull, + /// + /// Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. Slower (for large trees). + /// + DrawLinesToNodes = (int)Inno.Native.ImGui.ImGuiTreeNodeFlags.DrawLinesToNodes, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs new file mode 100644 index 000000000..4fc22b7c6 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Controls/Options/ImGuiWindowFlags.cs @@ -0,0 +1,130 @@ +using System; + +namespace Inno.Editor.ImGui; +/// +/// Defines the supported window flags choices at the Editor presentation boundary. +/// +[Flags] +public enum ImGuiWindowFlags : int +{ + /// + /// Uses the ordinary behavior without additional options. + /// + None = (int)Inno.Native.ImGui.ImGuiWindowFlags.None, + /// + /// Disable title-bar + /// + NoTitleBar = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoTitleBar, + /// + /// Disable user resizing with the lower-right grip + /// + NoResize = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoResize, + /// + /// Disable user moving the window + /// + NoMove = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoMove, + /// + /// Disable scrollbars (window can still scroll with mouse or programmatically) + /// + NoScrollbar = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoScrollbar, + /// + /// Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + /// + NoScrollWithMouse = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoScrollWithMouse, + /// + /// Disable user collapsing window by double-clicking on it. Also referred to as Window Menu Button (e.g. within a docking node). + /// + NoCollapse = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoCollapse, + /// + /// Resize every window to its content every frame + /// + AlwaysAutoResize = (int)Inno.Native.ImGui.ImGuiWindowFlags.AlwaysAutoResize, + /// + /// Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f). + /// + NoBackground = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoBackground, + /// + /// Never load/save settings in .ini file + /// + NoSavedSettings = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoSavedSettings, + /// + /// Disable catching mouse, hovering test with pass through. + /// + NoMouseInputs = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoMouseInputs, + /// + /// Has a menu-bar + /// + MenuBar = (int)Inno.Native.ImGui.ImGuiWindowFlags.MenuBar, + /// + /// Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + /// + HorizontalScrollbar = (int)Inno.Native.ImGui.ImGuiWindowFlags.HorizontalScrollbar, + /// + /// Disable taking focus when transitioning from hidden to visible state + /// + NoFocusOnAppearing = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoFocusOnAppearing, + /// + /// Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) + /// + NoBringToFrontOnFocus = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoBringToFrontOnFocus, + /// + /// Always show vertical scrollbar (even if ContentSize.y < Size.y) + /// + AlwaysVerticalScrollbar = (int)Inno.Native.ImGui.ImGuiWindowFlags.AlwaysVerticalScrollbar, + /// + /// Always show horizontal scrollbar (even if ContentSize.x < Size.x) + /// + AlwaysHorizontalScrollbar = (int)Inno.Native.ImGui.ImGuiWindowFlags.AlwaysHorizontalScrollbar, + /// + /// No keyboard/gamepad navigation within the window + /// + NoNavInputs = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoNavInputs, + /// + /// No focusing toward this window with keyboard/gamepad navigation (e.g. skipped by Ctrl+Tab) + /// + NoNavFocus = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoNavFocus, + /// + /// Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. + /// + UnsavedDocument = (int)Inno.Native.ImGui.ImGuiWindowFlags.UnsavedDocument, + /// + /// Disable docking of this window + /// + NoDocking = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoDocking, + /// + /// Selects the no nav presentation behavior. + /// + NoNav = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoNav, + /// + /// Selects the no decoration presentation behavior. + /// + NoDecoration = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoDecoration, + /// + /// Selects the no inputs presentation behavior. + /// + NoInputs = (int)Inno.Native.ImGui.ImGuiWindowFlags.NoInputs, + /// + /// Don't use! For internal use by Begin()/NewFrame() + /// + DockNodeHost = (int)Inno.Native.ImGui.ImGuiWindowFlags.DockNodeHost, + /// + /// Don't use! For internal use by BeginChild() + /// + ChildWindow = (int)Inno.Native.ImGui.ImGuiWindowFlags.ChildWindow, + /// + /// Don't use! For internal use by BeginTooltip() + /// + Tooltip = (int)Inno.Native.ImGui.ImGuiWindowFlags.Tooltip, + /// + /// Don't use! For internal use by BeginPopup() + /// + Popup = (int)Inno.Native.ImGui.ImGuiWindowFlags.Popup, + /// + /// Don't use! For internal use by BeginPopupModal() + /// + Modal = (int)Inno.Native.ImGui.ImGuiWindowFlags.Modal, + /// + /// Don't use! For internal use by BeginMenu() + /// + ChildMenu = (int)Inno.Native.ImGui.ImGuiWindowFlags.ChildMenu, +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs new file mode 100644 index 000000000..8ba5a9a2a --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/EditorDrawList.cs @@ -0,0 +1,202 @@ +using System; +using System.Numerics; +using BGCS.Runtime; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// A pointer-free drawing borrow valid only in its original window, UI session and frame. +/// +public readonly unsafe struct EditorDrawList +{ + private readonly EditorUiSession m_session; + private readonly int m_frame; + private readonly uint m_window; + internal EditorDrawList(EditorUiSession session) + { + ImGuiNativeContext* context = session.RequireDrawingFrame(); + m_session = session; + m_frame = context->FrameCount; + m_window = NativeImGui.Raw.GetCurrentWindow()->ID; + } + + /// + /// Adds a straight screen-space stroke. + /// + /// + /// The first endpoint. + /// + /// + /// The final endpoint. + /// + /// + /// Packed RGBA color. + /// + /// + /// Stroke width in UI pixels. + /// + public void Line( + Vector2 start, + Vector2 end, + uint color, + float thickness = 1f + ) => ImGui.DrawNativeLine(Resolve(), start, end, color, thickness); + /// + /// Adds a filled rectangular surface. + /// + /// + /// The upper-left corner. + /// + /// + /// The lower-right corner. + /// + /// + /// Packed RGBA color. + /// + /// + /// Corner radius. + /// + /// + /// Whether only the two top corners should be rounded. + /// + public void FilledRectangle( + Vector2 minimum, + Vector2 maximum, + uint color, + float rounding = 0f, + bool topCornersOnly = false + ) => ImGui.DrawNativeFilledRectangle(Resolve(), minimum, maximum, color, rounding, topCornersOnly ? (int)ImDrawFlags.RoundCornersTop : 0); + /// + /// Adds a rectangular outline. + /// + /// + /// The upper-left corner. + /// + /// + /// The lower-right corner. + /// + /// + /// Packed RGBA color. + /// + /// + /// Corner radius. + /// + /// + /// Stroke width in UI pixels. + /// + public void Rectangle( + Vector2 minimum, + Vector2 maximum, + uint color, + float rounding = 0f, + float thickness = 1f + ) => ImGui.DrawNativeRectangle(Resolve(), minimum, maximum, color, rounding, 0, thickness); + /// + /// Adds a filled circle with backend-selected tessellation. + /// + /// + /// The screen-space center. + /// + /// + /// The nonnegative radius. + /// + /// + /// Packed RGBA color. + /// + public void FilledCircle( + Vector2 center, + float radius, + uint color + ) { + ImVec2C native = ImGui.ToNative(center); + NativeImGui.Raw.AddCircleFilled(Resolve(), &native, radius, color, 0); + } + + /// + /// Adds literal text using the current font and an explicit scale. + /// + /// + /// Screen-space text origin. + /// + /// + /// Packed RGBA color. + /// + /// + /// Literal managed text; encoded only for this synchronous call. + /// + /// + /// Positive scale relative to the current font size. + /// + public void Text( + Vector2 position, + uint color, + string text, + float scale = 1f + ) { + ImDrawList* draw = Resolve(); + ArgumentNullException.ThrowIfNull(text); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(scale); + ImVec2C native = ImGui.ToNative(position); + using NativeStringLease encoded = Utf8Codec.EncodeNullTerminated(text, allowsNull: false); + NativeImGui.Raw.AddText(draw, NativeImGui.Raw.GetFont(), NativeImGui.GetFontSize() * scale, &native, color, (sbyte*)encoded.pointer, null, 0f, null); + } + + /// + /// Restricts subsequent drawing to one screen rectangle. + /// + /// + /// The clip minimum. + /// + /// + /// The clip maximum. + /// + /// + /// Whether to intersect the enclosing clip. + /// + public void PushClip( + Vector2 minimum, + Vector2 maximum, + bool intersect = true + ) { + ImVec2C first = ImGui.ToNative(minimum), last = ImGui.ToNative(maximum); + NativeImGui.Raw.PushClipRect(Resolve(), &first, &last, intersect ? (byte)1 : (byte)0); + } + + /// + /// Restores the enclosing clip after a matching PushClip. + /// + public void PopClip() => NativeImGui.Raw.PopClipRect(Resolve()); + /// + /// Begins ordered draw channels within this window. + /// + /// + /// The positive number of layers. + /// + public void SplitChannels(int count) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count); + NativeImGui.Raw.ChannelsSplit(Resolve(), count); + } + + /// + /// Selects a channel from the current split. + /// + /// + /// The zero-based channel. + /// + public void SetChannel(int index) => NativeImGui.Raw.ChannelsSetCurrent(Resolve(), index); + /// + /// Merges the current split back into ordered draw commands. + /// + public void MergeChannels() => NativeImGui.Raw.ChannelsMerge(Resolve()); + private ImDrawList* Resolve() + { + if (m_session is null) + throw new InvalidOperationException("A default drawing borrow has no owner."); + ImGuiNativeContext* context = m_session.RequireDrawingFrame(); + if (context->FrameCount != m_frame || context->CurrentWindow->ID != m_window) + throw new InvalidOperationException("The drawing borrow belongs to a different window or frame."); + return NativeImGui.Raw.GetWindowDrawList(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs new file mode 100644 index 000000000..85cc436ef --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Drawing/ImGui.Drawing.cs @@ -0,0 +1,157 @@ +using System.Numerics; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Copies presentation values and creates frame-bounded drawing borrows. +/// +public static unsafe partial class ImGui +{ + /// + /// Copies the current UI layout metrics. + /// + /// + /// Value metrics valid independently from the native style pointer. + /// + public static EditorUiLayout GetLayout() + { + EditorUiSession.current.EnsureDrawing(); + return new EditorUiLayout(NativeImGui.Raw.GetStyle()); + } + + /// + /// Copies the input facts for this frame. + /// + /// + /// A neutral value snapshot; no pointer or callback is retained. + /// + public static EditorUiInput GetInput() + { + EditorUiSession.current.EnsureDrawing(); + return new EditorUiInput(NativeImGui.Raw.GetIO()); + } + + /// + /// Copies an unmodulated style color for compound presentation. + /// + /// + /// The semantic style color slot. + /// + /// + /// The copied linear vector of UI color components. + /// + public static Vector4 GetStyleColorValue(ImGuiCol color) + { + EditorUiSession.current.EnsureDrawing(); + return FromNative(NativeImGui.Raw.GetStyle()->Colors[(int)color]); + } + + /// + /// Gets the logical viewport identity and copied position for the current window. + /// + /// + /// The viewport ID and its screen-space origin. + /// + public static (uint id, Vector2 position) GetViewport() + { + EditorUiSession.current.EnsureDrawing(); + var viewport = NativeImGui.Raw.GetWindowViewport(); + return (viewport->ID, FromNative(viewport->Pos)); + } + + /// + /// Gets the table and current row bounds needed by row overlays. + /// + /// + /// The table's horizontal bounds and current row's vertical bounds. + /// + /// + /// No valid drawing frame or active table exists. + /// + public static (float minimumX, float maximumX, float rowMinimum, float rowMaximum) GetTableBounds() + { + _ = EditorUiSession.current.RequireDrawingFrame(); + var table = NativeImGui.Raw.GetCurrentTable(); + if (table is null) + throw new System.InvalidOperationException("A table row layout requires an active table."); + return (table->OuterRect.Min.X, table->OuterRect.Max.X, table->RowPosY1, table->RowPosY2); + } + + /// + /// Gets copied inner and work bounds for the current window. + /// + /// + /// Screen-space horizontal bounds and work maximum. + /// + /// + /// No valid drawing session or native frame is active. + /// + public static (float minimumX, float maximumX, Vector2 workMaximum, Vector2 padding) GetWindowBounds() + { + _ = EditorUiSession.current.RequireDrawingFrame(); + var window = NativeImGui.Raw.GetCurrentWindow(); + return (window->InnerRect.Min.X, window->InnerRect.Max.X, FromNative(window->WorkRect.Max), FromNative(window->WindowPadding)); + } + + /// + /// Copies the current window's effective clip after parent and viewport clipping. + /// + /// + /// Screen-space minimum and maximum values for bounding frame-local submission work. + /// + /// + /// No valid drawing session or native frame is active. + /// + public static (Vector2 minimum, Vector2 maximum) GetClipBounds() + { + _ = EditorUiSession.current.RequireDrawingFrame(); + var clip = NativeImGui.Raw.GetCurrentWindow()->ClipRect; + return (FromNative(clip.Min), FromNative(clip.Max)); + } + + /// + /// Borrows drawing commands for the current window and frame without exposing native memory. + /// + /// + /// A value that rejects use after a window, frame, context or session change. + /// + public static EditorDrawList GetDrawList() => new(EditorUiSession.current); + /// + /// Checks the active payload's logical protocol without exposing its native buffer. + /// + /// + /// The exact drag protocol name. + /// + /// + /// True when a drag of that protocol is currently active. + /// + public static bool HasDragPayload(string protocol) + { + EditorUiSession.current.EnsureDrawing(); + var payload = NativeImGui.Raw.GetDragDropPayload(); + if (payload is null) + return false; + using var text = BGCS.Runtime.Utf8Codec.EncodeNullTerminated(protocol, allowsNull: false); + return NativeImGui.Raw.IsDataType(payload, (sbyte*)text.pointer) != 0; + } + + /// + /// Measures literal text at a relative font scale. + /// + /// + /// The literal text to measure. + /// + /// + /// Scale relative to the current font. + /// + /// + /// The copied width and height in UI pixels. + /// + public static Vector2 MeasureText( + string text, + float scale + ) { + EditorUiSession.current.EnsureDrawing(); + return NativeImGui.CalcTextSize(text) * scale; + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs index 96c799174..688b71635 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/ImGui.cs @@ -1,17 +1,15 @@ using System; using System.Numerics; using BGCS.Runtime; - using Inno.Native.ImGui; using RawImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui; - /// /// Exposes a script-safe, pointer-free subset of Dear ImGui for custom Editor tools. /// Every call is valid only while an Editor drawing callback is active. /// -public static unsafe class ImGui +public static unsafe partial class ImGui { /// /// Draws unformatted text. @@ -19,7 +17,11 @@ public static unsafe class ImGui /// /// Literal text to draw; percent characters are displayed without format-string interpretation. /// - public static void Text(string text) => RawImGui.TextUnformatted(text); + public static void Text(string text) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TextUnformatted(text); + } /// /// Draws a standard button. @@ -36,7 +38,10 @@ public static unsafe class ImGui public static bool Button( string label, Vector2 size = default - ) => RawImGui.Button(label, size); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.Button(label, size); + } /// /// Draws a compact button. @@ -47,7 +52,11 @@ public static bool Button( /// /// The visible label and optional ImGui identity suffix. /// - public static bool SmallButton(string label) => RawImGui.SmallButton(label); + public static bool SmallButton(string label) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.SmallButton(label); + } /// /// Draws and edits a Boolean value. @@ -64,7 +73,10 @@ public static bool Button( public static bool Checkbox( string label, ref bool value - ) => RawImGui.Checkbox(label, ref value); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.Checkbox(label, ref value); + } /// /// Draws and edits a bounded UTF-8 text value. @@ -144,10 +156,25 @@ public static bool InputTextWithHint( /// /// The integer displayed by the control and replaced after an accepted edit. /// + /// + /// Increment used by the ordinary step button. + /// + /// + /// Increment used by the accelerated step button. + /// + /// + /// Text editing options for the numeric field. + /// public static bool InputInt( string label, - ref int value - ) => RawImGui.InputInt(label, ref value, 1, 100, 0); + ref int value, + int step = 1, + int fastStep = 100, + ImGuiInputTextFlags flags = ImGuiInputTextFlags.None + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.InputInt(label, ref value, step, fastStep, (int)flags); + } /// /// Draws and edits a floating-point value. @@ -164,7 +191,10 @@ ref int value public static bool InputFloat( string label, ref float value - ) => RawImGui.InputFloat(label, ref value, 0f, 0f, "%.3f", 0); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.InputFloat(label, ref value, 0f, 0f, "%.3f", 0); + } /// /// Draws a floating-point drag control. @@ -193,8 +223,10 @@ public static bool DragFloat( float speed = 1f, float minimum = 0f, float maximum = 0f - ) - => RawImGui.DragFloat(label, ref value, speed, minimum, maximum); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.DragFloat(label, ref value, speed, minimum, maximum); + } /// /// Draws a bounded floating-point slider. @@ -223,8 +255,10 @@ public static bool SliderFloat( float minimum, float maximum, ImGuiSliderFlags flags = ImGuiSliderFlags.None - ) - => RawImGui.SliderFloat(label, ref value, minimum, maximum, (int)flags); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.SliderFloat(label, ref value, minimum, maximum, (int)flags); + } /// /// Draws and edits a four-component color. @@ -286,7 +320,8 @@ public static bool ColorEditLinear4( display.X == original.X ? value.X : SrgbToLinear(display.X), display.Y == original.Y ? value.Y : SrgbToLinear(display.Y), display.Z == original.Z ? value.Z : SrgbToLinear(display.Z), - display.W); + display.W + ); return true; } @@ -305,17 +340,27 @@ public static bool ColorEditLinear4( /// /// when child contents should be submitted. /// + /// + /// Scroll and window policies for the child. + /// public static bool BeginChild( string id, Vector2 size = default, - ImGuiChildFlags flags = ImGuiChildFlags.None - ) - => RawImGui.BeginChild(id, size, (int)(flags)); + ImGuiChildFlags flags = ImGuiChildFlags.None, + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.None + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.BeginChild(id, size, (int)flags, (int)windowFlags); + } /// /// Ends the current child region. /// - public static void EndChild() => RawImGui.EndChild(); + public static void EndChild() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndChild(); + } /// /// Begins a table. @@ -335,18 +380,28 @@ public static bool BeginChild( /// /// when table contents should be submitted. /// + /// + /// Optional inner scrolling width; zero uses the outer size. + /// public static bool BeginTable( string id, int columns, ImGuiTableFlags flags = ImGuiTableFlags.None, - Vector2 size = default - ) - => RawImGui.BeginTable(id, columns, (int)flags, size); + Vector2 size = default, + float innerWidth = 0f + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.BeginTable(id, columns, (int)flags, size, innerWidth); + } /// /// Ends the current table. /// - public static void EndTable() => RawImGui.EndTable(); + public static void EndTable() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndTable(); + } /// /// Declares one table column. @@ -364,18 +419,36 @@ public static void TableSetupColumn( string label, ImGuiTableColumnFlags flags = ImGuiTableColumnFlags.None, float widthOrWeight = 0f - ) - => RawImGui.TableSetupColumn(label, (int)(flags), widthOrWeight); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TableSetupColumn(label, (int)(flags), widthOrWeight); + } /// /// Draws the table header row from configured column labels. /// - public static void TableHeadersRow() => RawImGui.TableHeadersRow(); + public static void TableHeadersRow() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TableHeadersRow(); + } /// /// Advances to the next table row. /// - public static void TableNextRow() => RawImGui.TableNextRow(); + /// + /// Row presentation flags. + /// + /// + /// Minimum row height in UI pixels. + /// + public static void TableNextRow( + ImGuiTableRowFlags flags = ImGuiTableRowFlags.None, + float minimumHeight = 0f + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TableNextRow((int)flags, minimumHeight); + } /// /// Advances to the next table column. @@ -383,7 +456,11 @@ public static void TableSetupColumn( /// /// when the column is visible. /// - public static bool TableNextColumn() => RawImGui.TableNextColumn(); + public static bool TableNextColumn() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.TableNextColumn(); + } /// /// Begins a combo popup. @@ -404,13 +481,19 @@ public static bool BeginCombo( string label, string preview, ImGuiComboFlags flags = ImGuiComboFlags.None - ) - => RawImGui.BeginCombo(label, preview, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.BeginCombo(label, preview, (int)(flags)); + } /// /// Ends the current combo popup. /// - public static void EndCombo() => RawImGui.EndCombo(); + public static void EndCombo() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndCombo(); + } /// /// Draws one selectable item. @@ -427,12 +510,18 @@ public static bool BeginCombo( /// /// when the item is activated. /// + /// + /// The interaction bounds, or zero for the ordinary content size. + /// public static bool Selectable( string label, bool selected = false, - ImGuiSelectableFlags flags = ImGuiSelectableFlags.None - ) - => RawImGui.Selectable(label, selected, (int)flags); + ImGuiSelectableFlags flags = ImGuiSelectableFlags.None, + Vector2 size = default + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.Selectable(label, selected, (int)flags, size); + } /// /// Draws a collapsible section header. @@ -449,8 +538,10 @@ public static bool Selectable( public static bool CollapsingHeader( string label, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None - ) - => RawImGui.CollapsingHeader(label, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.CollapsingHeader(label, (int)(flags)); + } /// /// Draws a tree node and pushes its tree scope when it is open. @@ -467,12 +558,19 @@ public static bool CollapsingHeader( public static bool TreeNode( string label, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None - ) => RawImGui.TreeNodeEx(label, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.TreeNodeEx(label, (int)(flags)); + } /// /// Ends the current open tree-node scope. /// - public static void TreePop() => RawImGui.TreePop(); + public static void TreePop() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.TreePop(); + } /// /// Begins a tab bar. @@ -489,12 +587,19 @@ public static bool TreeNode( public static bool BeginTabBar( string id, ImGuiTabBarFlags flags = ImGuiTabBarFlags.None - ) => RawImGui.BeginTabBar(id, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.BeginTabBar(id, (int)(flags)); + } /// /// Ends the current tab bar. /// - public static void EndTabBar() => RawImGui.EndTabBar(); + public static void EndTabBar() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndTabBar(); + } /// /// Begins one tab item. @@ -542,7 +647,11 @@ public static unsafe bool BeginTabItem( /// /// Ends the current tab item. /// - public static void EndTabItem() => RawImGui.EndTabItem(); + public static void EndTabItem() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndTabItem(); + } /// /// Opens a named popup. @@ -556,7 +665,10 @@ public static unsafe bool BeginTabItem( public static void OpenPopup( string id, ImGuiPopupFlags flags = ImGuiPopupFlags.None - ) => RawImGui.OpenPopup(id, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.OpenPopup(id, (int)(flags)); + } /// /// Begins a named popup. @@ -573,17 +685,43 @@ public static void OpenPopup( public static bool BeginPopup( string id, ImGuiWindowFlags flags = ImGuiWindowFlags.None - ) => RawImGui.BeginPopup(id, (int)(flags)); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.BeginPopup(id, (int)(flags)); + } /// /// Ends the current popup. /// - public static void EndPopup() => RawImGui.EndPopup(); + public static void EndPopup() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndPopup(); + } /// /// Closes the current popup. /// - public static void CloseCurrentPopup() => RawImGui.CloseCurrentPopup(); + public static void CloseCurrentPopup() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.CloseCurrentPopup(); + } + + /// + /// Reads whether an open popup or modal currently prevents underlying canvas and list input. + /// + /// + /// True when any popup is open in this native context, independently of its identifier or nesting. + /// + /// + /// No valid drawing session or native frame is active. + /// + public static bool IsAnyPopupOpen() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsPopupOpen(string.Empty, (int)(ImGuiPopupFlags.AnyPopupId | ImGuiPopupFlags.AnyPopupLevel)); + } /// /// Begins a disabled block. @@ -591,22 +729,38 @@ public static bool BeginPopup( /// /// Whether controls in the block are disabled. /// - public static void BeginDisabled(bool disabled = true) => RawImGui.BeginDisabled(disabled); + public static void BeginDisabled(bool disabled = true) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.BeginDisabled(disabled); + } /// /// Ends the current disabled block. /// - public static void EndDisabled() => RawImGui.EndDisabled(); + public static void EndDisabled() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndDisabled(); + } /// /// Begins an item group. /// - public static void BeginGroup() => RawImGui.BeginGroup(); + public static void BeginGroup() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.BeginGroup(); + } /// /// Ends the current item group. /// - public static void EndGroup() => RawImGui.EndGroup(); + public static void EndGroup() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.EndGroup(); + } /// /// Pushes a string identity onto the ImGui ID stack. @@ -614,7 +768,11 @@ public static bool BeginPopup( /// /// The stable identity used to locate the requested value. /// - public static void PushId(string id) => RawImGui.PushID(id); + public static void PushId(string id) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushID(id); + } /// /// Pushes an integer identity onto the ImGui ID stack. @@ -622,12 +780,20 @@ public static bool BeginPopup( /// /// The stable identity used to locate the requested value. /// - public static void PushId(int id) => RawImGui.PushID(id); + public static void PushId(int id) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushID(id); + } /// /// Pops one identity from the ImGui ID stack. /// - public static void PopId() => RawImGui.PopID(); + public static void PopId() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PopID(); + } /// /// Pushes one color override. @@ -641,7 +807,10 @@ public static bool BeginPopup( public static void PushStyleColor( ImGuiCol color, Vector4 value - ) => RawImGui.PushStyleColor((int)(color), value); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushStyleColor((int)(color), value); + } /// /// Pops color overrides. @@ -649,7 +818,11 @@ Vector4 value /// /// The positive number of overrides to pop. /// - public static void PopStyleColor(int count = 1) => RawImGui.PopStyleColor(count); + public static void PopStyleColor(int count = 1) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PopStyleColor(count); + } /// /// Pushes one scalar style override. @@ -663,7 +836,10 @@ Vector4 value public static void PushStyleVar( ImGuiStyleVar style, float value - ) => RawImGui.PushStyleVar((int)(style), value); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushStyleVar((int)(style), value); + } /// /// Pushes one two-component style override. @@ -677,7 +853,10 @@ float value public static void PushStyleVar( ImGuiStyleVar style, Vector2 value - ) => RawImGui.PushStyleVar((int)(style), value); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PushStyleVar((int)(style), value); + } /// /// Pops style-variable overrides. @@ -685,7 +864,11 @@ Vector2 value /// /// The positive number of overrides to pop. /// - public static void PopStyleVar(int count = 1) => RawImGui.PopStyleVar(count); + public static void PopStyleVar(int count = 1) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.PopStyleVar(count); + } /// /// Places the next item on the same line. @@ -699,12 +882,19 @@ Vector2 value public static void SameLine( float offset = 0f, float spacing = -1f - ) => RawImGui.SameLine(offset, spacing); + ) { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SameLine(offset, spacing); + } /// /// Draws a horizontal separator. /// - public static void Separator() => RawImGui.Separator(); + public static void Separator() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.Separator(); + } /// /// Draws a labeled horizontal separator. @@ -712,12 +902,20 @@ public static void SameLine( /// /// The label text validated by the separator text operation. /// - public static void SeparatorText(string label) => RawImGui.SeparatorText(label); + public static void SeparatorText(string label) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SeparatorText(label); + } /// /// Adds one standard vertical spacing unit. /// - public static void Spacing() => RawImGui.Spacing(); + public static void Spacing() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.Spacing(); + } /// /// Advances layout by an invisible size. @@ -725,7 +923,11 @@ public static void SameLine( /// /// The size consumed by dummy; ownership remains with the caller unless explicitly stated otherwise. /// - public static void Dummy(Vector2 size) => RawImGui.Dummy(size); + public static void Dummy(Vector2 size) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.Dummy(size); + } /// /// Sets the width of the next item. @@ -733,7 +935,23 @@ public static void SameLine( /// /// The width in logical units or pixels required by this operation. /// - public static void SetNextItemWidth(float width) => RawImGui.SetNextItemWidth(width); + public static void SetNextItemWidth(float width) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetNextItemWidth(width); + } + + /// + /// Requests keyboard focus for the next submitted item in the current frame and window. + /// + /// + /// No matching owner-thread drawing session is active. + /// + public static void FocusNextItem() + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetKeyboardFocusHere(); + } /// /// Gets the remaining content size in the current region. @@ -741,7 +959,11 @@ public static void SameLine( /// /// The remaining local width and height. /// - public static Vector2 GetContentRegionAvailable() => RawImGui.GetContentRegionAvail(); + public static Vector2 GetContentRegionAvailable() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetContentRegionAvail(); + } /// /// Gets the current local cursor position. @@ -749,7 +971,11 @@ public static void SameLine( /// /// The local cursor position. /// - public static Vector2 GetCursorPosition() => RawImGui.GetCursorPos(); + public static Vector2 GetCursorPosition() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetCursorPos(); + } /// /// Sets the current local cursor position. @@ -757,7 +983,11 @@ public static void SameLine( /// /// The position consumed by set cursor position; ownership remains with the caller unless explicitly stated otherwise. /// - public static void SetCursorPosition(Vector2 position) => RawImGui.SetCursorPos(position); + public static void SetCursorPosition(Vector2 position) + { + EditorUiSession.current.EnsureDrawing(); + RawImGui.SetCursorPos(position); + } /// /// Gets the current screen-space cursor position. @@ -765,7 +995,11 @@ public static void SameLine( /// /// The screen-space cursor position. /// - public static Vector2 GetCursorScreenPosition() => RawImGui.GetCursorScreenPos(); + public static Vector2 GetCursorScreenPosition() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetCursorScreenPos(); + } /// /// Gets the minimum screen-space corner of the previous item. @@ -773,7 +1007,11 @@ public static void SameLine( /// /// The item minimum corner. /// - public static Vector2 GetItemMinimum() => RawImGui.GetItemRectMin(); + public static Vector2 GetItemMinimum() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetItemRectMin(); + } /// /// Gets the maximum screen-space corner of the previous item. @@ -781,7 +1019,11 @@ public static void SameLine( /// /// The item maximum corner. /// - public static Vector2 GetItemMaximum() => RawImGui.GetItemRectMax(); + public static Vector2 GetItemMaximum() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetItemRectMax(); + } /// /// Measures text using the current font. @@ -792,7 +1034,11 @@ public static void SameLine( /// /// The text text validated by the measure text operation. /// - public static Vector2 MeasureText(string text) => RawImGui.CalcTextSize(text); + public static Vector2 MeasureText(string text) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.CalcTextSize(text); + } /// /// Gets whether the previous item is hovered. @@ -803,7 +1049,11 @@ public static void SameLine( /// /// when the query succeeds. /// - public static bool IsItemHovered(ImGuiHoveredFlags flags = ImGuiHoveredFlags.None) => RawImGui.IsItemHovered((int)(flags)); + public static bool IsItemHovered(ImGuiHoveredFlags flags = ImGuiHoveredFlags.None) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsItemHovered((int)(flags)); + } /// /// Gets whether the previous item is active. @@ -811,7 +1061,11 @@ public static void SameLine( /// /// while the item owns an active interaction. /// - public static bool IsItemActive() => RawImGui.IsItemActive(); + public static bool IsItemActive() + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsItemActive(); + } /// /// Gets whether the previous item was clicked with a mouse button. @@ -822,7 +1076,11 @@ public static void SameLine( /// /// when the item was clicked. /// - public static bool IsItemClicked(ImGuiMouseButton button = ImGuiMouseButton.Left) => RawImGui.IsItemClicked((int)(button)); + public static bool IsItemClicked(ImGuiMouseButton button = ImGuiMouseButton.Left) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsItemClicked((int)(button)); + } /// /// Gets whether a mouse button is held in the current Editor UI frame, including input consumed by UI controls. @@ -833,7 +1091,11 @@ public static void SameLine( /// /// True while the button is held; callers must also check their item's active or hovered state before editing. /// - public static bool IsMouseButtonDown(ImGuiMouseButton button = ImGuiMouseButton.Left) => RawImGui.IsMouseDown((int)button); + public static bool IsMouseButtonDown(ImGuiMouseButton button = ImGuiMouseButton.Left) + { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.IsMouseDown((int)button); + } /// /// Gets accumulated pointer displacement from the current UI drag's starting position for timeline and canvas controls. @@ -851,7 +1113,10 @@ public static void SameLine( public static Vector2 GetMouseDragDelta( ImGuiMouseButton button = ImGuiMouseButton.Left, float threshold = -1f - ) => RawImGui.GetMouseDragDelta((int)button, threshold); + ) { + EditorUiSession.current.EnsureDrawing(); + return RawImGui.GetMouseDragDelta((int)button, threshold); + } /// /// Converts a floating-point RGBA color to Dear ImGui packed color order. @@ -862,7 +1127,13 @@ public static Vector2 GetMouseDragDelta( /// /// The color consumed by to packed color; ownership remains with the caller unless explicitly stated otherwise. /// - public static uint ToPackedColor(Vector4 color) => RawImGui.ColorConvertFloat4ToU32(color); + public static uint ToPackedColor(Vector4 color) + { + EditorUiSession.current.EnsureDrawing(); + return Channel(color.X) | Channel(color.Y) << 8 | Channel(color.Z) << 16 | Channel(color.W) << 24; + + static uint Channel(float value) => unchecked((uint)float.ConvertToIntegerNative(Math.Clamp(value, 0f, 1f) * 255f + 0.5f)); + } /// /// Adds a line to the current window draw list. @@ -884,8 +1155,10 @@ public static void DrawLine( Vector2 end, uint color, float thickness = 1f - ) - => ImGui.DrawNativeLine(RawImGui.Raw.GetWindowDrawList(), start, end, color, thickness); + ) { + EditorUiSession.current.EnsureDrawing(); + ImGui.DrawNativeLine(RawImGui.Raw.GetWindowDrawList(), start, end, color, thickness); + } /// /// Adds a rectangle outline to the current window draw list. @@ -911,8 +1184,10 @@ public static void DrawRectangle( uint color, float rounding = 0f, float thickness = 1f - ) - => ImGui.DrawNativeRectangle(RawImGui.Raw.GetWindowDrawList(), minimum, maximum, color, rounding, 0, thickness); + ) { + EditorUiSession.current.EnsureDrawing(); + ImGui.DrawNativeRectangle(RawImGui.Raw.GetWindowDrawList(), minimum, maximum, color, rounding, 0, thickness); + } /// /// Adds a filled rectangle to the current window draw list. @@ -934,8 +1209,44 @@ public static void DrawFilledRectangle( Vector2 maximum, uint color, float rounding = 0f - ) - => ImGui.DrawNativeFilledRectangle(RawImGui.Raw.GetWindowDrawList(), minimum, maximum, color, rounding, 0); + ) { + EditorUiSession.current.EnsureDrawing(); + ImGui.DrawNativeFilledRectangle(RawImGui.Raw.GetWindowDrawList(), minimum, maximum, color, rounding, 0); + } + + /// + /// Draws a cubic curve without exposing a native draw list or borrowed pointer. + /// + /// + /// The first screen-space endpoint. + /// + /// + /// The control point beside the first endpoint. + /// + /// + /// The control point beside the final endpoint. + /// + /// + /// The final screen-space endpoint. + /// + /// + /// The packed presentation color. + /// + /// + /// The positive stroke width. + /// + public static void DrawCubicCurve( + Vector2 start, + Vector2 firstControl, + Vector2 secondControl, + Vector2 end, + uint color, + float thickness = 1f + ) { + EditorUiSession.current.EnsureDrawing(); + ImVec2C a = ToNative(start), b = ToNative(firstControl), c = ToNative(secondControl), d = ToNative(end); + RawImGui.Raw.AddBezierCubic(RawImGui.Raw.GetWindowDrawList(), &a, &b, &c, &d, color, thickness, 0); + } /// /// Adds text to the current window draw list. @@ -953,16 +1264,25 @@ public static void DrawText( Vector2 position, uint color, string text - ) => ImGui.DrawNativeText(RawImGui.Raw.GetWindowDrawList(), position, color, text); - - internal static ImVec2C ToNative(Vector2 value) => new() { X = value.X, Y = value.Y }; - - internal static ImVec4C ToNative(Vector4 value) => new() { X = value.X, Y = value.Y, Z = value.Z, W = value.W }; + ) { + EditorUiSession.current.EnsureDrawing(); + ImGui.DrawNativeText(RawImGui.Raw.GetWindowDrawList(), position, color, text); + } + internal static ImVec2C ToNative(Vector2 value) => new() + { + X = value.X, + Y = value.Y + }; + internal static ImVec4C ToNative(Vector4 value) => new() + { + X = value.X, + Y = value.Y, + Z = value.Z, + W = value.W + }; internal static Vector2 FromNative(ImVec2C value) => new(value.X, value.Y); - internal static Vector4 FromNative(ImVec4C value) => new(value.X, value.Y, value.Z, value.W); - internal static void DrawNativeLine( ImDrawList* drawList, Vector2 start, @@ -1085,8 +1405,7 @@ internal static bool BeginNativeWindow( bool modal ) { using NativeStringLease nativeLabel = Utf8Codec.EncodeNullTerminated(label, allowsNull: false); - return (modal ? RawImGui.Raw.BeginPopupModal((sbyte*)nativeLabel.pointer, null, (int)flags) - : RawImGui.Raw.Begin((sbyte*)nativeLabel.pointer, null, (int)flags)) != 0; + return (modal ? RawImGui.Raw.BeginPopupModal((sbyte*)nativeLabel.pointer, null, (int)flags) : RawImGui.Raw.Begin((sbyte*)nativeLabel.pointer, null, (int)flags)) != 0; } internal static uint HashNativeId( @@ -1098,6 +1417,5 @@ internal static uint HashNativeId( } private static float LinearToSrgb(float value) => value <= 0.0031308f ? value * 12.92f : 1.055f * MathF.Pow(value, 1f / 2.4f) - 0.055f; - private static float SrgbToLinear(float value) => value <= 0.04045f ? value / 12.92f : MathF.Pow((value + 0.055f) / 1.055f, 2.4f); } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Inno.Editor.ImGui.csproj b/src/composition/editor/presentation/Inno.Editor.ImGui/Inno.Editor.ImGui.csproj index 5dfa97cd4..201a6a18e 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Inno.Editor.ImGui.csproj +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Inno.Editor.ImGui.csproj @@ -11,28 +11,22 @@ - - - - + + + + + - - - - + + + + + diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Properties/ScriptingApi.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Properties/ScriptingApi.cs index 52d83c98f..10f5afa90 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Properties/ScriptingApi.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Properties/ScriptingApi.cs @@ -1,29 +1,14 @@ using Inno.Scripting.Api; using Inno.Editor.ImGui; using Inno.Editor.ImGui.ImGuiWidget; -using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -[assembly: ScriptingApiNamespace( - "InnoEditor.ImGui", - "Inno.Editor.ImGui", - ScriptingApiScope.Editor)] -[assembly: ScriptingApiNamespace( - "InnoEditor.ImGui", - "Inno.Editor.ImGui.ImGuiWidget", - ScriptingApiScope.Editor)] -[assembly: ScriptingApiNamespace( - "InnoEditor.ImGui", - "Inno.Native.ImGui", - ScriptingApiScope.Editor)] -[assembly: ScriptingApiNamespace( - "InnoEditor.ImGui", - "Inno.Adapter.Presentation.ImGui", - ScriptingApiScope.Editor)] - +[assembly: ScriptingApiNamespace("InnoEditor.ImGui", "Inno.Editor.ImGui", ScriptingApiScope.Editor)] +[assembly: ScriptingApiNamespace("InnoEditor.ImGui", "Inno.Editor.ImGui.ImGuiWidget", ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorUiSession), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorPalette), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorStyleMetrics), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ListNavigationResult), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(Inno.Editor.ImGui.ImGui), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(EditorWidget), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(InlineRenameResult), ScriptingApiScope.Editor)] @@ -48,3 +33,20 @@ [assembly: ScriptingApiExport(typeof(ImGuiTableFlags), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ImGuiTreeNodeFlags), ScriptingApiScope.Editor)] [assembly: ScriptingApiExport(typeof(ImGuiWindowFlags), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorDrawList), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorUiLayout), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(EditorUiInput), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(TransformGizmoOperation), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(TransformGizmoSpace), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(TransformGizmoOptions), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(TransformGizmoResult), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiButtonFlags), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiCond), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiFocusedFlags), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiKey), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiMouseCursor), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiTableBgTarget), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiTableRowFlags), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiFont), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiFontScope), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ImGuiFontStyle), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs index 5dbdda45e..b6cfab1b8 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorDragDropRenderer.cs @@ -1,20 +1,17 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Globalization; - using Inno.Core.Identity; using Inno.Editor.Interactions; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; namespace Inno.Editor.ImGui; - /// /// Bridges managed editor drag sessions to the native ImGui payload API. /// public static class EditorDragDropRenderer { private const string C_IDENTITY_PAYLOAD_PREFIX = "INNO_ID_"; - /// /// Publishes managed drag data for the most recently submitted ImGui item. /// @@ -36,10 +33,7 @@ public static bool Source( Action? drawPreview = null ) { ArgumentNullException.ThrowIfNull(data); - return EditorWidget.DragDropSource( - GetPayloadType(data.sourceIdentity.domainId), - () => interaction.BeginDrag(data).runtimeId, - drawPreview); + return EditorUI.current.widgets.DragDropSource(GetPayloadType(data.sourceIdentity.domainId), () => interaction.BeginDrag(data).runtimeId, drawPreview); } /// @@ -60,25 +54,18 @@ public static EditorDropWidgetResult Target( ) { if (!interaction.TryGetActiveDragIdentity(out RuntimeIdentity activeIdentity)) return EditorDropWidgetResult.none; - bool delivered = EditorWidget.DragDropTarget( - GetPayloadType(activeIdentity.domainId), - out int runtimeId, - out bool isPreviewing, - drawDefaultHighlight: false); + bool delivered = EditorUI.current.widgets.DragDropTarget(GetPayloadType(activeIdentity.domainId), out int runtimeId, out bool isPreviewing, drawDefaultHighlight: false); if (!isPreviewing && !delivered) return EditorDropWidgetResult.none; if (runtimeId <= 0) return EditorDropWidgetResult.none; var identity = new RuntimeIdentity(activeIdentity.domainId, runtimeId); EditorDropStatus status = interaction.QueryDrop(identity, placement); - EditorDropResult result = delivered && status.canDrop - ? interaction.Drop(identity, placement) - : EditorDropResult.rejected; + EditorDropResult result = delivered && status.canDrop ? interaction.Drop(identity, placement) : EditorDropResult.rejected; return new EditorDropWidgetResult(isPreviewing, status, result); } - private static string GetPayloadType(IdentityDomainId domainId) - => C_IDENTITY_PAYLOAD_PREFIX + domainId.value.ToString("X8", CultureInfo.InvariantCulture); + private static string GetPayloadType(IdentityDomainId domainId) => C_IDENTITY_PAYLOAD_PREFIX + domainId.value.ToString("X8", CultureInfo.InvariantCulture); } /// @@ -112,17 +99,14 @@ EditorDropResult result /// Gets whether a compatible native payload is hovering the target. /// public bool isPreviewing { get; } - /// /// Gets the managed drop compatibility status. /// public EditorDropStatus status { get; } - /// /// Gets the delivered drop result. /// public EditorDropResult result { get; } - /// /// Gets an inactive drop target result. /// diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs index 53d90660d..bf661b25f 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorMenuRenderer.cs @@ -1,28 +1,33 @@ +using System.Linq; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Numerics; - using Inno.Editor.Interactions; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui; - /// /// Renders immutable editor menu models through ImGui. /// -public static unsafe class EditorMenuRenderer +public sealed unsafe class EditorMenuRenderer { - private static readonly Dictionary m_contextSearches = []; + private readonly EditorUiSession m_session; + private readonly Dictionary<(string Owner, uint Popup), string> m_contextSearches = []; + internal EditorMenuRenderer(EditorUiSession session) => m_session = session; + internal void RetireOwner(string owner) + { + foreach (var key in m_contextSearches.Keys.Where(value => value.Owner == owner).ToArray()) + m_contextSearches.Remove(key); + } + internal void Clear() => m_contextSearches.Clear(); private readonly record struct MenuSection( EditorInteraction interaction, IReadOnlyList items ); - /// /// Draws a resolved right-click menu for the most recently submitted ImGui item. /// @@ -35,21 +40,23 @@ IReadOnlyList items /// /// while the context popup is open and its items were drawn. /// - public static bool ContextMenu( + public bool ContextMenu( string id, EditorInteraction interaction ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); uint popupId = NativeImGui.GetID(id); if (!ShouldResolveItemContextMenu(id)) { - m_contextSearches.Remove(popupId); + m_contextSearches.Remove((m_session.owner, popupId)); return false; } + EditorMenuModel menu = interaction.BuildMenu(); if (menu.items.Count == 0) return false; - if (!EditorWidget.BeginContextMenu(id)) + if (!EditorUI.current.widgets.BeginContextMenu(id)) return false; try { @@ -57,8 +64,9 @@ EditorInteraction interaction } finally { - EditorWidget.EndContextMenu(); + EditorUI.current.widgets.EndContextMenu(); } + return true; } @@ -77,37 +85,35 @@ EditorInteraction interaction /// /// while the context popup is open and at least one command group was drawn. /// - public static bool ContextMenu( + public bool ContextMenu( string id, EditorInteraction scopeInteraction, EditorInteraction itemInteraction ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); uint popupId = NativeImGui.GetID(id); if (!ShouldResolveItemContextMenu(id)) { - m_contextSearches.Remove(popupId); + m_contextSearches.Remove((m_session.owner, popupId)); return false; } + EditorMenuModel scopeMenu = scopeInteraction.BuildMenu(); EditorMenuModel itemMenu = itemInteraction.BuildMenu(); if (scopeMenu.items.Count == 0 && itemMenu.items.Count == 0) return false; - if (!EditorWidget.BeginContextMenu(id)) + if (!EditorUI.current.widgets.BeginContextMenu(id)) return false; try { - DrawContextMenuContents( - popupId, - [ - new MenuSection(scopeInteraction, scopeMenu.items), - new MenuSection(itemInteraction, itemMenu.items) - ]); + DrawContextMenuContents(popupId, [new MenuSection(scopeInteraction, scopeMenu.items), new MenuSection(itemInteraction, itemMenu.items)]); } finally { - EditorWidget.EndContextMenu(); + EditorUI.current.widgets.EndContextMenu(); } + return true; } @@ -123,21 +129,23 @@ EditorInteraction itemInteraction /// /// while the background context popup is open and its items were drawn. /// - public static bool WindowContextMenu( + public bool WindowContextMenu( string id, EditorInteraction interaction ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); uint popupId = NativeImGui.GetID(id); if (!ShouldResolveWindowContextMenu(id)) { - m_contextSearches.Remove(popupId); + m_contextSearches.Remove((m_session.owner, popupId)); return false; } + EditorMenuModel menu = interaction.BuildMenu(); if (menu.items.Count == 0) return false; - if (!EditorWidget.BeginWindowContextMenu(id)) + if (!EditorUI.current.widgets.BeginWindowContextMenu(id)) return false; try { @@ -145,38 +153,31 @@ EditorInteraction interaction } finally { - EditorWidget.EndContextMenu(); + EditorUI.current.widgets.EndContextMenu(); } + return true; } - private static void DrawContextMenuContents( + private void DrawContextMenuContents( uint popupId, IReadOnlyList sections ) { - string search = NativeImGui.IsWindowAppearing() - ? string.Empty - : m_contextSearches.GetValueOrDefault(popupId, string.Empty); - NativeImGui.PushStyleVar( - (int)(ImGuiStyleVar.FramePadding), - EditorWidget.style.menuSearchFramePadding); + string search = NativeImGui.IsWindowAppearing() ? string.Empty : m_contextSearches.GetValueOrDefault((m_session.owner, popupId), string.Empty); + NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), EditorUI.current.widgets.style.menuSearchFramePadding); try { if (NativeImGui.IsWindowAppearing()) NativeImGui.SetKeyboardFocusHere(); - _ = EditorWidget.SearchInput( - $"context-menu-{popupId}", - "Search commands…", - ref search, - width: -1f); + _ = EditorUI.current.widgets.SearchInput($"context-menu-{popupId}", "Search commands…", ref search, width: -1f); } finally { NativeImGui.PopStyleVar(); } - m_contextSearches[popupId] = search; - NativeImGui.Separator(); + m_contextSearches[(m_session.owner, popupId)] = search; + NativeImGui.Separator(); bool searching = !string.IsNullOrWhiteSpace(search); bool drewSection = false; foreach (MenuSection section in sections) @@ -192,29 +193,28 @@ IReadOnlyList sections NativeImGui.CloseCurrentPopup(); return; } + DrawItems(section.interaction, section.items); drewSection = true; } } - private static bool ShouldResolveItemContextMenu(string id) - => NativeImGui.IsPopupOpen(id) || - NativeImGui.IsItemHovered() && NativeImGui.IsMouseReleased((int)(Inno.Native.ImGui.ImGuiMouseButton.Right)); - - private static bool ShouldResolveWindowContextMenu(string id) - => NativeImGui.IsPopupOpen(id) || - NativeImGui.IsWindowHovered() && - !NativeImGui.IsAnyItemHovered() && - NativeImGui.IsMouseReleased((int)(Inno.Native.ImGui.ImGuiMouseButton.Right)); - + private bool ShouldResolveItemContextMenu(string id) => NativeImGui.IsPopupOpen(id) + || NativeImGui.IsItemHovered() + && NativeImGui.IsMouseReleased((int)(Inno.Native.ImGui.ImGuiMouseButton.Right)); + private bool ShouldResolveWindowContextMenu(string id) => NativeImGui.IsPopupOpen(id) + || NativeImGui.IsWindowHovered() + && !NativeImGui.IsAnyItemHovered() + && NativeImGui.IsMouseReleased((int)(Inno.Native.ImGui.ImGuiMouseButton.Right)); /// /// Draws the complete editor main menu bar for the supplied menu context. /// /// /// The main-menu interaction area. /// - public static void MainMenu(EditorInteraction interaction) + public void MainMenu(EditorInteraction interaction) { + m_session.EnsureDrawing(); if (!NativeImGui.BeginMainMenuBar()) return; try @@ -228,7 +228,7 @@ public static void MainMenu(EditorInteraction interaction) } } - private static void DrawCenteredToolbar( + private void DrawCenteredToolbar( EditorInteraction interaction, IReadOnlyList items ) { @@ -240,7 +240,6 @@ IReadOnlyList items float centeredOffset = (NativeImGui.GetWindowWidth() - width) * 0.5f; float minimumOffset = NativeImGui.GetCursorPosX() + style->ItemSpacing.X; NativeImGui.SameLine(MathF.Max(centeredOffset, minimumOffset), 0f); - for (int i = 0; i < items.Count; i++) { if (i > 0) @@ -249,7 +248,7 @@ IReadOnlyList items } } - private static void DrawToolbarItem( + private void DrawToolbarItem( EditorInteraction interaction, EditorToolbarItem item, Vector2 size @@ -263,14 +262,9 @@ Vector2 size bool active = NativeImGui.IsItemActive(); if (disabled) NativeImGui.EndDisabled(); - if (item.status.isChecked || hovered || active) { - Vector4 background = active - ? EditorPalette.accentActive - : hovered - ? EditorPalette.accentHovered - : EditorPalette.accent; + Vector4 background = active ? EditorPalette.accentActive : hovered ? EditorPalette.accentHovered : EditorPalette.accent; ImGui.DrawNativeFilledRectangle( NativeImGui.Raw.GetWindowDrawList(), minimum, @@ -282,37 +276,27 @@ Vector2 size } uint color = NativeImGui.GetColorU32((int)(disabled ? ImGuiCol.TextDisabled : ImGuiCol.Text)); - EditorWidget.AddGlyphCentered( - NativeImGui.Raw.GetWindowDrawList(), - NativeImGui.Raw.GetFont(), - NativeImGui.GetFontSize(), - GetToolbarIcon(item.icon), - minimum + size * 0.5f, - color); - - if (hovered && EditorWidget.BeginMenuTooltip()) + EditorUI.current.widgets.AddGlyphCentered(NativeImGui.GetFontSize(), GetToolbarIcon(item.icon), minimum + size * 0.5f, color); + if (hovered && EditorUI.current.widgets.BeginMenuTooltip()) { - string tooltip = interaction.TryGetShortcut(item.actionId, out HotKeyGesture shortcut) - ? $"{item.tooltip} ({shortcut})" - : item.tooltip; + string tooltip = interaction.TryGetShortcut(item.actionId, out HotKeyGesture shortcut) ? $"{item.tooltip} ({shortcut})" : item.tooltip; NativeImGui.TextUnformatted(tooltip); - EditorWidget.EndMenuTooltip(); + EditorUI.current.widgets.EndMenuTooltip(); } + if (pressed && item.status.isEnabled) interaction.Enqueue(item.actionId); } - private static string GetToolbarIcon(EditorToolbarIcon icon) - => icon switch - { - EditorToolbarIcon.Play => ImGuiIcon.Play, - EditorToolbarIcon.Stop => ImGuiIcon.Stop, - EditorToolbarIcon.Pause => ImGuiIcon.Pause, - EditorToolbarIcon.Step => ImGuiIcon.ForwardStep, - EditorToolbarIcon.Edit => ImGuiIcon.Pen, - _ => string.Empty - }; - + private string GetToolbarIcon(EditorToolbarIcon icon) => icon switch + { + EditorToolbarIcon.Play => ImGuiIcon.Play, + EditorToolbarIcon.Stop => ImGuiIcon.Stop, + EditorToolbarIcon.Pause => ImGuiIcon.Pause, + EditorToolbarIcon.Step => ImGuiIcon.ForwardStep, + EditorToolbarIcon.Edit => ImGuiIcon.Pen, + _ => string.Empty + }; /// /// Recursively draws resolved menu nodes into the currently open popup or menu. /// @@ -322,10 +306,11 @@ private static string GetToolbarIcon(EditorToolbarIcon icon) /// /// The immutable menu nodes to draw in display order. /// - public static void DrawItems( + public void DrawItems( EditorInteraction interaction, IReadOnlyList items ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(items); for (int i = 0; i < items.Count; i++) { @@ -347,20 +332,17 @@ IReadOnlyList items { NativeImGui.EndMenu(); } + continue; } - string shortcut = interaction.TryGetShortcut(item.actionId, out HotKeyGesture gesture) - ? gesture.ToString() - : string.Empty; - if (NativeImGui.MenuItem( - item.label, - shortcut, - item.status.isChecked, - item.status.isEnabled)) + string shortcut = interaction.TryGetShortcut(item.actionId, out HotKeyGesture gesture) ? gesture.ToString() : string.Empty; + if (NativeImGui.MenuItem(item.label, shortcut, item.status.isChecked, item.status.isEnabled)) { interaction.Enqueue(item.actionId, item.argument); } + + DrawUnavailableReason(item.status); } } @@ -379,16 +361,17 @@ IReadOnlyList items /// /// when a command was selected. /// - public static bool DrawSearchItems( + public bool DrawSearchItems( EditorInteraction interaction, IReadOnlyList items, string search ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(items); return DrawSearchItems(interaction, items, search ?? string.Empty, string.Empty); } - private static bool DrawSearchItems( + private bool DrawSearchItems( EditorInteraction interaction, IReadOnlyList items, string search, @@ -397,26 +380,53 @@ string parentPath for (int i = 0; i < items.Count; i++) { EditorMenuItem item = items[i]; - string path = string.IsNullOrEmpty(parentPath) - ? item.label - : $"{parentPath}/{item.label}"; + string path = string.IsNullOrEmpty(parentPath) ? item.label : $"{parentPath}/{item.label}"; if (item.children.Count > 0) { if (DrawSearchItems(interaction, item.children, search, path)) return true; continue; } - if (!item.status.isEnabled || - !string.IsNullOrWhiteSpace(search) && - path.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) + + if (!string.IsNullOrWhiteSpace(search) && path.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) { continue; } - if (!NativeImGui.Selectable(path)) - continue; - interaction.Enqueue(item.actionId, item.argument); - return true; + + bool chosen; + NativeImGui.BeginDisabled(!item.status.isEnabled); + try + { + chosen = NativeImGui.Selectable(path); + } + finally + { + NativeImGui.EndDisabled(); + } + + DrawUnavailableReason(item.status); + if (chosen && item.status.isEnabled) + { + interaction.Enqueue(item.actionId, item.argument); + return true; + } } + return false; } + + private void DrawUnavailableReason(EditorActionState state) + { + if (state.isEnabled || string.IsNullOrWhiteSpace(state.unavailableReason) || !NativeImGui.IsItemHovered((int)ImGuiHoveredFlags.AllowWhenDisabled)) + return; + NativeImGui.BeginTooltip(); + try + { + EditorUI.current.widgets.WrappedText(state.unavailableReason); + } + finally + { + NativeImGui.EndTooltip(); + } + } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalHost.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalHost.cs index 54cef0f5a..d3f9772a8 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalHost.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalHost.cs @@ -1,17 +1,14 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; - using Inno.Editor.Core; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Interactions; namespace Inno.Editor.ImGui; - internal sealed class EditorModalHost { private readonly Dictionary m_transitions = new(StringComparer.Ordinal); - internal bool Update( IReadOnlyList modals, double now @@ -19,13 +16,13 @@ double now bool newBlockingModalRequested = false; for (int i = 0; i < modals.Count; i++) { - if (modals[i].TryGetPresentation(out EditorModalExtension.Presentation candidate) && - candidate.isVisible && candidate.blocksInteraction) + if (modals[i].TryGetPresentation(out EditorModalExtension.Presentation candidate) && candidate.isVisible && candidate.blocksInteraction) { newBlockingModalRequested = true; break; } } + bool blocksInteraction = false; for (int i = 0; i < modals.Count; i++) { @@ -40,6 +37,7 @@ double now if (presentation.blocksInteraction && transition.isVisible) blocksInteraction = true; } + return blocksInteraction; } @@ -56,24 +54,19 @@ double now EditorModalRenderer.Close(extension.id, extension.title); continue; } + Transition transition = GetTransition(extension.id); if (!transition.isVisible) { EditorModalRenderer.Close(extension.id, extension.title); continue; } - EditorModalRenderer.Draw( - extension.id, - extension.title, - transition.GetAlpha(now), - extension, - presentation, - context); + + EditorModalRenderer.Draw(extension.id, extension.title, transition.GetAlpha(now), extension, presentation, context); } } internal void Clear() => m_transitions.Clear(); - private Transition GetTransition(string id) { if (m_transitions.TryGetValue(id, out Transition? transition)) @@ -88,7 +81,6 @@ private sealed class Transition private double m_visibleAt; private double m_hideAt; private bool m_requested; - internal bool isVisible { get; private set; } internal void HideImmediately() @@ -110,17 +102,16 @@ double now m_visibleAt = now; isVisible = true; } + m_hideAt = double.PositiveInfinity; } else if (m_requested) { - m_hideAt = Math.Max( - now, - m_visibleAt + EditorWidget.style.modalMinimumVisibleSeconds); + m_hideAt = Math.Max(now, m_visibleAt + EditorUI.current.widgets.style.modalMinimumVisibleSeconds); } + m_requested = requested; - if (isVisible && !requested && - now >= m_hideAt + EditorWidget.style.modalFadeOutSeconds) + if (isVisible && !requested && now >= m_hideAt + EditorUI.current.widgets.style.modalFadeOutSeconds) { isVisible = false; } @@ -130,10 +121,11 @@ internal float GetAlpha(double now) { if (m_requested || now <= m_hideAt) { - double fadeIn = (now - m_visibleAt) / EditorWidget.style.modalFadeInSeconds; + double fadeIn = (now - m_visibleAt) / EditorUI.current.widgets.style.modalFadeInSeconds; return (float)Math.Clamp(fadeIn, EditorPalette.opacityFaint, EditorPalette.opacityOpaque); } - double fadeOut = (now - m_hideAt) / EditorWidget.style.modalFadeOutSeconds; + + double fadeOut = (now - m_hideAt) / EditorUI.current.widgets.style.modalFadeOutSeconds; return (float)Math.Clamp(1.0 - fadeOut, EditorPalette.opacityFaint, EditorPalette.opacityOpaque); } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalRenderer.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalRenderer.cs index af09e970c..b3b110ed8 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalRenderer.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorModalRenderer.cs @@ -1,15 +1,13 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Numerics; - using Inno.Editor.Core; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Interactions; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui; - /// /// Renders centered editor modals consistently on the main viewport. /// @@ -48,38 +46,31 @@ EditorContext context ArgumentException.ThrowIfNullOrWhiteSpace(title); ArgumentNullException.ThrowIfNull(modal); ArgumentNullException.ThrowIfNull(context); - string popupId = $"{title}##{id}"; ImGuiViewport* viewport = NativeImGui.Raw.GetMainViewport(); Vector2 center = ImGui.FromNative(viewport->WorkPos) + ImGui.FromNative(viewport->WorkSize) * 0.5f; if (presentation.blocksInteraction) NativeImGui.OpenPopup(popupId, (int)(ImGuiPopupFlags.NoReopen)); NativeImGui.SetNextWindowViewport(viewport->ID); - ImGuiCond placementCondition = presentation.canMove || presentation.canResize - ? ImGuiCond.Appearing - : ImGuiCond.Always; + ImGuiCond placementCondition = presentation.canMove || presentation.canResize ? ImGuiCond.Appearing : ImGuiCond.Always; NativeImGui.SetNextWindowPos(center, (int)(placementCondition), new Vector2(0.5f, 0.5f)); Vector2 initialSize = presentation.initialSize; if (initialSize.X > 0f && initialSize.Y > 0f) { - NativeImGui.SetNextWindowSize( - initialSize * EditorWidget.style.zoom, - (int)(placementCondition)); + NativeImGui.SetNextWindowSize(initialSize * EditorUI.current.widgets.style.zoom, (int)(placementCondition)); } else if (!presentation.canResize) { - NativeImGui.SetNextWindowSize( - new Vector2(EditorWidget.style.modalWidth, 0f), - (int)(ImGuiCond.Always)); + NativeImGui.SetNextWindowSize(new Vector2(EditorUI.current.widgets.style.modalWidth, 0f), (int)(ImGuiCond.Always)); } + if (presentation.canResize) { Vector2 maximumSize = Vector2.Max(Vector2.One, ImGui.FromNative(viewport->WorkSize)); - Vector2 minimumSize = Vector2.Min( - presentation.minimumSize * EditorWidget.style.zoom, - maximumSize); + Vector2 minimumSize = Vector2.Min(presentation.minimumSize * EditorUI.current.widgets.style.zoom, maximumSize); ImGui.ConstrainNativeWindowSize(minimumSize, maximumSize, null, null); } + NativeImGui.PushStyleVar((int)(ImGuiStyleVar.Alpha), alpha); ImGuiWindowFlags flags = ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoCollapse; if (!presentation.allowScrolling) @@ -92,12 +83,19 @@ EditorContext context if (presentation.initialSize.X <= 0f || presentation.initialSize.Y <= 0f) flags |= ImGuiWindowFlags.AlwaysAutoResize; } + bool beganWindow = false; try { - bool visible = presentation.blocksInteraction - ? ImGui.BeginNativeWindow(popupId, flags, modal: true) - : ImGui.BeginNativeWindow(popupId, flags | ImGuiWindowFlags.NoSavedSettings, modal: false); + bool visible = presentation.blocksInteraction ? ImGui.BeginNativeWindow( + popupId, + flags, + modal: true + ) : ImGui.BeginNativeWindow( + popupId, + flags | ImGuiWindowFlags.NoSavedSettings, + modal: false + ); beganWindow = !presentation.blocksInteraction || visible; if (visible) { @@ -113,6 +111,7 @@ EditorContext context else NativeImGui.End(); } + NativeImGui.PopStyleVar(); } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs new file mode 100644 index 000000000..95581416c --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiInput.cs @@ -0,0 +1,60 @@ +using System.Numerics; +using Inno.Native.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Copies frame input facts used by presentation without exposing native input storage. +/// +public readonly struct EditorUiInput +{ + /// + /// Gets whether Alt is held. + /// + public bool alt { get; } + /// + /// Gets whether the physical Control modifier is held, independently of native shortcut conventions. + /// + public bool control { get; } + /// + /// Gets whether Shift is held. + /// + public bool shift { get; } + /// + /// Gets whether the physical Super modifier is held, independently of native shortcut conventions. + /// + public bool super { get; } + /// + /// Gets whether an active control owns text input. + /// + public bool wantsText { get; } + /// + /// Gets the current pointer movement in UI units. + /// + public Vector2 mouseDelta { get; } + /// + /// Gets the vertical scroll movement. + /// + public float wheel { get; } + /// + /// Gets the frame duration in seconds. + /// + public float deltaTime { get; } + /// + /// Gets the drawable pixel scale relative to logical UI coordinates. + /// + public Vector2 framebufferScale { get; } + + internal unsafe EditorUiInput(ImGuiIO* io) + { + alt = io->KeyAlt != 0; + bool swapsModifiers = io->ConfigMacOsxBehaviors != 0; + control = (swapsModifiers ? io->KeySuper : io->KeyCtrl) != 0; + shift = io->KeyShift != 0; + super = (swapsModifiers ? io->KeyCtrl : io->KeySuper) != 0; + wantsText = io->WantTextInput != 0; + mouseDelta = ImGui.FromNative(io->MouseDelta); + framebufferScale = ImGui.FromNative(io->DisplayFramebufferScale); + wheel = io->MouseWheel; + deltaTime = io->DeltaTime; + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs new file mode 100644 index 000000000..7b9add853 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/EditorUiSession.cs @@ -0,0 +1,230 @@ +using Inno.Core.Events; +using Inno.Editor.Interactions; +using EditorControls = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using System; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Owns editor controls and transient presentation state for one host, independently from other ImGui contexts. +/// +public sealed unsafe class EditorUiSession : IDisposable +{ + [ThreadStatic] + private static EditorUiSession? s_current; + private readonly int m_ownerThread = Environment.CurrentManagedThreadId; + private EventHub? m_lifecycleHub; + private Scope? m_scope; + private OwnerScope? m_ownerScope; + private nint m_nativeContext; + private int m_styleInputFrame = -1; + private bool m_disposed; + private string m_owner = "root"; + /// + /// Creates independent controls and style state; the presentation backend retains its native context ownership. + /// + public EditorUiSession() + { + widgets = new EditorControls(this); + menus = new EditorMenuRenderer(this); + } + + /// + /// Gets the current synchronous drawing scope; this accessor never creates or owns a session. + /// + /// + /// No UI drawing scope is active on the calling thread. + /// + public static EditorUiSession current => s_current ?? throw new InvalidOperationException("Editor drawing requires an active UI session scope."); + /// + /// Gets the controls whose transient state belongs exclusively to this session. + /// + public EditorControls widgets { get; } + + /// + /// Gets the menu presenter whose search state belongs exclusively to this session. + /// + [Inno.Scripting.Api.ScriptingApiIgnore] + public EditorMenuRenderer menus { get; } + /// + /// Gets whether this session has successfully retired its presentation state. + /// + public bool isDisposed => m_disposed; + internal string owner => m_owner; + + internal bool shouldDeferStyleChanges + { + get + { + EnsureOwner(); + ObjectDisposedException.ThrowIf(m_disposed, this); + if (m_nativeContext == 0) + return false; + ImGuiNativeContext* context = NativeImGui.Raw.GetCurrentContext(); + return context is null || (nint)context != m_nativeContext || context->WithinFrameScope != 0; + } + } + + /// + /// Activates this session for a synchronous callback while preserving the enclosing scope. + /// + /// + /// An owner-thread scope that must be released in reverse activation order. + /// + /// + /// The session has retired. + /// + /// + /// The caller is not the owner thread. + /// + public IDisposable Enter() + { + EnsureOwner(); + ObjectDisposedException.ThrowIf(m_disposed, this); + var scope = new Scope(this, s_current, m_scope); + m_scope = scope; + s_current = this; + return scope; + } + + /// + /// Releases presentation state after all drawing scopes and domain edit gestures have ended. + /// + /// + /// A drawing scope is active or the caller is not the owner thread. + /// + public void Dispose() + { + EnsureOwner(); + if (m_disposed) + return; + if (m_scope is not null) + throw new InvalidOperationException("The UI session still owns an active drawing scope."); + widgets.Retire(); + menus.Clear(); + m_lifecycleHub?.Dispose(); + m_lifecycleHub = null; + m_disposed = true; + GC.SuppressFinalize(this); + } + + internal void Connect(EditorInteractions interactions) + { + EnsureOwner(); + if (m_lifecycleHub is not null) + throw new InvalidOperationException("The UI session already belongs to an interaction runtime."); + m_lifecycleHub = interactions.events.CreateHub(int.MaxValue); + m_lifecycleHub.Listen(_ => widgets.CommitEdits()); + } + + internal IDisposable EnterOwner(string owner) + { + EnsureDrawing(); + ArgumentException.ThrowIfNullOrWhiteSpace(owner); + var scope = new OwnerScope(this, m_owner, m_ownerScope); + m_owner = owner; + m_ownerScope = scope; + return scope; + } + + internal void RetireOwner(string owner) + { + widgets.RetireOwner(owner); + menus.RetireOwner(owner); + } + + internal void EnsureDrawing() => _ = RequireContext(); + + internal void PrepareStyleInput() + { + ImGuiNativeContext* context = RequireContext(); + if (context->WithinFrameScope != 0) + { + if (m_styleInputFrame == context->FrameCount) + return; + m_styleInputFrame = context->FrameCount; + } + else + m_styleInputFrame = context->FrameCount + 1; + widgets.style.CommitPending(); + } + + internal void RequireFramePreparation() + { + if (RequireContext()->WithinFrameScope != 0) + throw new InvalidOperationException("Style preparation must run before native frame creation."); + } + + internal ImGuiNativeContext* RequireDrawingFrame() + { + ImGuiNativeContext* context = RequireContext(); + if (context->WithinFrameScope == 0 || context->CurrentWindow is null) + throw new InvalidOperationException("Drawing requires an active native frame and window."); + return context; + } + + private ImGuiNativeContext* RequireContext() + { + EnsureOwner(); + ObjectDisposedException.ThrowIf(m_disposed, this); + if (!ReferenceEquals(s_current, this)) + throw new InvalidOperationException("Controls must be drawn within their owning UI session scope."); + nint context = (nint)NativeImGui.Raw.GetCurrentContext(); + if (context == 0) + throw new InvalidOperationException("Editor controls require a live ImGui context."); + if (m_nativeContext == 0) + m_nativeContext = context; + else if (m_nativeContext != context) + throw new InvalidOperationException("The UI session belongs to a different ImGui context."); + return (ImGuiNativeContext*)context; + } + + private void EnsureOwner() + { + if (Environment.CurrentManagedThreadId != m_ownerThread) + throw new InvalidOperationException("Editor UI sessions must be used and retired on their owner thread."); + } + + private sealed class Scope( + EditorUiSession owner, + EditorUiSession? previous, + Scope? previousScope + ) : IDisposable + { + private bool m_disposed; + /// + public void Dispose() + { + owner.EnsureOwner(); + if (m_disposed) + return; + if (!ReferenceEquals(s_current, owner) || !ReferenceEquals(owner.m_scope, this)) + throw new InvalidOperationException("UI drawing scopes must retire in reverse activation order."); + owner.m_scope = previousScope; + s_current = previous; + m_disposed = true; + } + } + + private sealed class OwnerScope( + EditorUiSession session, + string previous, + OwnerScope? previousScope + ) : IDisposable + { + private bool m_disposed; + /// + public void Dispose() + { + if (m_disposed) + return; + session.EnsureOwner(); + if (!ReferenceEquals(session.m_ownerScope, this)) + throw new InvalidOperationException("UI owner scopes must retire in reverse activation order."); + session.m_owner = previous; + session.m_ownerScope = previousScope; + m_disposed = true; + } + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs index 221f7b09b..3b3f5f53d 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Runtime/ImGuiEditorRuntime.cs @@ -1,8 +1,8 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using System.Collections.Generic; using System.Diagnostics; using System.Numerics; - using Inno.Core.Events; using Inno.Core.Execution; using Inno.Core.Logging; @@ -10,13 +10,11 @@ using Inno.Extensibility.Types; using Inno.Editor.Core; using Inno.Editor.ImGui.ImGuiWidget; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using Inno.Editor.Interactions; using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui; - /// /// Presents the backend-independent editor interaction runtime through ImGui. /// @@ -24,11 +22,14 @@ public sealed unsafe class ImGuiEditorRuntime : EditorRuntime { private readonly Stopwatch m_timer = Stopwatch.StartNew(); private readonly EditorInteractionRuntime m_runtime; + private readonly EditorUiSession m_ui = new(); private readonly EditorModalHost m_modals = new(); + private readonly HashSet m_openOwners = new(StringComparer.Ordinal); + private readonly HashSet m_currentOwners = new(StringComparer.Ordinal); + private readonly List m_retiredOwners = []; private uint m_dockspaceId; private Vector2 m_dockspaceSize; private bool m_disposed; - /// /// Creates an ImGui editor runtime with stable host-owned extension services. /// @@ -45,7 +46,7 @@ public sealed unsafe class ImGuiEditorRuntime : EditorRuntime /// Services available to discovered extension constructors. /// /// - /// Thrown when , , or + /// Thrown when , , or /// is . /// public ImGuiEditorRuntime( @@ -53,20 +54,25 @@ public ImGuiEditorRuntime( TypeCatalog types, LogRouter logs, IEnumerable hostServices - ) - : base(context ?? throw new ArgumentNullException(nameof(context))) + ) : base(context ?? throw new ArgumentNullException(nameof(context))) { ArgumentNullException.ThrowIfNull(types); ArgumentNullException.ThrowIfNull(logs); ArgumentNullException.ThrowIfNull(hostServices); - m_runtime = new EditorInteractionRuntime(context, types, logs, hostServices); + var services = new List(hostServices) + { + m_ui + }; + m_runtime = new EditorInteractionRuntime(context, types, logs, services); + m_ui.Connect(m_runtime.interactions); + using IDisposable scope = m_ui.Enter(); + m_ui.widgets.SetupStyle(); } /// /// Gets the active presentation-independent interaction entry point. /// public EditorInteractions interactions => m_runtime.interactions; - /// /// Gets the number of active dockable panels. /// @@ -75,7 +81,11 @@ IEnumerable hostServices /// /// Starts value processing after validating the current state. /// - public override void Start() => m_runtime.Start(); + public override void Start() + { + using IDisposable scope = m_ui.Enter(); + m_runtime.Start(); + } /// /// Recomputes owned state from the current validated inputs. @@ -83,7 +93,11 @@ IEnumerable hostServices /// /// The frame consumed by update; ownership remains with the caller unless explicitly stated otherwise. /// - public override void Update(EditorFrame frame) => m_runtime.Update(frame); + public override void Update(EditorFrame frame) + { + using IDisposable scope = m_ui.Enter(); + m_runtime.Update(frame); + } /// /// Captures all stateful active modules and panels and flushes their project state to disk. @@ -93,6 +107,7 @@ IEnumerable hostServices /// public void SaveState() { + using IDisposable uiScope = m_ui.Enter(); ObjectDisposedException.ThrowIf(m_disposed, this); m_runtime.SaveState(); } @@ -110,21 +125,41 @@ public void SaveState() /// public void PrepareShutdown() { + using IDisposable uiScope = m_ui.Enter(); ObjectDisposedException.ThrowIf(m_disposed, this); + m_ui.widgets.CommitEdits(); m_runtime.PrepareShutdown(); } + /// + /// Applies pending style inputs with the owned context before the backend creates its native frame. + /// + /// + /// This runtime has retired. + /// + /// + /// The owning native context is unavailable, incorrect, or already drawing. + /// + public void PrepareFrame() + { + using IDisposable uiScope = m_ui.Enter(); + ObjectDisposedException.ThrowIf(m_disposed, this); + m_ui.RequireFramePreparation(); + m_ui.widgets.ApplyPendingStyle(); + } + /// /// Draws the complete editor frame through ImGui. /// public void Draw() { + using IDisposable uiScope = m_ui.Enter(); ObjectDisposedException.ThrowIf(m_disposed, this); - EditorWidget.ApplyPendingStyle(); + m_ui.widgets.BeginEditingFrame(); + EditorUI.current.widgets.ApplyPendingStyle(); IReadOnlyList modals = m_runtime.modals; double now = m_timer.Elapsed.TotalSeconds; bool blocksInteraction = m_modals.Update(modals, now); - if (blocksInteraction) NativeImGui.BeginDisabled(true); try @@ -132,7 +167,7 @@ public void Draw() // The main menu must reserve the viewport work area before the dockspace snapshots it. // Reversing this order makes the dockspace overlap the bottom of the menu and clip the // first docked tab bar row. - EditorMenuRenderer.MainMenu(interactions.For(ImGuiInteractionIds.C_MAIN_MENU_AREA)); + EditorUI.current.menus.MainMenu(interactions.For(ImGuiInteractionIds.C_MAIN_MENU_AREA)); DrawDockSpace(); DrawPanels(m_runtime.panels); } @@ -142,6 +177,7 @@ public void Draw() NativeImGui.EndDisabled(); } + m_ui.widgets.CompleteEditingFrame(); m_runtime.Flush(); // Menu actions can change modal visibility after the pre-draw transition update. modals = m_runtime.modals; @@ -162,9 +198,7 @@ private void DrawDockSpace() if (loaded != null) ResizeDockSplits(loaded, size, style->DockingSeparatorSize, ImGui.FromNative(style->WindowMinSize)); } - else if (m_dockspaceSize != Vector2.Zero - && (MathF.Abs(size.X - m_dockspaceSize.X) >= 0.5f - || MathF.Abs(size.Y - m_dockspaceSize.Y) >= 0.5f)) + else if (m_dockspaceSize != Vector2.Zero && (MathF.Abs(size.X - m_dockspaceSize.X) >= 0.5f || MathF.Abs(size.Y - m_dockspaceSize.Y) >= 0.5f)) { ImGuiDockNode* root = NativeImGui.Raw.DockBuilderGetNode(m_dockspaceId); if (root != null) @@ -183,12 +217,14 @@ private void DrawDockSpace() { NativeImGui.PopStyleColor(2); } + if (m_dockspaceSize == Vector2.Zero) { ImGuiDockNode* root = NativeImGui.Raw.DockBuilderGetNode(m_dockspaceId); if (root != null) ResizeDockSplits(root, size, style->DockingSeparatorSize, ImGui.FromNative(style->WindowMinSize)); } + m_dockspaceSize = size; } @@ -200,27 +236,23 @@ Vector2 minimumWindowSize ) { ImGuiDockNode* first = node->ChildNodes[0]; ImGuiDockNode* second = node->ChildNodes[1]; - if (first == null || second == null - || node->SplitAxis is not (ImGuiAxis.X or ImGuiAxis.Y)) + if (first == null || second == null || node->SplitAxis is not (ImGuiAxis.X or ImGuiAxis.Y)) return; bool horizontal = node->SplitAxis == ImGuiAxis.X; float firstReference = horizontal ? first->Size.X : first->Size.Y; float secondReference = horizontal ? second->Size.X : second->Size.Y; float previousAvailable = (horizontal ? node->Size.X : node->Size.Y) - separator; - if (firstReference <= 0f || secondReference <= 0f - || MathF.Abs(firstReference + secondReference - previousAvailable) > MathF.Max(1f, separator)) + if (firstReference <= 0f || secondReference <= 0f || MathF.Abs(firstReference + secondReference - previousAvailable) > MathF.Max(1f, separator)) { firstReference = horizontal ? first->SizeRef.X : first->SizeRef.Y; secondReference = horizontal ? second->SizeRef.X : second->SizeRef.Y; } + float available = (horizontal ? size.X : size.Y) - separator; - if (!float.IsFinite(firstReference) || !float.IsFinite(secondReference) - || firstReference <= 0f || secondReference <= 0f || available <= 1f) + if (!float.IsFinite(firstReference) || !float.IsFinite(secondReference) || firstReference <= 0f || secondReference <= 0f || available <= 1f) return; - float minimum = MathF.Min(available * 0.5f, - horizontal ? minimumWindowSize.X : minimumWindowSize.Y); - float firstExtent = Math.Clamp(available * firstReference / (firstReference + secondReference), - minimum, available - minimum); + float minimum = MathF.Min(available * 0.5f, horizontal ? minimumWindowSize.X : minimumWindowSize.Y); + float firstExtent = Math.Clamp(available * firstReference / (firstReference + secondReference), minimum, available - minimum); Vector2 firstSize = size; Vector2 secondSize = size; if (horizontal) @@ -233,6 +265,7 @@ Vector2 minimumWindowSize firstSize.Y = firstExtent; secondSize.Y = available - firstExtent; } + first->SizeRef = ImGui.ToNative(firstSize); second->SizeRef = ImGui.ToNative(secondSize); ResizeDockSplits(first, firstSize, separator, minimumWindowSize); @@ -247,6 +280,7 @@ Vector2 minimumWindowSize /// public void HandleKeyPressed(KeyPressedEvent keyEvent) { + using IDisposable uiScope = m_ui.Enter(); ArgumentNullException.ThrowIfNull(keyEvent); if (m_modals.Update(m_runtime.modals, m_timer.Elapsed.TotalSeconds)) return; @@ -255,6 +289,7 @@ public void HandleKeyPressed(KeyPressedEvent keyEvent) HotKeyGesture save = HotKeyGesture.Primary(KeyCode.S, context.keyboard); if (NativeImGui.Raw.GetIO()->WantTextInput != 0 && (keyEvent.key != save.key || keyEvent.modifiers != save.modifiers)) return; + m_ui.widgets.CommitEdits(); m_runtime.HandleKeyPressed(keyEvent); } @@ -271,7 +306,13 @@ public override void Dispose() m_modals.Clear(); try { - m_runtime.Dispose(); + using (m_ui.Enter()) + { + m_ui.widgets.CommitEdits(); + m_runtime.Dispose(); + } + + m_ui.Dispose(); } catch (Exception pendingRetirement) when (RetirementPendingException.Find(pendingRetirement) is not null) { @@ -282,19 +323,28 @@ public override void Dispose() m_disposed = true; throw; } + m_disposed = true; GC.SuppressFinalize(this); } private void DrawPanels(IReadOnlyList panels) { + m_currentOwners.Clear(); for (int i = 0; i < panels.Count; i++) { EditorPanelExtension extension = panels[i]; - if (!extension.isOpen || !extension.TryGetWindowPresentation( - out bool useWindowPadding, - out bool allowScrolling, - out Vector2 initialSize)) + m_currentOwners.Add(extension.id); + if (!extension.isOpen) + { + if (m_openOwners.Remove(extension.id)) + m_ui.RetireOwner(extension.id); + continue; + } + + m_openOwners.Add(extension.id); + using IDisposable ownerScope = m_ui.EnterOwner(extension.id); + if (!extension.TryGetWindowPresentation(out bool useWindowPadding, out bool allowScrolling, out Vector2 initialSize)) continue; bool isOpen = extension.isOpen; if (initialSize.X > 0 && initialSize.Y > 0) @@ -304,17 +354,26 @@ private void DrawPanels(IReadOnlyList panels) flags |= ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; if (extension.TakeFocusRequest()) NativeImGui.SetNextWindowFocus(); - EditorWidget.PanelWindow(extension.title, ref isOpen, () => + EditorUI.current.widgets.PanelWindow(extension.title, ref isOpen, () => { - if (extension.Draw(context) && - NativeImGui.IsWindowFocused((int)(Inno.Native.ImGui.ImGuiFocusedFlags.RootAndChildWindows))) + if (extension.Draw(context) && NativeImGui.IsWindowFocused((int)(Inno.Native.ImGui.ImGuiFocusedFlags.RootAndChildWindows))) { - interactions.For( - $"panel/{extension.id}", - interactions.selection.selectedTarget).Focus(); + interactions.For($"panel/{extension.id}", interactions.selection.selectedTarget).Focus(); } }, flags, useWindowPadding); extension.isOpen = isOpen; } + + m_retiredOwners.Clear(); + foreach (string owner in m_openOwners) + if (!m_currentOwners.Contains(owner)) + m_retiredOwners.Add(owner); + foreach (string owner in m_retiredOwners) + { + m_ui.RetireOwner(owner); + m_openOwners.Remove(owner); + } + + m_retiredOwners.Clear(); } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs index d28bf94fc..5dfe8ceca 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorPalette.cs @@ -100,21 +100,6 @@ public static unsafe class EditorPalette /// public static Vector4 warning { get; } = new(0.9f, 0.65f, 0.25f, opacityOpaque); - /// - /// Gets the neutral Shader node heading surface. - /// - public static Vector4 shaderNodeHeader { get; } = new(0.16f, 0.17f, 0.20f, opacityOpaque); - - /// - /// Gets the orange heading surface shared by Shader input nodes. - /// - public static Vector4 shaderInputNodeHeader { get; } = new(0.43f, 0.245f, 0.085f, opacityOpaque); - - /// - /// Gets the purple heading surface shared by Shader output nodes. - /// - public static Vector4 shaderOutputNodeHeader { get; } = new(0.25f, 0.19f, 0.34f, opacityOpaque); - /// /// Gets the editor window background. /// @@ -296,79 +281,44 @@ public static unsafe class EditorPalette public static Vector4 modalDim { get; } = new(0.8f, 0.8f, 0.8f, opacityMuted); /// - /// Gets inspector card header background. - /// - public static Vector4 inspectorCardHeader { get; } = new(0.17f, 0.18f, 0.205f, opacityOpaque); - - /// - /// Gets the persistent Inspector target header background. - /// - public static Vector4 inspectorTargetHeader { get; } = new(0.14f, 0.14f, 0.16f, opacityOpaque); - - /// - /// Gets the persistent Inspector target header border. - /// - public static Vector4 inspectorTargetHeaderBorder { get; } = new(0.30f, 0.29f, 0.34f, opacityOpaque); - - /// - /// Gets the subdued background of Tag labels in Inspector target headers. - /// - public static Vector4 inspectorTagLabel { get; } = new(0.34f, 0.29f, 0.44f, opacityOpaque); - - /// - /// Gets the subdued background of Layer labels in Inspector target headers. - /// - public static Vector4 inspectorLayerLabel { get; } = new(0.24f, 0.34f, 0.40f, opacityOpaque); - - /// - /// Gets inspector card body background. + /// Gets card header background. /// - public static Vector4 inspectorCardBody { get; } = new(0.12f, 0.12f, 0.14f, opacityOpaque); + public static Vector4 cardHeader { get; } = new(0.17f, 0.18f, 0.205f, opacityOpaque); /// - /// Gets inspector card body border. + /// Gets the persistent target header background. /// - public static Vector4 inspectorCardBodyBorder { get; } = new(0.28f, 0.27f, 0.32f, opacityOpaque); + public static Vector4 targetHeader { get; } = new(0.14f, 0.14f, 0.16f, opacityOpaque); /// - /// Gets the outline color of framed Inspector sections. + /// Gets the persistent target header border. /// - public static Vector4 inspectorSectionBorder { get; } = new(0.34f, 0.33f, 0.39f, opacityOpaque); + public static Vector4 targetHeaderBorder { get; } = new(0.30f, 0.29f, 0.34f, opacityOpaque); /// - /// Gets the accent used by numeric Shader type badges. + /// Gets card body background. /// - public static Vector4 shaderTypeNumeric { get; } = new(0.53f, 0.72f, 0.96f, opacityOpaque); + public static Vector4 cardBody { get; } = new(0.12f, 0.12f, 0.14f, opacityOpaque); /// - /// Gets the accent used by integer Shader type badges. + /// Gets card body border. /// - public static Vector4 shaderTypeInteger { get; } = new(0.58f, 0.82f, 0.72f, opacityOpaque); + public static Vector4 cardBodyBorder { get; } = new(0.28f, 0.27f, 0.32f, opacityOpaque); /// - /// Gets the accent used by Boolean Shader type badges. + /// Gets the outline color of framed sections. /// - public static Vector4 shaderTypeBoolean { get; } = new(0.91f, 0.69f, 0.38f, opacityOpaque); + public static Vector4 sectionBorder { get; } = new(0.34f, 0.33f, 0.39f, opacityOpaque); /// - /// Gets the accent used by Shader resource type badges. + /// Gets disabled card text. /// - public static Vector4 shaderTypeResource { get; } = new(0.58f, 0.78f, 0.86f, opacityOpaque); + public static Vector4 cardDisabledText { get; } = new(0.52f, 0.52f, 0.54f, opacityOpaque); /// - /// Gets the accent used by unclassified Shader type badges. + /// Gets card disclosure hover background. /// - public static Vector4 shaderTypeOther { get; } = new(0.72f, 0.66f, 0.84f, opacityOpaque); - - /// - /// Gets disabled inspector card text. - /// - public static Vector4 inspectorCardDisabledText { get; } = new(0.52f, 0.52f, 0.54f, opacityOpaque); - - /// - /// Gets inspector disclosure hover background. - /// - public static Vector4 inspectorCardDisclosureHovered { get; } = new(0.24f, 0.22f, 0.31f, opacityOpaque); + public static Vector4 cardDisclosureHovered { get; } = new(0.24f, 0.22f, 0.31f, opacityOpaque); /// /// Gets compact control hover color. @@ -391,49 +341,39 @@ public static unsafe class EditorPalette public static Vector4 collectionRowAlternate { get; } = new(0.135f, 0.144f, 0.160f, opacityOpaque); /// - /// Gets the translucent primary background of a complete Settings field. - /// - public static Vector4 settingsField { get; } = new(0.36f, 0.36f, 0.38f, opacityTrace); - - /// - /// Gets the translucent alternate background of a complete Settings field. - /// - public static Vector4 settingsFieldAlternate { get; } = new(0.52f, 0.52f, 0.56f, opacityTrace); - - /// - /// Gets the asset browser field background. + /// Gets the content field background. /// - public static Vector4 assetField { get; } = new(0.235f, 0.22f, 0.27f, opacityOpaque); + public static Vector4 contentField { get; } = new(0.235f, 0.22f, 0.27f, opacityOpaque); /// - /// Gets the asset browser border. + /// Gets the content border. /// - public static Vector4 assetBorder { get; } = new(0.31f, 0.30f, 0.35f, opacityOpaque); + public static Vector4 contentBorder { get; } = new(0.31f, 0.30f, 0.35f, opacityOpaque); /// - /// Gets the soft asset browser border. + /// Gets the soft content border. /// - public static Vector4 assetBorderSoft { get; } = new(0.24f, 0.24f, 0.27f, opacityOpaque); + public static Vector4 contentBorderSoft { get; } = new(0.24f, 0.24f, 0.27f, opacityOpaque); /// - /// Gets the asset browser text color. + /// Gets the content text color. /// - public static Vector4 assetText { get; } = new(0.86f, 0.86f, 0.86f, opacityOpaque); + public static Vector4 contentText { get; } = new(0.86f, 0.86f, 0.86f, opacityOpaque); /// - /// Gets the muted asset browser text color. + /// Gets the muted content text color. /// - public static Vector4 assetTextMuted { get; } = new(0.54f, 0.54f, 0.56f, opacityOpaque); + public static Vector4 contentTextMuted { get; } = new(0.54f, 0.54f, 0.56f, opacityOpaque); /// - /// Gets the subdued text color used by asset browser breadcrumb paths. + /// Gets the subdued contextual text color used by paths and origin hints. /// - public static Vector4 assetBreadcrumbText { get; } = new(0.86f, 0.86f, 0.86f, opacityMedium); + public static Vector4 contextText { get; } = new(0.86f, 0.86f, 0.86f, opacityMedium); /// - /// Gets the opaque asset browser accent. + /// Gets the opaque content accent. /// - public static Vector4 assetAccent { get; } = new(0.50f, 0.45f, 0.62f, opacityOpaque); + public static Vector4 contentAccent { get; } = new(0.50f, 0.45f, 0.62f, opacityOpaque); /// /// Gets a hover treatment derived from a base theme color. @@ -458,17 +398,7 @@ public static unsafe class EditorPalette public static Vector4 GetActive(Vector4 color) => Lerp(color, text, 0.24f); /// - /// Gets scene row background. - /// - public static Vector4 hierarchySceneRow => title; - - /// - /// Gets inactive hierarchy text. - /// - public static Vector4 hierarchyInactiveText { get; } = new(0.52f, 0.52f, 0.54f, opacityOpaque); - - /// - /// Gets hierarchy tree guide color. + /// Gets the guide color of reusable tree controls. /// public static Vector4 treeGuide { get; } = new(0.62f, 0.62f, 0.66f, opacityOpaque); @@ -492,99 +422,6 @@ public static unsafe class EditorPalette /// public static Vector4 axisW { get; } = new(0.48f, 0.48f, 0.50f, opacityOpaque); - /// - /// Gets debug log color. - /// - public static Vector4 logDebug { get; } = new(0.80f, 0.90f, 0.85f, opacityOpaque); - - /// - /// Gets informational log color. - /// - public static Vector4 logInfo { get; } = new(0.20f, 1f, 0.20f, opacityOpaque); - - /// - /// Gets warning log color. - /// - public static Vector4 logWarning { get; } = new(1f, 1f, 0.20f, opacityOpaque); - - /// - /// Gets error log color. - /// - public static Vector4 logError { get; } = new(1f, 0.20f, 0.20f, opacityOpaque); - - /// - /// Gets fatal log color. - /// - public static Vector4 logFatal { get; } = new(1f, 0.20f, 1f, opacityOpaque); - - /// - /// Gets collapsed log card background. - /// - public static Vector4 logCollapsedCard { get; } = new(0.61960787f, 0.5764706f, 0.76862746f, opacityMuted); - - /// - /// Gets collapsed log card border. - /// - public static Vector4 logCollapsedBorder { get; } = new(0.32f, 0.34f, 0.37f, opacityMedium); - - /// - /// Gets the base expanded log card background. - /// - public static Vector4 logExpandedBase { get; } = new(0.10f, 0.10f, 0.10f, opacityOpaque); - - /// - /// Gets the base expanded log card border. - /// - public static Vector4 logExpandedBorderBase { get; } = new(0.24f, 0.24f, 0.24f, opacityOpaque); - - /// - /// Gets log header button background. - /// - public static Vector4 logToggle { get; } = new(0.61960787f, 0.5764706f, 0.76862746f, opacityNone); - - /// - /// Gets hovered log header button background. - /// - public static Vector4 logToggleHovered { get; } = new(0.7372549f, 0.69411767f, 0.8862745f, opacityMedium); - - /// - /// Gets active log header button background. - /// - public static Vector4 logToggleActive { get; } = new(0.8156863f, 0.77254903f, 0.9647059f, opacityMedium); - - /// - /// Gets the expanded log card background derived from a severity color. - /// - /// - /// The palette color representing the log severity. - /// - /// - /// The standard expanded-card base blended with the severity color. - /// - public static Vector4 GetLogExpandedCard(Vector4 severityColor) => Lerp(logExpandedBase, severityColor, 0.12f); - - /// - /// Gets the expanded log card border derived from a severity color. - /// - /// - /// The palette color representing the log severity. - /// - /// - /// The standard expanded-border base blended with the severity color. - /// - public static Vector4 GetLogExpandedBorder(Vector4 severityColor) => Lerp(logExpandedBorderBase, severityColor, 0.20f); - - /// - /// Gets the separator color used inside an expanded log card. - /// - /// - /// The resolved background color of the expanded card. - /// - /// - /// The card color blended toward the palette text color. - /// - public static Vector4 GetLogSeparator(Vector4 cardColor) => Lerp(cardColor, text, 0.12f); - /// /// Linearly interpolates two palette colors using a clamped amount. /// diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs index 24eb4bb96..03af0b790 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorStyleMetrics.cs @@ -4,8 +4,13 @@ namespace Inno.Editor.ImGui; /// -/// Defines named editor layout metrics used by widgets and feature panels. +/// Defines reusable layout metrics and separates owned frame values from requested style changes. /// +/// +/// Standalone instances apply changes immediately. Session-owned instances defer changes requested +/// during native drawing until the next style preparation. They are mutable only on the owner thread +/// and must not be modified after their session retires. +/// public sealed class EditorStyleMetrics { /// @@ -23,11 +28,25 @@ public sealed class EditorStyleMetrics /// public const float C_ZOOM_STEP = 0.10f; + private readonly WeakReference? m_owner; + private float m_requestedZoom = 1f; + private bool m_requestedCompactMode; private float m_zoom = 1f; private bool m_compactMode; /// - /// Gets the current editor UI zoom multiplier. + /// Creates independent metrics whose changes apply immediately without a native context. + /// + public EditorStyleMetrics() { } + + internal EditorStyleMetrics(EditorUiSession owner) + { + ArgumentNullException.ThrowIfNull(owner); + m_owner = new(owner); + } + + /// + /// Gets the effective zoom used by the current presentation frame. /// public float zoom => m_zoom; @@ -37,66 +56,87 @@ public sealed class EditorStyleMetrics public bool isCompact => m_compactMode; /// - /// Switches between comfortable and compact editor density. + /// Gets the scaled height of a compact sampled-value trend plot. + /// + public float trendHeight => Scale(64f); + + /// + /// Requests comfortable or compact density without changing an active session frame. /// /// /// for compact density. /// /// - /// when the density changed. + /// when the requested density changed; owned drawing applies it at the next frame preparation. /// + /// + /// The owning session has retired or is no longer available. + /// + /// + /// The caller is not the owning session's thread. + /// public bool SetCompactMode(bool value) { - if (m_compactMode == value) - return false; - m_compactMode = value; - return true; + bool defer = ShouldDeferChanges(); + bool changed = m_requestedCompactMode != value; + m_requestedCompactMode = value; + if (!defer) + CommitPending(); + return changed; } /// - /// Sets the editor UI zoom after clamping it to the supported range. + /// Requests a bounded zoom without changing metrics partway through an owned native frame. /// /// /// The requested UI zoom multiplier. /// /// - /// when the effective zoom changed. + /// when the requested zoom changed; owned drawing applies it at the next frame preparation. /// /// /// Thrown when is not finite. /// + /// + /// The owning session has retired or is no longer available. + /// + /// + /// The caller is not the owning session's thread. + /// public bool SetZoom(float value) { if (!float.IsFinite(value)) throw new ArgumentOutOfRangeException(nameof(value), value, "UI zoom must be finite."); + bool defer = ShouldDeferChanges(); float clamped = Math.Clamp(value, C_MIN_ZOOM, C_MAX_ZOOM); - if (MathF.Abs(m_zoom - clamped) < 0.0001f) - return false; - m_zoom = clamped; - return true; + bool changed = MathF.Abs(m_requestedZoom - clamped) >= 0.0001f; + m_requestedZoom = clamped; + if (!defer) + CommitPending(); + return changed; } /// /// Increases editor UI zoom by one bounded increment. /// /// - /// when the zoom changed. + /// when the requested zoom changed. /// - public bool ZoomIn() => SetZoom(m_zoom + C_ZOOM_STEP); + public bool ZoomIn() => SetZoom(m_requestedZoom + C_ZOOM_STEP); /// /// Decreases editor UI zoom by one bounded increment. /// /// - /// when the zoom changed. + /// when the requested zoom changed. /// - public bool ZoomOut() => SetZoom(m_zoom - C_ZOOM_STEP); + public bool ZoomOut() => SetZoom(m_requestedZoom - C_ZOOM_STEP); /// /// Restores the baseline editor UI zoom. /// /// - /// when the zoom changed. + /// when the requested zoom changed. /// public bool ResetZoom() => SetZoom(1f); @@ -206,11 +246,6 @@ public bool SetZoom(float value) /// public Vector2 compactItemSpacing => ScaleVector(new(4f, 2f)); - /// - /// Gets hierarchy item spacing. - /// - public Vector2 hierarchyItemSpacing => ScaleVector(new(3f, 2f)); - /// /// Gets standard inner item spacing. /// @@ -221,31 +256,6 @@ public bool SetZoom(float value) /// public Vector2 cellPadding => ScaleVector(m_compactMode ? new(3f, 1f) : new(5f, 3f)); - /// - /// Gets the inner padding applied to one complete Settings field. - /// - public Vector2 settingsFieldPadding => ScaleVector(new(8f, 6f)); - - /// - /// Gets asset browser window padding. - /// - public Vector2 assetWindowPadding => ScaleVector(new(2f, 1f)); - - /// - /// Gets asset browser cell padding. - /// - public Vector2 assetCellPadding => ScaleVector(new(5f, 2f)); - - /// - /// Gets asset browser item spacing. - /// - public Vector2 assetItemSpacing => ScaleVector(new(2f, 2f)); - - /// - /// Gets asset browser frame rounding. - /// - public float assetFrameRounding => Scale(1f); - /// /// Gets tree indentation. /// @@ -272,79 +282,44 @@ public bool SetZoom(float value) public float grabMinimumSize => Scale(m_compactMode ? 10f : 12f); /// - /// Gets the minimum visible width retained for either asset browser pane while its splitter is dragged. - /// - public float assetPaneMinimumVisibleWidth => Scale(16f); - - /// - /// Gets the minimum draggable asset browser splitter width. - /// - public float assetSplitterMinimumWidth => Scale(2f); - - /// - /// Gets asset breadcrumb bar height. - /// - public float assetBreadcrumbHeight => Scale(25f); - - /// - /// Gets minimum asset grid cell size. - /// - public float assetGridMinimumCellSize => Scale(32f); - - /// - /// Gets asset grid cell padding. - /// - public float assetGridCellPadding => Scale(2f); - - /// - /// Gets default asset grid scale. - /// - public float assetGridDefaultScale => 3f; - - /// - /// Gets minimum asset grid scale. - /// - public float assetGridMinimumScale => 1f; - - /// - /// Gets maximum asset grid scale. + /// Gets the minimum visible width retained for either resizable content pane while its splitter is dragged. /// - public float assetGridMaximumScale => 10f; + public float paneMinimumVisibleWidth => Scale(16f); /// - /// Gets spacing between asset list rows. + /// Gets the minimum draggable resizable content splitter width. /// - public float assetListRowSpacing => Scale(2f); + public float splitterMinimumWidth => Scale(2f); /// - /// Gets spacing between inspector cards. + /// Gets spacing between cards. /// - public float inspectorCardSpacing => Scale(3f); + public float cardSpacing => Scale(3f); /// - /// Gets the inner padding of the persistent Inspector target header. + /// Gets the inner padding of the persistent target header. /// - public Vector2 inspectorTargetHeaderPadding => ScaleVector(new(6f, 4f)); + public Vector2 targetHeaderPadding => ScaleVector(new(6f, 4f)); /// - /// Gets spacing between the two rows of the persistent Inspector target header. + /// Gets spacing between the two rows of the persistent target header. /// - public float inspectorTargetHeaderRowSpacing => Scale(1f); + public float targetHeaderRowSpacing => Scale(1f); /// - /// Gets the font-size multiplier used by the large Inspector target icon. + /// Gets the font-size multiplier used by the large target icon. /// - public float inspectorTargetIconScale => 1.65f; + public float targetIconScale => 1.65f; /// - /// Gets inspector card body padding. + /// Gets card body padding. /// - public Vector2 inspectorCardBodyPadding => ScaleVector(new(7f, 5f)); + public Vector2 cardBodyPadding => ScaleVector(new(7f, 5f)); /// - /// Gets inspector card header padding. + /// Gets card header padding. /// - public Vector2 inspectorCardHeaderPadding => ScaleVector(new(4f, 1f)); + public Vector2 cardHeaderPadding => ScaleVector(new(4f, 1f)); /// /// Gets the shared padding around separator-style section titles. @@ -354,17 +329,17 @@ public bool SetZoom(float value) /// /// Gets the inner padding of a framed Inspector section. /// - public Vector2 inspectorSectionPadding => ScaleVector(new(7f, 5f)); + public Vector2 sectionPadding => ScaleVector(new(7f, 5f)); /// /// Gets the gap between an Inspector section legend and its interrupted top border. /// - public float inspectorSectionLegendGap => itemSpacing.X; + public float sectionLegendGap => itemSpacing.X; /// /// Gets the length of each centered end cap on a collapsed Inspector section. /// - public float inspectorCollapsedSectionCapLength => Scale(10f); + public float collapsedSectionCapLength => Scale(10f); /// /// Gets the spacing around the separator between inline metadata and its value. @@ -374,15 +349,15 @@ public bool SetZoom(float value) /// /// Gets the vertical distance after a framed Inspector section. /// - public float inspectorSectionSpacing => Scale(4f); + public float sectionSpacing => Scale(4f); /// /// Gets the subtle corner rounding used by framed content sections inside editor containers. /// - public float inspectorSectionRounding => Scale(3f); + public float sectionRounding => Scale(3f); /// - /// Gets inspector disclosure inset. + /// Gets card disclosure inset. /// public float disclosureButtonInset => Scale(2f); @@ -391,16 +366,6 @@ public bool SetZoom(float value) /// public float propertyLabelRatio => 0.40f; - /// - /// Gets the distance from the bottom treated as auto-scroll. - /// - public float logAutoScrollTolerance => Scale(1f); - - /// - /// Gets log disclosure padding. - /// - public Vector2 logDisclosurePadding => ScaleVector(new(6f, 1f)); - /// /// Gets the default width of searchable editor popups. /// @@ -419,7 +384,7 @@ public bool SetZoom(float value) /// /// Gets spacing between leading controls inside card headers. /// - public float inspectorHeaderControlSpacing => Scale(4f); + public float headerControlSpacing => Scale(4f); /// /// Gets the inner padding of compact colored label chips. @@ -447,119 +412,29 @@ public bool SetZoom(float value) public float typeBadgeSpacing => Scale(6f); /// - /// Gets the vertical padding inside one statistics row. + /// Gets spacing between distinct control groups in the target header row. /// - public float statisticRowPadding => Scale(3f); + public float headerSectionSpacing => Scale(10f); /// - /// Gets spacing between distinct control groups in an Inspector target header row. + /// Gets top padding above trailing add buttons. /// - public float inspectorHeaderSectionSpacing => Scale(10f); + public float addButtonTopPadding => Scale(7f); /// - /// Gets top padding above inspector add buttons. + /// Gets the tight resizable content toolbar spacing. /// - public float inspectorAddButtonTopPadding => Scale(7f); + public float toolbarTightSpacing => Scale(2f); /// - /// Gets the minimum inline hierarchy rename width. + /// Gets the regular resizable content toolbar spacing. /// - public float hierarchyRenameMinimumWidth => Scale(48f); + public float toolbarSpacing => Scale(4f); /// - /// Gets the hierarchy rename trailing-control gap. + /// Gets spacing between resizable content toolbar sections. /// - public float hierarchyRenameTrailingGap => Scale(8f); - - /// - /// Gets the minimum height of the hierarchy blank drop area. - /// - public float hierarchyBlankMinimumHeight => Scale(24f); - - /// - /// Gets the tight asset browser toolbar spacing. - /// - public float assetToolbarTightSpacing => Scale(2f); - - /// - /// Gets the regular asset browser toolbar spacing. - /// - public float assetToolbarSpacing => Scale(4f); - - /// - /// Gets spacing between asset browser toolbar sections. - /// - public float assetToolbarSectionSpacing => Scale(5f); - - /// - /// Gets the default normalized position of the asset list name/type separator. - /// - public float assetListNameSeparatorPosition => 0.4f; - - /// - /// Gets the default normalized position of the asset list type/source separator. - /// - public float assetListTypeSeparatorPosition => 0.7f; - - /// - /// Gets the minimum normalized width reserved for each asset list column. - /// - public float assetListMinimumColumnRatio => 0.1f; - - /// - /// Gets the horizontal hit width of an asset list column separator. - /// - public float assetListSeparatorHitWidth => Scale(8f); - - /// - /// Gets the horizontal inset between an asset list separator and column content. - /// - public float assetListContentHorizontalPadding => Scale(6f); - - /// - /// Gets horizontal padding for asset grid labels. - /// - public float assetGridLabelHorizontalPadding => Scale(6f); - - /// - /// Gets bottom padding for asset grid labels. - /// - public float assetGridLabelBottomPadding => Scale(3f); - - /// - /// Gets the additional vertical spacing between asset grid label lines. - /// - public float assetGridLabelLineSpacing => Scale(-2f); - - /// - /// Gets fixed padding added to calculated asset grid cells. - /// - public float assetGridFixedCellPadding => Scale(8f); - - /// - /// Gets the scale bias added to asset grid icons. - /// - public float assetGridScaleBias => 2f; - - /// - /// Gets the top inset reserved above an asset grid icon. - /// - public float assetGridIconTopPadding => Scale(6f); - - /// - /// Gets the horizontal inset that constrains an asset grid icon inside its card. - /// - public float assetGridIconHorizontalPadding => Scale(6f); - - /// - /// Gets the vertical spacing between an asset grid icon and its label. - /// - public float assetGridIconLabelSpacing => Scale(4f); - - /// - /// Gets spacing around breadcrumb separators. - /// - public float assetBreadcrumbSpacing => Scale(4f); + public float toolbarSectionSpacing => Scale(5f); /// /// Gets the minimum width of one axis value field. @@ -626,6 +501,21 @@ public bool SetZoom(float value) /// public float textDecorationOffset => Scale(2f); + internal void CommitPending() + { + m_zoom = m_requestedZoom; + m_compactMode = m_requestedCompactMode; + } + + private bool ShouldDeferChanges() + { + if (m_owner is null) + return false; + if (!m_owner.TryGetTarget(out EditorUiSession? owner)) + throw new ObjectDisposedException(nameof(EditorUiSession)); + return owner.shouldDeferStyleChanges; + } + private float Scale(float value) => value * m_zoom; private Vector2 ScaleVector(Vector2 value) => value * m_zoom; diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs new file mode 100644 index 000000000..f26d83e9c --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Styling/EditorUiLayout.cs @@ -0,0 +1,54 @@ +using System.Numerics; +using Inno.Native.ImGui; + +namespace Inno.Editor.ImGui; +/// +/// Copies the presentation metrics needed by custom layouts without retaining native style memory. +/// +public readonly struct EditorUiLayout +{ + /// + /// Gets spacing between neighboring controls. + /// + public Vector2 itemSpacing { get; } + /// + /// Gets the current window inset. + /// + public Vector2 windowPadding { get; } + /// + /// Gets table cell padding. + /// + public Vector2 cellPadding { get; } + /// + /// Gets spacing between parts of a compound control. + /// + public Vector2 itemInnerSpacing { get; } + /// + /// Gets padding inside a control. + /// + public Vector2 framePadding { get; } + /// + /// Gets dock splitter thickness. + /// + public float dockingSeparatorSize { get; } + /// + /// Gets scrollbar width. + /// + public float scrollbarSize { get; } + /// + /// Gets control corner radius. + /// + public float frameRounding { get; } + + internal unsafe EditorUiLayout(ImGuiStyle* style) + { + itemSpacing = ImGui.FromNative(style->ItemSpacing); + windowPadding = ImGui.FromNative(style->WindowPadding); + cellPadding = ImGui.FromNative(style->CellPadding); + itemInnerSpacing = ImGui.FromNative(style->ItemInnerSpacing); + framePadding = ImGui.FromNative(style->FramePadding); + dockingSeparatorSize = style->DockingSeparatorSize; + scrollbarSize = style->ScrollbarSize; + frameRounding = style->FrameRounding; + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/EditorIconCatalog.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/EditorIconCatalog.cs new file mode 100644 index 000000000..511dc310f --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/EditorIconCatalog.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Inno.Editor.ImGui; +/// +/// Describes the host's complete standard glyph vocabulary as immutable presentation values. +/// +public static class EditorIconCatalog +{ + /// + /// Gets all named glyphs supplied by the installed UI font, in deterministic name order. + /// + public static IReadOnlyList<(string Name, string Glyph)> standard { get; } = System.Array.AsReadOnly(typeof(Inno.Adapter.Presentation.ImGui.ImGuiIcon).GetFields(BindingFlags.Public | BindingFlags.Static).Where(static field => field.IsLiteral && field.FieldType == typeof(string)).Select(static field => (field.Name, (string)field.GetRawConstantValue()!)).OrderBy(static value => value.Name, System.StringComparer.Ordinal).ToArray()); +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs new file mode 100644 index 000000000..f65210df6 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiFont.cs @@ -0,0 +1,102 @@ +using System; +using NativeImGui = Inno.Native.ImGui.ImGui; +using HostFont = Inno.Adapter.Presentation.ImGui.ImGuiFont; +using HostStyle = Inno.Adapter.Presentation.ImGui.ImGuiFontStyle; + +namespace Inno.Editor.ImGui; +/// +/// Chooses composable font emphasis while leaving actual font ownership with the presentation host. +/// +[Flags] +public enum ImGuiFontStyle +{ + /// + /// Uses the ordinary face. + /// + Regular = 0, + /// + /// Emphasizes text with the bold face. + /// + Bold = 1, + /// + /// Emphasizes text with the italic face. + /// + Italic = 2 +} + +/// +/// Uses the host's context-owned font catalog without exposing backend types to Editor features. +/// +public static unsafe class ImGuiFont +{ + /// + /// Checks whether the current host registered an exact face. + /// + /// + /// The requested emphasis. + /// + /// + /// True when the exact face is available. + /// + public static bool IsAvailable(ImGuiFontStyle style) + { + EditorUiSession.current.EnsureDrawing(); + return HostFont.IsAvailable((HostStyle)style); + } + + /// + /// Pushes a host-owned font face for one synchronous control group. + /// + /// + /// The requested emphasis. + /// + /// + /// An owner-window/frame scope; the host may use a registered nearest face. + /// + public static ImGuiFontScope PushStyle(ImGuiFontStyle style) + { + EditorUiSession session = EditorUiSession.current; + session.EnsureDrawing(); + var scope = HostFont.PushStyle((HostStyle)style); + return new ImGuiFontScope(session, scope, NativeImGui.GetFrameCount(), NativeImGui.Raw.GetCurrentContext()->FontStack.Size); + } +} + +/// +/// Restores a font push only in its original session, frame and native stack position. +/// +public unsafe ref struct ImGuiFontScope : IDisposable +{ + private EditorUiSession? m_session; + private Inno.Adapter.Presentation.ImGui.ImGuiFontScope m_scope; + private readonly int m_frame; + private readonly int m_depth; + internal ImGuiFontScope( + EditorUiSession session, + Inno.Adapter.Presentation.ImGui.ImGuiFontScope scope, + int frame, + int depth + ) { + m_session = session; + m_scope = scope; + m_frame = frame; + m_depth = depth; + } + + /// + /// Restores the preceding font after validating scope order; default and retired scopes are inert. + /// + /// + /// The scope is used in another context, frame or stack position. + /// + public void Dispose() + { + if (m_session is null) + return; + m_session.EnsureDrawing(); + if (NativeImGui.GetFrameCount() != m_frame || NativeImGui.Raw.GetCurrentContext()->FontStack.Size != m_depth) + throw new InvalidOperationException("Font scopes must retire in their original frame and reverse stack order."); + m_scope.Dispose(); + m_session = null; + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs new file mode 100644 index 000000000..42c966df8 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiIcon.cs @@ -0,0 +1,179 @@ +namespace Inno.Editor.ImGui; +/// +/// Provides the standard Editor glyph vocabulary; extension controls may also supply their own glyph text. +/// +public static class ImGuiIcon +{ + /// + /// Gets the angle left glyph used by standard Editor controls. + /// + public const string AngleLeft = Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleLeft; + /// + /// Gets the angle right glyph used by standard Editor controls. + /// + public const string AngleRight = Inno.Adapter.Presentation.ImGui.ImGuiIcon.AngleRight; + /// + /// Gets the arrow rotate left glyph used by standard Editor controls. + /// + public const string ArrowRotateLeft = Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowRotateLeft; + /// + /// Gets the arrows up down left right glyph used by standard Editor controls. + /// + public const string ArrowsUpDownLeftRight = Inno.Adapter.Presentation.ImGui.ImGuiIcon.ArrowsUpDownLeftRight; + /// + /// Gets the bug glyph used by standard Editor controls. + /// + public const string Bug = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Bug; + /// + /// Gets the camera glyph used by standard Editor controls. + /// + public const string Camera = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Camera; + /// + /// Gets the circle info glyph used by standard Editor controls. + /// + public const string CircleInfo = Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleInfo; + /// + /// Gets the circle question glyph used by standard Editor controls. + /// + public const string CircleQuestion = Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleQuestion; + /// + /// Gets the circle xmark glyph used by standard Editor controls. + /// + public const string CircleXmark = Inno.Adapter.Presentation.ImGui.ImGuiIcon.CircleXmark; + /// + /// Gets the cube glyph used by standard Editor controls. + /// + public const string Cube = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cube; + /// + /// Gets the cubes glyph used by standard Editor controls. + /// + public const string Cubes = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Cubes; + /// + /// Gets the dice d6 glyph used by standard Editor controls. + /// + public const string DiceD6 = Inno.Adapter.Presentation.ImGui.ImGuiIcon.DiceD6; + /// + /// Gets the eye glyph used by standard Editor controls. + /// + public const string Eye = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Eye; + /// + /// Gets the eye slash glyph used by standard Editor controls. + /// + public const string EyeSlash = Inno.Adapter.Presentation.ImGui.ImGuiIcon.EyeSlash; + /// + /// Gets the file glyph used by standard Editor controls. + /// + public const string File = Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + /// + /// Gets the file code glyph used by standard Editor controls. + /// + public const string FileCode = Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileCode; + /// + /// Gets the file lines glyph used by standard Editor controls. + /// + public const string FileLines = Inno.Adapter.Presentation.ImGui.ImGuiIcon.FileLines; + /// + /// Gets the folder glyph used by standard Editor controls. + /// + public const string Folder = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Folder; + /// + /// Gets the forward step glyph used by standard Editor controls. + /// + public const string ForwardStep = Inno.Adapter.Presentation.ImGui.ImGuiIcon.ForwardStep; + /// + /// Gets the gears glyph used by standard Editor controls. + /// + public const string Gears = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Gears; + /// + /// Gets the globe glyph used by standard Editor controls. + /// + public const string Globe = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Globe; + /// + /// Gets the grip vertical glyph used by standard Editor controls. + /// + public const string GripVertical = Inno.Adapter.Presentation.ImGui.ImGuiIcon.GripVertical; + /// + /// Gets the layer group glyph used by standard Editor controls. + /// + public const string LayerGroup = Inno.Adapter.Presentation.ImGui.ImGuiIcon.LayerGroup; + /// + /// Gets the lightbulb glyph used by standard Editor controls. + /// + public const string Lightbulb = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lightbulb; + /// + /// Gets the lock glyph used by standard Editor controls. + /// + public const string Lock = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Lock; + /// + /// Gets the lock open glyph used by standard Editor controls. + /// + public const string LockOpen = Inno.Adapter.Presentation.ImGui.ImGuiIcon.LockOpen; + /// + /// Gets the magnifying glass glyph used by standard Editor controls. + /// + public const string MagnifyingGlass = Inno.Adapter.Presentation.ImGui.ImGuiIcon.MagnifyingGlass; + /// + /// Gets the palette glyph used by standard Editor controls. + /// + public const string Palette = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Palette; + /// + /// Gets the pause glyph used by standard Editor controls. + /// + public const string Pause = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pause; + /// + /// Gets the pen glyph used by standard Editor controls. + /// + public const string Pen = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Pen; + /// + /// Gets the play glyph used by standard Editor controls. + /// + public const string Play = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Play; + /// + /// Gets the plus glyph used by standard Editor controls. + /// + public const string Plus = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Plus; + /// + /// Gets the rotate glyph used by standard Editor controls. + /// + public const string Rotate = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Rotate; + /// + /// Gets the rupee sign glyph used by standard Editor controls. + /// + public const string RupeeSign = Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupeeSign; + /// + /// Gets the rupiah sign glyph used by standard Editor controls. + /// + public const string RupiahSign = Inno.Adapter.Presentation.ImGui.ImGuiIcon.RupiahSign; + /// + /// Gets the shader glyph used by standard Editor controls. + /// + public const string Shader = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Shader; + /// + /// Gets the skull crossbones glyph used by standard Editor controls. + /// + public const string SkullCrossbones = Inno.Adapter.Presentation.ImGui.ImGuiIcon.SkullCrossbones; + /// + /// Gets the stop glyph used by standard Editor controls. + /// + public const string Stop = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Stop; + /// + /// Gets the triangle exclamation glyph used by standard Editor controls. + /// + public const string TriangleExclamation = Inno.Adapter.Presentation.ImGui.ImGuiIcon.TriangleExclamation; + /// + /// Gets the up down left right glyph used by standard Editor controls. + /// + public const string UpDownLeftRight = Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpDownLeftRight; + /// + /// Gets the up right and down left from center glyph used by standard Editor controls. + /// + public const string UpRightAndDownLeftFromCenter = Inno.Adapter.Presentation.ImGui.ImGuiIcon.UpRightAndDownLeftFromCenter; + /// + /// Gets the vector square glyph used by standard Editor controls. + /// + public const string VectorSquare = Inno.Adapter.Presentation.ImGui.ImGuiIcon.VectorSquare; + /// + /// Gets the xmark glyph used by standard Editor controls. + /// + public const string Xmark = Inno.Adapter.Presentation.ImGui.ImGuiIcon.Xmark; +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiUtf8Buffer.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiUtf8Buffer.cs index 4f9dd1545..9d8bab8b6 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiUtf8Buffer.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/TextInput/ImGuiUtf8Buffer.cs @@ -1,12 +1,12 @@ using System; using System.Text; +using System.Buffers; +using System.Text.Unicode; using BGCS.Runtime; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui; - internal static unsafe class ImGuiUtf8Buffer { internal static bool InputText( @@ -19,25 +19,29 @@ ImGuiInputTextFlags flags ArgumentException.ThrowIfNullOrWhiteSpace(label); if (capacity is 0 or > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(capacity)); - - byte[] buffer = new byte[(int)capacity]; - Encoding.UTF8.GetEncoder().Convert( - value.AsSpan(), - buffer.AsSpan(0, buffer.Length - 1), - flush: true, - out _, - out int bytesUsed, - out _); - buffer[bytesUsed] = 0; - - bool changed; - using NativeStringLease nativeLabel = Utf8Codec.EncodeNullTerminated(label, allowsNull: false); - using NativeStringLease nativeHint = Utf8Codec.EncodeNullTerminated(hint, allowsNull: true); - fixed (byte* nativeBuffer = buffer) + const ImGuiInputTextFlags callbacks = ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackAlways | ImGuiInputTextFlags.CallbackCharFilter | ImGuiInputTextFlags.CallbackResize | ImGuiInputTextFlags.CallbackEdit; + if ((flags & callbacks) != 0) + throw new ArgumentException("Managed text controls do not accept native callback flags.", nameof(flags)); + int length = (int)capacity; + byte[]? rented = length > 1024 ? ArrayPool.Shared.Rent(length) : null; + Span buffer = rented is null ? stackalloc byte[length] : rented.AsSpan(0, length); + try { - changed = hint is null - ? NativeImGui.Raw.InputText((sbyte*)nativeLabel.pointer, (sbyte*)nativeBuffer, (uint)capacity, (int)flags, null, null) != 0 - : NativeImGui.Raw.InputTextWithHint( + _ = Utf8.FromUtf16(value.AsSpan(), buffer[..^1], out _, out int bytesUsed, replaceInvalidSequences: true, isFinalBlock: true); + buffer[bytesUsed] = 0; + bool changed; + using NativeStringLease nativeLabel = Utf8Codec.EncodeNullTerminated(label, allowsNull: false); + using NativeStringLease nativeHint = Utf8Codec.EncodeNullTerminated(hint, allowsNull: true); + fixed (byte* nativeBuffer = buffer) + { + changed = hint is null ? NativeImGui.Raw.InputText( + (sbyte*)nativeLabel.pointer, + (sbyte*)nativeBuffer, + (uint)capacity, + (int)flags, + null, + null + ) != 0 : NativeImGui.Raw.InputTextWithHint( (sbyte*)nativeLabel.pointer, (sbyte*)nativeHint.pointer, (sbyte*)nativeBuffer, @@ -46,13 +50,20 @@ ImGuiInputTextFlags flags null, null ) != 0; - } + } + + if (changed) + { + int terminator = buffer.IndexOf((byte)0); + value = Encoding.UTF8.GetString(buffer[..(terminator < 0 ? buffer.Length : terminator)]); + } - if (changed) + return changed; + } + finally { - int terminator = Array.IndexOf(buffer, (byte)0); - value = Encoding.UTF8.GetString(buffer, 0, terminator < 0 ? buffer.Length : terminator); + if (rented is not null) + ArrayPool.Shared.Return(rented, clearArray: true); } - return changed; } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs index 5be096be9..d99726228 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Card.cs @@ -1,15 +1,13 @@ using System; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { /// /// Draws a component-style collapsible card header. @@ -41,7 +39,7 @@ public static unsafe partial class ImGuiWidget /// /// when card content should be drawn; otherwise, . /// - public static bool CollapsingCard( + public bool CollapsingCard( string id, string title, Action? drawLeadingControl = null, @@ -51,26 +49,22 @@ public static bool CollapsingCard( float trailingControlWidth = 0f, Action? drawContextMenu = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(title); if (trailingControlWidth < 0f) { - throw new ArgumentOutOfRangeException( - nameof(trailingControlWidth), - trailingControlWidth, - "Trailing control width cannot be negative."); + throw new ArgumentOutOfRangeException(nameof(trailingControlWidth), trailingControlWidth, "Trailing control width cannot be negative."); } - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.SpanAvailWidth - | ImGuiTreeNodeFlags.AllowOverlap - | ImGuiTreeNodeFlags.OpenOnArrow; + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.SpanAvailWidth | ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.OpenOnArrow; if (defaultOpen) flags |= ImGuiTreeNodeFlags.DefaultOpen; - - NativeImGui.PushStyleColor((int)(ImGuiCol.Header), EditorPalette.inspectorCardHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderHovered), EditorPalette.inspectorCardHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderActive), EditorPalette.inspectorCardHeader); + NativeImGui.PushStyleColor((int)(ImGuiCol.Header), EditorPalette.cardHeader); + NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderHovered), EditorPalette.cardHeader); + NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderActive), EditorPalette.cardHeader); NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.transparent); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.inspectorCardHeaderPadding); + NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.cardHeaderPadding); Vector2 itemHeaderMin; Vector2 headerMin; Vector2 headerMax; @@ -86,7 +80,7 @@ public static bool CollapsingCard( NativeImGui.Raw.GetWindowDrawList(), persistentHeaderMin, persistentHeaderMax, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorCardHeader), + NativeImGui.ColorConvertFloat4ToU32(EditorPalette.cardHeader), 0f, 0 ); @@ -100,7 +94,7 @@ public static bool CollapsingCard( NativeImGui.Raw.GetWindowDrawList(), headerMin, headerMax, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorCardBodyBorder), + NativeImGui.ColorConvertFloat4ToU32(EditorPalette.cardBodyBorder), 0f, (int)(ImDrawFlags.RoundCornersNone), style.borderSize @@ -111,27 +105,17 @@ public static bool CollapsingCard( NativeImGui.PopStyleVar(); NativeImGui.PopStyleColor(4); } + try { drawContextMenu?.Invoke(); - float contentX = itemHeaderMin.X + NativeImGui.GetTreeNodeToLabelSpacing(); - DrawDisclosureIndicator( - new Vector2(itemHeaderMin.X, headerMin.Y), - new Vector2(contentX, headerMax.Y), - open, - dimmed); - float contentY = headerMin.Y + MathF.Max( - 0f, - (headerMax.Y - headerMin.Y - NativeImGui.GetFrameHeight()) * 0.5f); - float trailingWidth = drawTrailingControl is null - ? 0f - : trailingControlWidth > 0f - ? trailingControlWidth - : NativeImGui.GetFrameHeight(); + DrawDisclosureIndicator(new Vector2(itemHeaderMin.X, headerMin.Y), new Vector2(contentX, headerMax.Y), open, dimmed); + float contentY = headerMin.Y + MathF.Max(0f, (headerMax.Y - headerMin.Y - NativeImGui.GetFrameHeight()) * 0.5f); + float trailingWidth = drawTrailingControl is null ? 0f : trailingControlWidth > 0f ? trailingControlWidth : NativeImGui.GetFrameHeight(); float titleRight = headerMax.X - trailingWidth; if (drawTrailingControl is not null) - titleRight -= style.inspectorHeaderControlSpacing; + titleRight -= style.headerControlSpacing; NativeImGui.SetCursorScreenPos(new Vector2(contentX, contentY)); bool dimmedColorPushed = false; bool groupStarted = false; @@ -139,26 +123,23 @@ public static bool CollapsingCard( { if (dimmed) { - NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.inspectorCardDisabledText); + NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.cardDisabledText); dimmedColorPushed = true; } + NativeImGui.BeginGroup(); groupStarted = true; if (drawLeadingControl is not null) { drawLeadingControl(); - NativeImGui.SameLine(0f, style.inspectorHeaderControlSpacing); + NativeImGui.SameLine(0f, style.headerControlSpacing); } + NativeImGui.AlignTextToFramePadding(); Vector2 titleCursor = NativeImGui.GetCursorScreenPos(); float titleWidth = MathF.Max(1f, titleRight - titleCursor.X); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - ImGui.PushNativeClipRectangle( - drawList, - titleCursor, - new Vector2(titleCursor.X + titleWidth, headerMax.Y), - 1 - ); + ImGui.PushNativeClipRectangle(drawList, titleCursor, new Vector2(titleCursor.X + titleWidth, headerMax.Y), 1); try { ImGui.DrawNativeText(drawList, titleCursor, NativeImGui.GetColorU32((int)(ImGuiCol.Text)), title); @@ -167,15 +148,13 @@ public static bool CollapsingCard( { NativeImGui.Raw.PopClipRect(drawList); } + NativeImGui.Dummy(new Vector2(titleWidth, NativeImGui.GetFrameHeight())); NativeImGui.EndGroup(); groupStarted = false; - if (drawTrailingControl is not null) { - NativeImGui.SetCursorScreenPos(new Vector2( - MathF.Max(contentX, headerMax.X - trailingWidth), - contentY)); + NativeImGui.SetCursorScreenPos(new Vector2(MathF.Max(contentX, headerMax.X - trailingWidth), contentY)); drawTrailingControl(); } } @@ -187,6 +166,7 @@ public static bool CollapsingCard( NativeImGui.PopStyleColor(); NativeImGui.SetCursorScreenPos(contentCursor); } + return open; } catch @@ -212,27 +192,25 @@ public static bool CollapsingCard( /// /// Whether the indicator uses the inactive text color. /// - public static void DrawDisclosureIndicator( + public void DrawDisclosureIndicator( Vector2 min, Vector2 max, bool open, bool dimmed = false ) { + m_session.EnsureDrawing(); Vector2 availableSize = Vector2.Max(Vector2.Zero, max - min); - float buttonSize = MathF.Max( - 1f, - MathF.Min(availableSize.X, availableSize.Y) - style.disclosureButtonInset * 2f); + float buttonSize = MathF.Max(1f, MathF.Min(availableSize.X, availableSize.Y) - style.disclosureButtonInset * 2f); Vector2 buttonMin = min + (availableSize - new Vector2(buttonSize)) * 0.5f; Vector2 buttonMax = buttonMin + new Vector2(buttonSize); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - if (!IsPopupBlockingInteraction() && - NativeImGui.IsMouseHoveringRect(buttonMin, buttonMax)) + if (!IsPopupBlockingInteraction() && NativeImGui.IsMouseHoveringRect(buttonMin, buttonMax)) { ImGui.DrawNativeFilledRectangle( drawList, buttonMin, buttonMax, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorCardDisclosureHovered), + NativeImGui.ColorConvertFloat4ToU32(EditorPalette.cardDisclosureHovered), NativeImGui.Raw.GetStyle()->FrameRounding, 0 ); @@ -241,10 +219,7 @@ public static void DrawDisclosureIndicator( string indicator = open ? "▼" : "▶"; Vector2 indicatorSize = NativeImGui.CalcTextSize(indicator); Vector2 indicatorPosition = buttonMin + (new Vector2(buttonSize) - indicatorSize) * 0.5f; - uint color = NativeImGui.ColorConvertFloat4ToU32( - dimmed - ? EditorPalette.inspectorCardDisabledText - : EditorPalette.text); + uint color = NativeImGui.ColorConvertFloat4ToU32(dimmed ? EditorPalette.cardDisabledText : EditorPalette.text); ImGui.DrawNativeText(drawList, indicatorPosition, color, indicator); } @@ -260,31 +235,25 @@ public static void DrawDisclosureIndicator( /// /// Whether content is visually disabled and non-interactive. /// - public static void CardBody( + public void CardBody( string id, Action drawContent, bool dimmed = false ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(drawContent); - Vector2 originalCursor = NativeImGui.GetCursorScreenPos(); (float cardLeft, float cardRight) = GetFullWidthCardBounds(originalCursor); NativeImGui.SetCursorScreenPos(new Vector2(cardLeft, originalCursor.Y)); - NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), EditorPalette.inspectorCardBody); - NativeImGui.PushStyleColor((int)(ImGuiCol.Border), EditorPalette.inspectorCardBodyBorder); + NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), EditorPalette.cardBody); + NativeImGui.PushStyleColor((int)(ImGuiCol.Border), EditorPalette.cardBodyBorder); NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameRounding), NativeImGui.Raw.GetStyle()->FrameRounding); NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameBorderSize), style.borderSize); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.inspectorCardBodyPadding); + NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.cardBodyPadding); ImGuiChildFlags childFlags = ImGuiChildFlags.FrameStyle | ImGuiChildFlags.AutoResizeY; - ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoSavedSettings; - bool visible = NativeImGui.BeginChild( - $"##card_body_{id}", - new Vector2(MathF.Max(1f, cardRight - cardLeft), 0f), - (int)(childFlags), - (int)(windowFlags)); + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; + bool visible = NativeImGui.BeginChild($"##card_body_{id}", new Vector2(MathF.Max(1f, cardRight - cardLeft), 0f), (int)(childFlags), (int)(windowFlags)); bool disabled = false; try { @@ -293,6 +262,7 @@ public static void CardBody( NativeImGui.BeginDisabled(true); disabled = true; } + if (visible) drawContent(); } @@ -308,7 +278,7 @@ public static void CardBody( } } - private static (float left, float right) GetFullWidthCardBounds(Vector2 cursor) + private (float left, float right) GetFullWidthCardBounds(Vector2 cursor) { float horizontalPadding = NativeImGui.Raw.GetCurrentWindow()->WindowPadding.X; float left = cursor.X - horizontalPadding; diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Clipping.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Clipping.cs new file mode 100644 index 000000000..a4d40f645 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Clipping.cs @@ -0,0 +1,79 @@ +using System; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui.ImGuiWidget; + +/// +/// Submits fixed-height content through the native clipper while retaining explicitly active rows. +/// +public sealed unsafe partial class ImGuiWidget +{ + /// + /// Draws visible row ranges and preserves native scrolling, navigation and table cursor bookkeeping. + /// + /// + /// Call-scoped drawing data, passed directly so static callbacks need no captured closure. + /// + /// + /// The nonnegative count in the caller's current filtered and sorted view. + /// + /// + /// The complete fixed row advance in screen pixels, or -1 to measure the first row through the native clipper. + /// + /// + /// Data borrowed only for this call; it is never stored by the widget. + /// + /// + /// A synchronous drawing callback receiving inclusive start and exclusive end indices. + /// + /// + /// Optional rows that must still submit an active edit, drag source or requested focus. + /// + /// + /// The drawing callback is null. + /// + /// + /// A count, row height or preserved index is invalid. + /// + /// + /// No native frame is active in this UI session. + /// + public void ClippedRows( + int itemCount, + float itemHeight, + TState state, + Action drawRange, + ReadOnlySpan preservedRows = default + ) { + m_session.EnsureDrawing(); + ArgumentNullException.ThrowIfNull(drawRange); + if (itemCount < 0) + throw new ArgumentOutOfRangeException(nameof(itemCount)); + if (!float.IsFinite(itemHeight) || (itemHeight <= 0f && itemHeight != -1f)) + throw new ArgumentOutOfRangeException(nameof(itemHeight)); + foreach (int row in preservedRows) + if (row < 0 || row >= itemCount) + throw new ArgumentOutOfRangeException(nameof(preservedRows)); + if (NativeImGui.Raw.GetCurrentContext()->WithinFrameScope == 0) + throw new InvalidOperationException("Row clipping requires an active native frame."); + if (itemCount == 0) + return; + ImGuiListClipper* clipper = NativeImGui.Raw.ImGuiListClipper(); + if (clipper == null) + throw new OutOfMemoryException("The native row clipper could not be allocated."); + try + { + NativeImGui.Raw.Begin(clipper, itemCount, itemHeight); + foreach (int row in preservedRows) + NativeImGui.Raw.IncludeItemByIndex(clipper, row); + while (NativeImGui.Raw.Step(clipper) != 0) + drawRange(state, clipper->DisplayStart, clipper->DisplayEnd); + } + finally + { + // The official destructor ends an unfinished clipper, including callback failures. + NativeImGui.Raw.Destroy(clipper); + } + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs index 98d609ea5..d29200af9 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.ContextMenu.cs @@ -2,20 +2,17 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { private const float C_TOOLTIP_MINIMUM_WIDTH = 300f; private const float C_TOOLTIP_WRAP_WIDTH = 440f; - /// /// Begins an explicitly opened popup using the editor context-menu presentation contract. /// The popup stays in its parent viewport, sizes itself to submitted content, and scrolls when @@ -31,13 +28,13 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when is empty. /// - public static bool BeginMenuPopup(string id) + public bool BeginMenuPopup(string id) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); PushContextMenuStyle(); NativeImGui.SetNextWindowViewport(NativeImGui.Raw.GetWindowViewport()->ID); - ImGuiWindowFlags flags = ImGuiWindowFlags.AlwaysAutoResize | - ImGuiWindowFlags.NoSavedSettings; + ImGuiWindowFlags flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings; if (NativeImGui.BeginPopup(id, (int)(flags))) return true; PopContextMenuStyle(); @@ -47,8 +44,9 @@ public static bool BeginMenuPopup(string id) /// /// Ends a popup opened by and restores the previous style. /// - public static void EndMenuPopup() + public void EndMenuPopup() { + m_session.EnsureDrawing(); NativeImGui.EndPopup(); PopContextMenuStyle(); } @@ -60,8 +58,9 @@ public static void EndMenuPopup() /// when tooltip content should be submitted; otherwise, /// . /// - public static bool BeginMenuTooltip() + public bool BeginMenuTooltip() { + m_session.EnsureDrawing(); PushContextMenuStyle(); NativeImGui.SetNextWindowViewport(NativeImGui.Raw.GetWindowViewport()->ID); if (NativeImGui.BeginTooltip()) @@ -73,8 +72,9 @@ public static bool BeginMenuTooltip() /// /// Ends a tooltip opened by and restores the previous style. /// - public static void EndMenuTooltip() + public void EndMenuTooltip() { + m_session.EnsureDrawing(); NativeImGui.EndTooltip(); PopContextMenuStyle(); } @@ -85,10 +85,10 @@ public static void EndMenuTooltip() /// /// Tooltip text. Empty values do not draw a tooltip. /// - public static void DrawItemTooltip(string? text) + public void DrawItemTooltip(string? text) { - if (string.IsNullOrWhiteSpace(text) || - !NativeImGui.IsItemHovered((int)(ImGuiHoveredFlags.AllowWhenDisabled))) + m_session.EnsureDrawing(); + if (string.IsNullOrWhiteSpace(text) || !NativeImGui.IsItemHovered((int)(ImGuiHoveredFlags.AllowWhenDisabled))) { return; } @@ -102,11 +102,11 @@ public static void DrawItemTooltip(string? text) /// /// Tooltip contents. The caller owns hit testing; empty text draws nothing. /// - public static void DrawTooltip(string? text) + public void DrawTooltip(string? text) { + m_session.EnsureDrawing(); if (string.IsNullOrWhiteSpace(text)) return; - ImGuiViewport* viewport = NativeImGui.Raw.GetWindowViewport(); Vector2 margin = new(6f * style.zoom); Vector2 available = Vector2.Max(Vector2.One, ImGui.FromNative(viewport->WorkSize) - margin * 2f); @@ -114,9 +114,7 @@ public static void DrawTooltip(string? text) float outerWidth = MathF.Min(C_TOOLTIP_WRAP_WIDTH * style.zoom, available.X); float innerWidth = MathF.Max(1f, outerWidth - padding.X * 2f - style.menuBorderSize * 2f); Vector2 textSize = NativeImGui.CalcTextSize(text, false, innerWidth); - outerWidth = MathF.Min(outerWidth, MathF.Max( - MathF.Min(C_TOOLTIP_MINIMUM_WIDTH * style.zoom, available.X), - textSize.X + padding.X * 2f + style.menuBorderSize * 2f)); + outerWidth = MathF.Min(outerWidth, MathF.Max(MathF.Min(C_TOOLTIP_MINIMUM_WIDTH * style.zoom, available.X), textSize.X + padding.X * 2f + style.menuBorderSize * 2f)); innerWidth = MathF.Max(1f, outerWidth - padding.X * 2f - style.menuBorderSize * 2f); textSize = NativeImGui.CalcTextSize(text, false, innerWidth); Vector2 size = new(outerWidth, MathF.Min(available.Y, textSize.Y + padding.Y * 2f + style.menuBorderSize * 2f)); @@ -160,8 +158,9 @@ public static void DrawTooltip(string? text) /// /// when context-menu content should be drawn; otherwise, . /// - public static bool BeginContextMenu(string id) + public bool BeginContextMenu(string id) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); PushContextMenuStyle(); NativeImGui.SetNextWindowViewport(NativeImGui.Raw.GetWindowViewport()->ID); @@ -181,56 +180,45 @@ public static bool BeginContextMenu(string id) /// /// when context-menu content should be drawn; otherwise, . /// - public static bool BeginWindowContextMenu(string id) + public bool BeginWindowContextMenu(string id) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); PushContextMenuStyle(); NativeImGui.SetNextWindowViewport(NativeImGui.Raw.GetWindowViewport()->ID); ConstrainContextMenu(); - if (NativeImGui.BeginPopupContextWindow( - id, - (int)(ImGuiPopupFlags.MouseButtonRight | ImGuiPopupFlags.NoOpenOverItems))) + if (NativeImGui.BeginPopupContextWindow(id, (int)(ImGuiPopupFlags.MouseButtonRight | ImGuiPopupFlags.NoOpenOverItems))) { return true; } + PopContextMenuStyle(); return false; } /// - /// Ends a context menu opened by or . + /// Ends a context menu opened by or . /// - public static void EndContextMenu() + public void EndContextMenu() { + m_session.EnsureDrawing(); EndMenuPopup(); } - private static bool IsPopupBlockingInteraction() - => NativeImGui.IsPopupOpen( - string.Empty, - (int)(ImGuiPopupFlags.AnyPopupId | ImGuiPopupFlags.AnyPopupLevel)); - - private static void ConstrainContextMenu() + private bool IsPopupBlockingInteraction() => ImGui.IsAnyPopupOpen(); + private void ConstrainContextMenu() { Vector2 workSize = ImGui.FromNative(NativeImGui.Raw.GetWindowViewport()->WorkSize); - float minimumWidth = MathF.Min( - workSize.X, - style.searchPopupWidth + style.menuWindowPadding.X * 2f + style.menuBorderSize * 2f); - SetMenuPopupSizeConstraints( - new Vector2(minimumWidth, 0f), - Vector2.Max(workSize, Vector2.One)); + float minimumWidth = MathF.Min(workSize.X, style.searchPopupWidth + style.menuWindowPadding.X * 2f + style.menuBorderSize * 2f); + SetMenuPopupSizeConstraints(new Vector2(minimumWidth, 0f), Vector2.Max(workSize, Vector2.One)); } - private static unsafe void SetMenuPopupSizeConstraints( + private unsafe void SetMenuPopupSizeConstraints( Vector2 minimum, Vector2 maximum ) { - Vector2 pixelMaximum = Vector2.Max(Vector2.One, new Vector2( - MathF.Floor(maximum.X), - MathF.Floor(maximum.Y))); - Vector2 pixelMinimum = Vector2.Min(pixelMaximum, new Vector2( - MathF.Ceiling(minimum.X), - MathF.Ceiling(minimum.Y))); + Vector2 pixelMaximum = Vector2.Max(Vector2.One, new Vector2(MathF.Floor(maximum.X), MathF.Floor(maximum.Y))); + Vector2 pixelMinimum = Vector2.Min(pixelMaximum, new Vector2(MathF.Ceiling(minimum.X), MathF.Ceiling(minimum.Y))); ImGui.ConstrainNativeWindowSize(pixelMinimum, pixelMaximum, &RoundMenuPopupSize, null); } @@ -239,12 +227,10 @@ private static unsafe void RoundMenuPopupSize(ImGuiSizeCallbackData* data) { // ImGui truncates constrained sizes. Round content upward within integer bounds so // fractional padding cannot turn a fully fitting menu into a scrolling window. - data->DesiredSize = ImGui.ToNative(new Vector2( - MathF.Ceiling(data->DesiredSize.X), - MathF.Ceiling(data->DesiredSize.Y))); + data->DesiredSize = ImGui.ToNative(new Vector2(MathF.Ceiling(data->DesiredSize.X), MathF.Ceiling(data->DesiredSize.Y))); } - private static void PushContextMenuStyle() + private void PushContextMenuStyle() { NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.menuText); NativeImGui.PushStyleColor((int)(ImGuiCol.PopupBg), EditorPalette.menuBackground); @@ -260,7 +246,7 @@ private static void PushContextMenuStyle() NativeImGui.PushStyleVar((int)(ImGuiStyleVar.PopupBorderSize), style.menuBorderSize); } - private static void PopContextMenuStyle() + private void PopContextMenuStyle() { NativeImGui.PopStyleVar(5); NativeImGui.PopStyleColor(7); diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs index e66a1d050..5a159493a 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Controls.cs @@ -1,15 +1,13 @@ using System; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { /// /// Gets the layout size of a compact colored label chip using the centralized editor style. @@ -23,15 +21,12 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when is . /// - public static Vector2 GetLabelChipSize(string label) + public Vector2 GetLabelChipSize(string label) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); Vector2 textSize = NativeImGui.CalcTextSize(label); - return new Vector2( - textSize.X + style.labelChipPadding.X * 2f, - MathF.Max( - NativeImGui.GetFrameHeight(), - textSize.Y + style.labelChipPadding.Y * 2f)); + return new Vector2(textSize.X + style.labelChipPadding.X * 2f, MathF.Max(NativeImGui.GetFrameHeight(), textSize.Y + style.labelChipPadding.Y * 2f)); } /// @@ -46,23 +41,17 @@ public static Vector2 GetLabelChipSize(string label) /// /// Thrown when is . /// - public static void LabelChip( + public void LabelChip( string label, Vector4 background ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); Vector2 size = GetLabelChipSize(label); Vector2 minimum = NativeImGui.GetCursorScreenPos(); Vector2 textSize = NativeImGui.CalcTextSize(label); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - ImGui.DrawNativeFilledRectangle( - drawList, - minimum, - minimum + size, - NativeImGui.ColorConvertFloat4ToU32(background), - style.labelChipRounding, - 0 - ); + ImGui.DrawNativeFilledRectangle(drawList, minimum, minimum + size, NativeImGui.ColorConvertFloat4ToU32(background), style.labelChipRounding, 0); ImGui.DrawNativeText(drawList, minimum + (size - textSize) * 0.5f, NativeImGui.GetColorU32((int)(ImGuiCol.Text)), label); NativeImGui.Dummy(size); } @@ -79,8 +68,9 @@ Vector4 background /// /// Thrown when is . /// - public static Vector2 GetTypeBadgeSize(string label) + public Vector2 GetTypeBadgeSize(string label) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); Vector2 textSize = NativeImGui.CalcTextSize(label); return textSize + style.typeBadgePadding * 2f; @@ -101,11 +91,12 @@ public static Vector2 GetTypeBadgeSize(string label) /// /// Thrown when is . /// - public static void TypeBadge( + public void TypeBadge( string label, Vector4 accent, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); Vector2 size = GetTypeBadgeSize(label); Vector2 minimum = NativeImGui.GetCursorScreenPos(); @@ -115,23 +106,8 @@ public static void TypeBadge( background.W = EditorPalette.opacityOpaque; Vector4 outline = accent; outline.W = EditorPalette.opacityStrong; - ImGui.DrawNativeFilledRectangle( - draw, - minimum, - minimum + size, - NativeImGui.ColorConvertFloat4ToU32(background), - style.typeBadgeRounding, - 0 - ); - ImGui.DrawNativeRectangle( - draw, - minimum, - minimum + size, - NativeImGui.ColorConvertFloat4ToU32(outline), - style.typeBadgeRounding, - (int)(ImDrawFlags.None), - style.borderSize - ); + ImGui.DrawNativeFilledRectangle(draw, minimum, minimum + size, NativeImGui.ColorConvertFloat4ToU32(background), style.typeBadgeRounding, 0); + ImGui.DrawNativeRectangle(draw, minimum, minimum + size, NativeImGui.ColorConvertFloat4ToU32(outline), style.typeBadgeRounding, (int)(ImDrawFlags.None), style.borderSize); ImGui.DrawNativeText(draw, minimum + (size - textSize) * 0.5f, NativeImGui.ColorConvertFloat4ToU32(accent), label); NativeImGui.Dummy(size); DrawItemTooltip(tooltip); @@ -155,12 +131,13 @@ public static void TypeBadge( /// /// when the value changed. /// - public static bool CompactCheckbox( + public bool CompactCheckbox( string id, ref bool value, float size = -1f, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); if (size < 0f) size = style.compactCheckboxSize; @@ -173,9 +150,7 @@ public static bool CompactCheckbox( float frameHeight = NativeImGui.GetFrameHeight(); float verticalOffset = MathF.Max(0f, (frameHeight - size) * 0.5f); Vector2 min = initialCursor + new Vector2(0f, verticalOffset); - bool changed = NativeImGui.InvisibleButton( - $"##compact_checkbox_{id}", - new Vector2(size, frameHeight)); + bool changed = NativeImGui.InvisibleButton($"##compact_checkbox_{id}", new Vector2(size, frameHeight)); if (changed) { value = !value; @@ -188,21 +163,9 @@ public static bool CompactCheckbox( if (value) { ImGui.DrawNativeFilledRectangle(drawList, min, max, textColor, 1f, 0); - uint checkColor = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorCardHeader); - ImGui.DrawNativeLine( - drawList, - min + new Vector2(size * 0.22f, size * 0.52f), - min + new Vector2(size * 0.43f, size * 0.72f), - checkColor, - 1.5f - ); - ImGui.DrawNativeLine( - drawList, - min + new Vector2(size * 0.43f, size * 0.72f), - min + new Vector2(size * 0.80f, size * 0.28f), - checkColor, - 1.5f - ); + uint checkColor = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.cardHeader); + ImGui.DrawNativeLine(drawList, min + new Vector2(size * 0.22f, size * 0.52f), min + new Vector2(size * 0.43f, size * 0.72f), checkColor, 1.5f); + ImGui.DrawNativeLine(drawList, min + new Vector2(size * 0.43f, size * 0.72f), min + new Vector2(size * 0.80f, size * 0.28f), checkColor, 1.5f); } else { @@ -211,19 +174,10 @@ public static bool CompactCheckbox( if (hovered) { - ImGui.DrawNativeRectangle( - drawList, - min - Vector2.One, - max + Vector2.One, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered), - 1f, - 0, - 1.5f - ); + ImGui.DrawNativeRectangle(drawList, min - Vector2.One, max + Vector2.One, NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered), 1f, 0, 1.5f); } DrawItemTooltip(tooltip); - return changed; } @@ -245,11 +199,12 @@ public static bool CompactCheckbox( /// /// Thrown when is empty or whitespace. /// - public static bool Checkbox( + public bool Checkbox( string label, ref bool value, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(label); bool changed = NativeImGui.Checkbox(label, ref value); DrawItemTooltip(tooltip); @@ -268,10 +223,11 @@ public static bool Checkbox( /// /// when the button is pressed. /// - public static bool CenteredButton( + public bool CenteredButton( string label, float topPadding = 0f ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(label); if (topPadding < 0f) { @@ -304,20 +260,18 @@ public static bool CenteredButton( /// /// Thrown when is . /// - public static void CenteredProgressBar( + public void CenteredProgressBar( float fraction, Vector2 size, string overlay ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(overlay); - NativeImGui.ProgressBar(fraction, size, string.Empty); - Vector2 minimum = NativeImGui.GetItemRectMin(); Vector2 maximum = NativeImGui.GetItemRectMax(); Vector2 textSize = NativeImGui.CalcTextSize(overlay); Vector2 textPosition = minimum + (maximum - minimum - textSize) * 0.5f; ImGui.DrawNativeText(NativeImGui.Raw.GetWindowDrawList(), textPosition, NativeImGui.GetColorU32((int)(ImGuiCol.Text)), overlay); } - } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs index 7009da145..2ed173cbb 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.DragDrop.cs @@ -1,16 +1,14 @@ using System; using BGCS.Runtime; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { /// /// Publishes an unmanaged drag payload for the most recently submitted item. @@ -36,7 +34,7 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when is empty or whitespace. /// - public static bool DragDropSource( + public bool DragDropSource( string payloadType, in TPayload payload, Action? drawPreview = null, @@ -45,9 +43,7 @@ public static bool DragDropSource( where TPayload : unmanaged { ArgumentException.ThrowIfNullOrWhiteSpace(payloadType); - ImGuiDragDropFlags sourceFlags = allowHoldToOpenOthers - ? ImGuiDragDropFlags.None - : ImGuiDragDropFlags.SourceNoHoldToOpenOthers; + ImGuiDragDropFlags sourceFlags = allowHoldToOpenOthers ? ImGuiDragDropFlags.None : ImGuiDragDropFlags.SourceNoHoldToOpenOthers; if (!NativeImGui.BeginDragDropSource((int)(sourceFlags))) { return false; @@ -86,7 +82,7 @@ public static bool DragDropSource( /// /// Thrown when is . /// - public static bool DragDropSource( + public bool DragDropSource( string payloadType, Func payloadFactory, Action? drawPreview = null, @@ -96,9 +92,7 @@ public static bool DragDropSource( { ArgumentException.ThrowIfNullOrWhiteSpace(payloadType); ArgumentNullException.ThrowIfNull(payloadFactory); - ImGuiDragDropFlags sourceFlags = allowHoldToOpenOthers - ? ImGuiDragDropFlags.None - : ImGuiDragDropFlags.SourceNoHoldToOpenOthers; + ImGuiDragDropFlags sourceFlags = allowHoldToOpenOthers ? ImGuiDragDropFlags.None : ImGuiDragDropFlags.SourceNoHoldToOpenOthers; if (!NativeImGui.BeginDragDropSource((int)(sourceFlags))) { return false; @@ -126,7 +120,7 @@ public static bool DragDropSource( /// /// only when a compatible payload is delivered. /// - public static bool DragDropTarget( + public bool DragDropTarget( string payloadType, out TPayload payload ) @@ -153,7 +147,7 @@ out TPayload payload /// /// only when a compatible payload is delivered. /// - public static bool DragDropTarget( + public bool DragDropTarget( string payloadType, out TPayload payload, out bool isPreviewing @@ -184,7 +178,7 @@ out bool isPreviewing /// /// only when a compatible payload is delivered. /// - public static bool DragDropTarget( + public bool DragDropTarget( string payloadType, out TPayload payload, out bool isPreviewing, @@ -208,7 +202,6 @@ bool drawDefaultHighlight bool compatible = TryReadDragDropPayload(nativePayload, out payload); if (compatible) isPreviewing = nativePayload->Preview != 0; - bool delivered = compatible && nativePayload->Delivery != 0; NativeImGui.EndDragDropTarget(); return delivered; @@ -247,7 +240,7 @@ bool drawDefaultHighlight /// /// Thrown when is empty or whitespace. /// - public static bool DragDropTarget( + public bool DragDropTarget( string payloadType, Vector2 minimum, Vector2 maximum, @@ -263,7 +256,11 @@ public static bool DragDropTarget( isPreviewing = false; if (maximum.X <= minimum.X || maximum.Y <= minimum.Y) return false; - ImRect bounds = new() { Min = ImGui.ToNative(minimum), Max = ImGui.ToNative(maximum) }; + ImRect bounds = new() + { + Min = ImGui.ToNative(minimum), + Max = ImGui.ToNative(maximum) + }; if (NativeImGui.Raw.BeginDragDropTargetCustom(&bounds, targetId) == 0) return false; try @@ -284,7 +281,7 @@ public static bool DragDropTarget( } } - private static unsafe void SetDragDropPayload( + private unsafe void SetDragDropPayload( string payloadType, in TPayload payload ) @@ -295,7 +292,7 @@ in TPayload payload _ = NativeImGui.Raw.SetDragDropPayload((sbyte*)nativeType.pointer, &payloadCopy, (uint)sizeof(TPayload), 0); } - private static unsafe bool TryReadDragDropPayload( + private unsafe bool TryReadDragDropPayload( ImGuiPayload* nativePayload, out TPayload payload ) diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs new file mode 100644 index 000000000..c8600bfa4 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Editing.cs @@ -0,0 +1,152 @@ +using System; +using Inno.Editor.Interactions; +using Inno.Scripting.Api; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui.ImGuiWidget; +/// +/// Owns session-local controls, native adaptation and reversible edit gestures. +/// +public sealed unsafe partial class ImGuiWidget +{ + private EditorEditGesture? m_propertyGesture; + private (string Owner, string Path) m_propertyEditKey; + private bool m_propertyEditTouched; + /// + /// Gets the current continuous-edit merge boundary, or null for a discrete edit. + /// + [ScriptingApiIgnore] + public string? editMergeKey => m_propertyGesture?.mergeKey; + + /// + /// Starts a History boundary only when a changed control is actively producing continuous input. + /// + /// + /// The authoritative domain History receiving the control's existing mutation records. + /// + /// + /// The stable owner-qualified property path. + /// + /// + /// The operation name shown by Undo and Redo. + /// + /// + /// A previous gesture cannot complete or History rejects the transaction. + /// + [ScriptingApiIgnore] + public void BeginPropertyEdit( + IEditorHistory history, + string path, + string name + ) { + m_session.EnsureDrawing(); + ArgumentException.ThrowIfNullOrWhiteSpace(path); + if (NativeImGui.IsAnyItemActive()) + BeginEdit(history, path, name); + } + + /// + /// Begins or touches an explicitly captured continuous edit, such as a viewport manipulation. + /// + /// + /// The authoritative domain History. + /// + /// + /// The stable owner-qualified edit identity. + /// + /// + /// The user-facing Undo name. + /// + [ScriptingApiIgnore] + public void BeginEdit( + IEditorHistory history, + string path, + string name + ) { + m_session.EnsureDrawing(); + ArgumentNullException.ThrowIfNull(history); + ArgumentException.ThrowIfNullOrWhiteSpace(path); + var key = (m_session.owner, path); + if (m_propertyGesture is not null && m_propertyEditKey != key) + CommitEdits(); + if (m_propertyGesture is null) + { + m_propertyGesture = new EditorEditGesture(history, name); + m_propertyEditKey = key; + } + + m_propertyEditTouched = true; + } + + /// + /// Checks whether this owner still owns a particular edit, including after a close or generation boundary. + /// + /// + /// The stable edit identity. + /// + /// + /// True only while the matching owner still has an active transaction. + /// + [ScriptingApiIgnore] + public bool IsEditing(string path) => m_propertyGesture is not null && m_propertyEditKey == (m_session.owner, path); + /// + /// Observes the current property after drawing, including release and cancellation without a new value sample. + /// + /// + /// The owner-qualified path passed to the property edit boundary. + /// + [ScriptingApiIgnore] + public void CompletePropertyEdit(string path) + { + m_session.EnsureDrawing(); + if (m_propertyGesture is null || m_propertyEditKey != (m_session.owner, path)) + return; + m_propertyEditTouched = true; + if (NativeImGui.IsKeyPressed((Inno.Native.ImGui.ImGuiKey)ImGuiKey.Escape, false)) + CancelEdits(); + else if (!NativeImGui.IsAnyItemActive()) + CommitEdits(); + } + + /// + /// Completes the current control edit before a document command, reload, Play transition or shutdown. + /// + /// + /// A nested transaction prevents completion; ownership is retained. + /// + [ScriptingApiIgnore] + public void CommitEdits() + { + if (m_propertyGesture is null) + return; + m_propertyGesture.Commit(); + m_propertyGesture.Dispose(); + m_propertyGesture = null; + } + + /// + /// Restores the control's initial domain values before discarding its transient presentation state. + /// + /// + /// Rollback is unavailable; the gesture remains owned for recovery. + /// + [ScriptingApiIgnore] + public void CancelEdits() + { + if (m_propertyGesture is null) + return; + EditorHistoryResult result = m_propertyGesture.Cancel(); + if (!result.succeeded) + throw new InvalidOperationException($"The property gesture could not roll back: {result.message}"); + m_propertyGesture.Dispose(); + m_propertyGesture = null; + } + + internal void BeginEditingFrame() => m_propertyEditTouched = false; + internal void CompleteEditingFrame() + { + if (m_propertyGesture is not null && !m_propertyEditTouched) + CancelEdits(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs new file mode 100644 index 000000000..34816bca5 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Gizmo.cs @@ -0,0 +1,257 @@ +using System; +using System.Numerics; +using Matrix = Inno.Core.Mathematics.Matrix; +using NativeImGui = Inno.Native.ImGui.ImGui; +using NativeGizmo = Inno.Native.ImGuizmo.ImGuizmo; +using NativeOperation = Inno.Native.ImGuizmo.ImGuizmoOperation; +using NativeSpace = Inno.Native.ImGuizmo.ImGuizmoMode; + +namespace Inno.Editor.ImGui.ImGuiWidget; +/// +/// Chooses the transform handles rendered by the viewport presenter. +/// +[Flags] +public enum TransformGizmoOperation +{ + /// + /// Renders translation handles. + /// + Translate = 1, + /// + /// Renders rotation handles. + /// + Rotate = 2, + /// + /// Renders the X scale handle. + /// + ScaleX = 4, + /// + /// Renders the Y scale handle. + /// + ScaleY = 8, + /// + /// Renders the Z scale handle. + /// + ScaleZ = 16, + /// + /// Renders all local scale handles. + /// + Scale = ScaleX | ScaleY | ScaleZ +} + +/// +/// Chooses how the manipulation axes are oriented. +/// +public enum TransformGizmoSpace +{ + /// + /// Uses the object's local orientation. + /// + Local, + /// + /// Uses the world's orientation. + /// + World +} + +/// +/// Supplies frame-local matrices and interaction policy without native handles. +/// +public readonly record struct TransformGizmoOptions +{ + /// + /// Gets the viewport's screen-space minimum. + /// + public Vector2 minimum { get; init; } + /// + /// Gets the viewport's screen-space maximum. + /// + public Vector2 maximum { get; init; } + /// + /// Gets the column-vector view matrix. + /// + public Matrix view { get; init; } + /// + /// Gets the projection matrix supplied by the viewport owner. + /// + public Matrix projection { get; init; } + /// + /// Gets whether this viewport uses an orthographic projection. + /// + public bool orthographic { get; init; } + /// + /// Gets whether manipulation can consume pointer input. + /// + public bool enabled { get; init; } + /// + /// Gets the requested handles. + /// + public TransformGizmoOperation operation { get; init; } + /// + /// Gets the handle coordinate space. + /// + public TransformGizmoSpace space { get; init; } + /// + /// Gets whether the X translation axis is excluded by the viewport plane. + /// + public bool maskX { get; init; } + /// + /// Gets whether the Y translation axis is excluded by the viewport plane. + /// + public bool maskY { get; init; } + /// + /// Gets whether the Z translation axis is excluded by the viewport plane. + /// + public bool maskZ { get; init; } +} + +/// +/// Returns copied manipulation output; the domain owner decides how to apply and record it. +/// +/// +/// The resulting world matrix. +/// +/// +/// Whether the native presenter reported an edited value. +/// +/// +/// Whether the handle currently captures the pointer. +/// +/// +/// Whether the pointer is over a handle. +/// +public readonly record struct TransformGizmoResult( + Matrix matrix, + bool changed, + bool isUsing, + bool isOver +); +/// +/// Owns session-local controls, native adaptation and reversible edit gestures. +/// +public sealed unsafe partial class ImGuiWidget +{ + /// + /// Draws a transform handle using stack-owned ABI buffers and restores native global style and masks before returning. + /// + /// + /// The current world matrix, copied into the native call. + /// + /// + /// The explicit viewport and interaction policy. + /// + /// + /// Copied output with no native pointer or domain object. + /// + public TransformGizmoResult TransformGizmo( + Matrix world, + TransformGizmoOptions options + ) { + m_session.EnsureDrawing(); + float* view = stackalloc float[16]; + float* projection = stackalloc float[16]; + float* model = stackalloc float[16]; + WriteColumnMajor(options.view, view); + WriteColumnMajor(options.projection, projection); + WriteColumnMajor(world, model); + NativeOperation operation = ToNativeOperation(options.operation); + NativeGizmo.BeginFrame(); + NativeGizmo.Raw.SetDrawlist((Inno.Native.ImGuizmo.ImDrawList*)(nint)NativeImGui.Raw.GetWindowDrawList()); + NativeGizmo.SetRect(options.minimum.X, options.minimum.Y, MathF.Max(1f, options.maximum.X - options.minimum.X), MathF.Max(1f, options.maximum.Y - options.minimum.Y)); + NativeGizmo.SetOrthographic(options.orthographic); + NativeGizmo.SetGizmoSizeClipSpace(0.21f); + var style = NativeGizmo.Raw.GetStyle(); + var previousStyle = *style; + try + { + style->TranslationLineThickness = 3.5f; + style->TranslationLineArrowSize = 9f; + style->RotationLineThickness = 3f; + style->RotationOuterLineThickness = 4f; + style->ScaleLineThickness = 3.5f; + style->ScaleLineCircleSize = 9f; + style->CenterCircleSize = 8f; + NativeGizmo.Enable(options.enabled); + NativeGizmo.SetAxisMask(options.maskX, options.maskY, options.maskZ); + NativeGizmo.SetAxisLimit(options.maskX || options.maskY || options.maskZ ? float.PositiveInfinity : 0.0025f); + bool changed = NativeGizmo.Raw.Manipulate( + view, + projection, + operation, + options.space == TransformGizmoSpace.Local ? NativeSpace.Local : NativeSpace.World, + model, + null, + null, + null, + null + ) != 0; + return new TransformGizmoResult(ReadColumnMajor(model), changed, NativeGizmo.IsUsing(), NativeGizmo.IsOver(operation)); + } + finally + { + *style = previousStyle; + NativeGizmo.Enable(true); + NativeGizmo.SetAxisMask(false, false, false); + NativeGizmo.SetAxisLimit(0.0025f); + } + } + + private static NativeOperation ToNativeOperation(TransformGizmoOperation operation) + { + if (operation == TransformGizmoOperation.Translate) + return NativeOperation.Translate; + if (operation == TransformGizmoOperation.Rotate) + return NativeOperation.Rotate; + NativeOperation result = 0; + if (operation.HasFlag(TransformGizmoOperation.ScaleX)) + result |= NativeOperation.ScaleX; + if (operation.HasFlag(TransformGizmoOperation.ScaleY)) + result |= NativeOperation.ScaleY; + if (operation.HasFlag(TransformGizmoOperation.ScaleZ)) + result |= NativeOperation.ScaleZ; + if (result == 0) + throw new ArgumentOutOfRangeException(nameof(operation)); + return result; + } + + private static unsafe void WriteColumnMajor( + Matrix matrix, + float* destination + ) { + destination[0] = matrix.m11; + destination[1] = matrix.m21; + destination[2] = matrix.m31; + destination[3] = matrix.m41; + destination[4] = matrix.m12; + destination[5] = matrix.m22; + destination[6] = matrix.m32; + destination[7] = matrix.m42; + destination[8] = matrix.m13; + destination[9] = matrix.m23; + destination[10] = matrix.m33; + destination[11] = matrix.m43; + destination[12] = matrix.m14; + destination[13] = matrix.m24; + destination[14] = matrix.m34; + destination[15] = matrix.m44; + } + + private static unsafe Matrix ReadColumnMajor(float* source) => new( + source[0], + source[4], + source[8], + source[12], + source[1], + source[5], + source[9], + source[13], + source[2], + source[6], + source[10], + source[14], + source[3], + source[7], + source[11], + source[15] + ); +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs index e3f57f496..eebd74b85 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.HelpBox.cs @@ -4,15 +4,12 @@ using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides standard help box presentation in the Editor UI. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { - [ThreadStatic] - private static SectionLayoutState? s_sectionLayout; - + private SectionLayoutState? m_sectionLayout; /// /// Draws a wrapped contextual message with a semantic icon and a subdued status surface. /// @@ -25,11 +22,12 @@ public static unsafe partial class ImGuiWidget /// /// Semantic accent for the icon, outline, and leading stripe. /// - public static void HelpBox( + public void HelpBox( string text, string icon, Vector4 color ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); ArgumentNullException.ThrowIfNull(icon); Vector2 origin = NativeImGui.GetCursorScreenPos(); @@ -45,31 +43,9 @@ Vector4 color background.W = EditorPalette.opacityOpaque; Vector4 outline = color; outline.W = EditorPalette.opacityMuted; - ImGui.DrawNativeFilledRectangle( - draw, - origin, - origin + new Vector2(width, height), - NativeImGui.ColorConvertFloat4ToU32(background), - 4f * style.zoom, - 0 - ); - ImGui.DrawNativeRectangle( - draw, - origin, - origin + new Vector2(width, height), - NativeImGui.ColorConvertFloat4ToU32(outline), - 4f * style.zoom, - 0, - 1f - ); - ImGui.DrawNativeFilledRectangle( - draw, - origin, - origin + new Vector2(2f * style.zoom, height), - NativeImGui.ColorConvertFloat4ToU32(color), - 0, - 0 - ); + ImGui.DrawNativeFilledRectangle(draw, origin, origin + new Vector2(width, height), NativeImGui.ColorConvertFloat4ToU32(background), 4f * style.zoom, 0); + ImGui.DrawNativeRectangle(draw, origin, origin + new Vector2(width, height), NativeImGui.ColorConvertFloat4ToU32(outline), 4f * style.zoom, 0, 1f); + ImGui.DrawNativeFilledRectangle(draw, origin, origin + new Vector2(2f * style.zoom, height), NativeImGui.ColorConvertFloat4ToU32(color), 0, 0); ImGui.DrawNativeText(draw, origin + padding, NativeImGui.ColorConvertFloat4ToU32(color), icon); NativeImGui.SetCursorScreenPos(origin + padding + new Vector2(iconWidth + gap, 0f)); NativeImGui.PushTextWrapPos(NativeImGui.GetCursorPosX() + textWidth); @@ -81,6 +57,7 @@ Vector4 color { NativeImGui.PopTextWrapPos(); } + NativeImGui.SetCursorScreenPos(origin); NativeImGui.Dummy(new Vector2(width, height)); } @@ -95,8 +72,9 @@ Vector4 color /// /// Thrown when is . /// - public static void CollectionSectionHeader(string title) + public void CollectionSectionHeader(string title) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(title); Vector2 origin = NativeImGui.GetCursorScreenPos(); float width = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); @@ -105,11 +83,11 @@ public static void CollectionSectionHeader(string title) NativeImGui.Raw.GetWindowDrawList(), origin, origin + new Vector2(width, height), - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.hierarchySceneRow), + NativeImGui.ColorConvertFloat4ToU32(EditorPalette.title), 0, 0 ); - NativeImGui.PushStyleColor((int)(ImGuiCol.Separator), EditorPalette.inspectorSectionBorder); + NativeImGui.PushStyleColor((int)(ImGuiCol.Separator), EditorPalette.sectionBorder); try { NativeImGui.SeparatorText(title); @@ -129,12 +107,13 @@ public static void CollectionSectionHeader(string title) /// /// Thrown when is . /// - public static void SectionLayout(Action drawContent) + public void SectionLayout(Action drawContent) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(drawContent); - SectionLayoutState? parent = s_sectionLayout; + SectionLayoutState? parent = m_sectionLayout; var current = new SectionLayoutState(); - s_sectionLayout = current; + m_sectionLayout = current; try { drawContent(); @@ -142,7 +121,7 @@ public static void SectionLayout(Action drawContent) finally { CompleteSection(current); - s_sectionLayout = parent; + m_sectionLayout = parent; } } @@ -153,7 +132,7 @@ public static void SectionLayout(Action drawContent) /// Attribute-driven drawers use this value to skip properties until the next section header /// when the current section is collapsed. /// - public static bool isSectionContentVisible => s_sectionLayout?.contentVisible ?? true; + public bool isSectionContentVisible => m_sectionLayout?.contentVisible ?? true; /// /// Ensures that subsequently drawn default properties belong to a framed section. @@ -171,17 +150,16 @@ public static void SectionLayout(Action drawContent) /// /// Thrown when is . /// - public static bool EnsureSection( + public bool EnsureSection( string title = "Properties", string? description = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(title); - SectionLayoutState? layout = s_sectionLayout; + SectionLayoutState? layout = m_sectionLayout; if (layout is null) return true; - return layout.sectionIndex == 0 - ? SectionHeader(title, description) - : layout.contentVisible; + return layout.sectionIndex == 0 ? SectionHeader(title, description) : layout.contentVisible; } /// @@ -203,13 +181,14 @@ public static bool EnsureSection( /// when the section content should be drawn; otherwise, /// when the section is collapsed. /// - public static bool SectionHeader( + public bool SectionHeader( string title, string? description = null, Action? drawLeadingControl = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(title); - SectionLayoutState? layout = s_sectionLayout; + SectionLayoutState? layout = m_sectionLayout; if (layout is null) { NativeImGui.SeparatorText(title); @@ -221,13 +200,12 @@ public static bool SectionHeader( Vector2 origin = NativeImGui.GetCursorScreenPos(); float width = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); float frameHeight = NativeImGui.GetFrameHeight(); - float paddingX = style.inspectorSectionPadding.X; + float paddingX = style.sectionPadding.X; float legendInset = style.sectionHeaderPadding.X; int sectionIndex = layout.sectionIndex++; uint stateId = NativeImGui.GetID($"##inspector_section_{sectionIndex}"); ImGuiStorage* storage = NativeImGui.Raw.GetStateStorage(); bool open = NativeImGui.Raw.GetBool(storage, stateId, 1) != 0; - NativeImGui.SetCursorScreenPos(new Vector2(origin.X + legendInset, origin.Y)); bool titlePressed = false; Vector2 titleSize = GetClickableTextSize(title, Vector2.Zero); @@ -237,14 +215,10 @@ public static bool SectionHeader( if (drawLeadingControl is not null) { drawLeadingControl(); - NativeImGui.SameLine(0f, style.inspectorHeaderControlSpacing); + NativeImGui.SameLine(0f, style.headerControlSpacing); } - titlePressed = ClickableText( - $"inspector_section_title_{sectionIndex}", - title, - new Vector2(MathF.Max(1f, titleSize.X), frameHeight), - description); + titlePressed = ClickableText($"inspector_section_title_{sectionIndex}", title, new Vector2(MathF.Max(1f, titleSize.X), frameHeight), description); } finally { @@ -262,25 +236,18 @@ public static bool SectionHeader( Vector2 contentCursor = NativeImGui.GetCursorScreenPos(); contentCursor.X = origin.X; float top = origin.Y + frameHeight * 0.5f; - float legendLeft = legendMin.X - style.inspectorSectionLegendGap; - float legendRight = legendMax.X + style.inspectorSectionLegendGap; - + float legendLeft = legendMin.X - style.sectionLegendGap; + float legendRight = legendMax.X + style.sectionLegendGap; NativeImGui.SetCursorScreenPos(contentCursor); layout.contentVisible = open; if (!open) { - DrawCollapsedSectionBorder( - NativeImGui.Raw.GetWindowDrawList(), - origin.X, - origin.X + width, - top, - legendLeft, - legendRight); - NativeImGui.Dummy(new Vector2(0f, style.inspectorSectionSpacing)); + DrawCollapsedSectionBorder(NativeImGui.Raw.GetWindowDrawList(), origin.X, origin.X + width, top, legendLeft, legendRight); + NativeImGui.Dummy(new Vector2(0f, style.sectionSpacing)); return false; } - NativeImGui.Dummy(new Vector2(0f, style.inspectorSectionPadding.Y)); + NativeImGui.Dummy(new Vector2(0f, style.sectionPadding.Y)); NativeImGui.Indent(paddingX); layout.active = true; layout.left = origin.X; @@ -291,7 +258,7 @@ public static bool SectionHeader( return true; } - private static void DrawCollapsedSectionBorder( + private void DrawCollapsedSectionBorder( ImDrawList* draw, float left, float right, @@ -299,24 +266,12 @@ private static void DrawCollapsedSectionBorder( float legendLeft, float legendRight ) { - uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorSectionBorder); - DrawCappedSectionSegment( - draw, - left, - MathF.Max(left, legendLeft), - top, - capAtStart: true, - color); - DrawCappedSectionSegment( - draw, - MathF.Min(right, legendRight), - right, - top, - capAtStart: false, - color); + uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.sectionBorder); + DrawCappedSectionSegment(draw, left, MathF.Max(left, legendLeft), top, capAtStart: true, color); + DrawCappedSectionSegment(draw, MathF.Min(right, legendRight), right, top, capAtStart: false, color); } - private static void DrawCappedSectionSegment( + private void DrawCappedSectionSegment( ImDrawList* draw, float from, float to, @@ -326,76 +281,37 @@ uint color ) { float thickness = style.borderSize; float halfThickness = thickness * 0.5f; - float halfCap = style.inspectorCollapsedSectionCapLength * 0.5f; + float halfCap = style.collapsedSectionCapLength * 0.5f; float capX = capAtStart ? from : to; - float innerX = capAtStart - ? MathF.Min(to, capX + halfThickness) - : MathF.Max(from, capX - halfThickness); - ImGui.DrawNativeLine( - draw, - new Vector2(capX, centerY - halfCap), - new Vector2(capX, centerY + halfCap), - color, - thickness - ); - ImGui.DrawNativeLine( - draw, - new Vector2(innerX, centerY), - new Vector2(capAtStart ? to : from, centerY), - color, - thickness - ); + float innerX = capAtStart ? MathF.Min(to, capX + halfThickness) : MathF.Max(from, capX - halfThickness); + ImGui.DrawNativeLine(draw, new Vector2(capX, centerY - halfCap), new Vector2(capX, centerY + halfCap), color, thickness); + ImGui.DrawNativeLine(draw, new Vector2(innerX, centerY), new Vector2(capAtStart ? to : from, centerY), color, thickness); } - private static void CompleteSection(SectionLayoutState layout) + private void CompleteSection(SectionLayoutState layout) { if (!layout.active) return; - - NativeImGui.Dummy(new Vector2(0f, style.inspectorSectionPadding.Y)); - NativeImGui.Unindent(style.inspectorSectionPadding.X); - float bottom = MathF.Max( - NativeImGui.GetCursorScreenPos().Y, - layout.top + NativeImGui.GetFrameHeight()); - uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.inspectorSectionBorder); + NativeImGui.Dummy(new Vector2(0f, style.sectionPadding.Y)); + NativeImGui.Unindent(style.sectionPadding.X); + float bottom = MathF.Max(NativeImGui.GetCursorScreenPos().Y, layout.top + NativeImGui.GetFrameHeight()); + uint color = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.sectionBorder); ImDrawList* draw = NativeImGui.Raw.GetWindowDrawList(); - float radius = MathF.Min( - style.inspectorSectionRounding, - MathF.Max(0f, MathF.Min(layout.right - layout.left, bottom - layout.top) * 0.5f)); + float radius = MathF.Min(style.sectionRounding, MathF.Max(0f, MathF.Min(layout.right - layout.left, bottom - layout.top) * 0.5f)); float leftLegend = MathF.Max(layout.left + radius, layout.legendLeft); float rightLegend = MathF.Min(layout.right - radius, layout.legendRight); ImGui.DrawNativePathLine(draw, new Vector2(leftLegend, layout.top)); ImGui.DrawNativePathLine(draw, new Vector2(layout.left + radius, layout.top)); - ImGui.DrawNativePathQuadratic( - draw, - new Vector2(layout.left, layout.top), - new Vector2(layout.left, layout.top + radius), - 0 - ); + ImGui.DrawNativePathQuadratic(draw, new Vector2(layout.left, layout.top), new Vector2(layout.left, layout.top + radius), 0); ImGui.DrawNativePathLine(draw, new Vector2(layout.left, bottom - radius)); - ImGui.DrawNativePathQuadratic( - draw, - new Vector2(layout.left, bottom), - new Vector2(layout.left + radius, bottom), - 0 - ); + ImGui.DrawNativePathQuadratic(draw, new Vector2(layout.left, bottom), new Vector2(layout.left + radius, bottom), 0); ImGui.DrawNativePathLine(draw, new Vector2(layout.right - radius, bottom)); - ImGui.DrawNativePathQuadratic( - draw, - new Vector2(layout.right, bottom), - new Vector2(layout.right, bottom - radius), - 0 - ); + ImGui.DrawNativePathQuadratic(draw, new Vector2(layout.right, bottom), new Vector2(layout.right, bottom - radius), 0); ImGui.DrawNativePathLine(draw, new Vector2(layout.right, layout.top + radius)); - ImGui.DrawNativePathQuadratic( - draw, - new Vector2(layout.right, layout.top), - new Vector2(layout.right - radius, layout.top), - 0 - ); + ImGui.DrawNativePathQuadratic(draw, new Vector2(layout.right, layout.top), new Vector2(layout.right - radius, layout.top), 0); ImGui.DrawNativePathLine(draw, new Vector2(rightLegend, layout.top)); NativeImGui.Raw.PathStroke(draw, color, (int)(ImDrawFlags.None), style.borderSize); - NativeImGui.Dummy(new Vector2(0f, style.inspectorSectionSpacing)); + NativeImGui.Dummy(new Vector2(0f, style.sectionSpacing)); layout.active = false; layout.contentVisible = true; } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs index db1eec291..db25fd6ab 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.InlineRename.cs @@ -1,18 +1,15 @@ using System; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { private const float C_INLINE_RENAME_FOCUS_OFFSET = 1f; - /// /// Draws a compact single-line rename editor inside an existing row. /// @@ -44,7 +41,7 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when is not finite or is not positive. /// - public static InlineRenameResult InlineRename( + public InlineRenameResult InlineRename( string id, ref string text, ref bool requestFocus, @@ -52,17 +49,14 @@ public static InlineRenameResult InlineRename( nuint capacity = 512, float width = -1f ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); if (!float.IsFinite(rowHeight) || rowHeight <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(rowHeight), - rowHeight, - "Inline rename row height must be finite and positive."); + throw new ArgumentOutOfRangeException(nameof(rowHeight), rowHeight, "Inline rename row height must be finite and positive."); } - float fieldWidth = MathF.Max( - 1f, - width < 0f ? NativeImGui.GetContentRegionAvail().X : width); + + float fieldWidth = MathF.Max(1f, width < 0f ? NativeImGui.GetContentRegionAvail().X : width); string controlId = $"##rename_{id}"; bool selectAll = requestFocus; NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.inlineRenameFramePadding); @@ -72,9 +66,7 @@ public static InlineRenameResult InlineRename( { float fieldHeight = NativeImGui.GetFrameHeight(); Vector2 cursor = NativeImGui.GetCursorScreenPos(); - NativeImGui.SetCursorScreenPos(new Vector2( - cursor.X, - cursor.Y + MathF.Max(0f, (rowHeight - fieldHeight) * 0.5f))); + NativeImGui.SetCursorScreenPos(new Vector2(cursor.X, cursor.Y + MathF.Max(0f, (rowHeight - fieldHeight) * 0.5f))); NativeImGui.SetNextItemWidth(fieldWidth); if (selectAll) { @@ -85,22 +77,17 @@ public static InlineRenameResult InlineRename( NativeImGui.PushStyleColor((int)(ImGuiCol.NavCursor), EditorPalette.transparent); try { - submitted = ImGuiUtf8Buffer.InputText( - controlId, - null, - ref text, - (nuint)capacity, - ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll); + submitted = ImGuiUtf8Buffer.InputText(controlId, null, ref text, (nuint)capacity, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll); } finally { NativeImGui.PopStyleColor(); } + bool deactivated = NativeImGui.IsItemDeactivated(); - bool escapePressed = NativeImGui.IsKeyPressed(ImGuiKey.Escape); + bool escapePressed = NativeImGui.IsKeyPressed((Inno.Native.ImGui.ImGuiKey)ImGuiKey.Escape); bool active = NativeImGui.IsItemActive(); - bool contextMenuRequested = active && - NativeImGui.IsMouseReleased((int)(ImGuiMouseButton.Right)); + bool contextMenuRequested = active && NativeImGui.IsMouseReleased((int)(ImGuiMouseButton.Right)); if (selectAll && active) { uint inputId = NativeImGui.GetItemID(); @@ -111,6 +98,7 @@ public static InlineRenameResult InlineRename( requestFocus = false; } } + if (active) { Vector2 focusOffset = new(C_INLINE_RENAME_FOCUS_OFFSET); @@ -137,9 +125,8 @@ public static InlineRenameResult InlineRename( NativeImGui.Raw.ClearActiveID(); return InlineRenameResult.FocusLost; } - return deactivated - ? InlineRenameResult.FocusLost - : InlineRenameResult.None; + + return deactivated ? InlineRenameResult.FocusLost : InlineRenameResult.None; } finally { @@ -172,7 +159,7 @@ public sealed class InlineRenamePresentation /// Thrown when is empty. /// /// - /// Thrown when or is not finite and + /// Thrown when or is not finite and /// positive, or when is zero. /// public InlineRenamePresentation( @@ -185,25 +172,19 @@ public InlineRenamePresentation( throw new ArgumentException("An inline rename identifier is required.", nameof(id)); if (!float.IsFinite(width) || width <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(width), - width, - "The inline rename width must be finite and positive."); + throw new ArgumentOutOfRangeException(nameof(width), width, "The inline rename width must be finite and positive."); } + if (!float.IsFinite(rowHeight) || rowHeight <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(rowHeight), - rowHeight, - "The inline rename row height must be finite and positive."); + throw new ArgumentOutOfRangeException(nameof(rowHeight), rowHeight, "The inline rename row height must be finite and positive."); } + if (bufferSize == 0) { - throw new ArgumentOutOfRangeException( - nameof(bufferSize), - bufferSize, - "The inline rename buffer size must be positive."); + throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "The inline rename buffer size must be positive."); } + this.id = id; this.width = width; this.rowHeight = rowHeight; @@ -214,17 +195,14 @@ public InlineRenamePresentation( /// Gets the stable ImGui identifier used by the input field. /// public string id { get; } - /// /// Gets the requested input width in logical pixels. /// public float width { get; } - /// /// Gets the height of the row area in which the input field is centered. /// public float rowHeight { get; } - /// /// Gets the maximum UTF-8 buffer size accepted by the input. /// @@ -240,17 +218,14 @@ public enum InlineRenameResult /// Indicates that the interaction remains active. /// None, - /// /// Indicates that the edited text should be committed. /// Commit, - /// /// Indicates that the input lost focus and its valid value should be committed before closing. /// FocusLost, - /// /// Indicates that the edited text should be discarded. /// diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Lifetime.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Lifetime.cs new file mode 100644 index 000000000..fb6934f99 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Lifetime.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace Inno.Editor.ImGui.ImGuiWidget; +/// +/// Owns and retires transient controls by session and stable presentation owner. +/// +public sealed unsafe partial class ImGuiWidget +{ + private readonly EditorUiSession m_session; + internal ImGuiWidget(EditorUiSession session) + { + m_session = session ?? throw new ArgumentNullException(nameof(session)); + style = new EditorStyleMetrics(session); + } + + internal void RetireOwner(string owner) + { + if (m_propertyGesture is not null && m_propertyEditKey.Owner == owner) + CancelEdits(); + var retired = new List<(string Owner, uint Window)>(); + foreach (var key in m_treeStatesByWindow.Keys) + { + if (string.Equals(key.Owner, owner, StringComparison.Ordinal)) + retired.Add(key); + } + + foreach (var key in retired) + m_treeStatesByWindow.Remove(key); + } + + internal void Retire() + { + CancelEdits(); + m_treeStatesByWindow.Clear(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.MixedValue.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.MixedValue.cs new file mode 100644 index 000000000..4b2880b0a --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.MixedValue.cs @@ -0,0 +1,46 @@ +using System; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui.ImGuiWidget; +/// +/// Presents mixed values using the same controls and shared History. +/// +public sealed partial class ImGuiWidget +{ + private bool m_isMixedValue; + + /// + /// Gets whether the currently drawn control group represents differing selected values. + /// + public bool isMixedValue => m_isMixedValue; + + /// + /// Draws one control group with the native mixed-value state, restoring the previous item flags on failure. + /// + /// + /// Whether the current selection contains different values. + /// + /// + /// Synchronous control drawing; never retained. + /// + public void MixedValue( + bool mixed, + Action draw + ) { + m_session.EnsureDrawing(); + ArgumentNullException.ThrowIfNull(draw); + bool previous = m_isMixedValue; + m_isMixedValue = mixed || previous; + NativeImGui.PushItemFlag((int)ImGuiItemFlagsPrivate.MixedValue, mixed); + try + { + draw(); + } + finally + { + NativeImGui.PopItemFlag(); + m_isMixedValue = previous; + } + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs index 2b3d9832b..c6e85ce7a 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Panel.cs @@ -1,16 +1,14 @@ using System; using System.Numerics; - using Inno.Scripting.Api; using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Reusable editor widgets built on top of . /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { /// /// Opens a standard panel window and executes panel body. @@ -31,16 +29,16 @@ public static unsafe partial class ImGuiWidget /// Whether the panel body should use the current standard window padding. /// [ScriptingApiIgnore] - public static void PanelWindow( + public void PanelWindow( string title, ref bool isOpen, Action drawBody, ImGuiWindowFlags flags = ImGuiWindowFlags.NoCollapse, bool useWindowPadding = true ) { + m_session.EnsureDrawing(); if (!isOpen) return; - bool pushedPadding = false; bool beganWindow = false; try @@ -50,6 +48,7 @@ public static void PanelWindow( NativeImGui.PushStyleVar((int)(ImGuiStyleVar.WindowPadding), Vector2.Zero); pushedPadding = true; } + // Dock tabs need their own vertical breathing room. Applying the metric only while // Begin builds the window decorations keeps normal inputs compact without clipping // the first tab row against the main-menu work rect. @@ -66,17 +65,18 @@ public static void PanelWindow( { NativeImGui.PopStyleVar(2); } + beganWindow = true; if (pushedPadding) { NativeImGui.PopStyleVar(); pushedPadding = false; } + if (visible) { if (DrawPanelCloseButton(title)) isOpen = false; - if (isOpen) drawBody(); } @@ -109,26 +109,21 @@ public static void PanelWindow( /// /// Thrown when is . /// - public static void ConstrainedContent( + public void ConstrainedContent( string id, Action drawContent, bool useWindowPadding = true ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(drawContent); - float width = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X); - Vector2 padding = useWindowPadding - ? ImGui.FromNative(NativeImGui.Raw.GetStyle()->WindowPadding) - : Vector2.Zero; + Vector2 padding = useWindowPadding ? ImGui.FromNative(NativeImGui.Raw.GetStyle()->WindowPadding) : Vector2.Zero; float contentWidth = MathF.Max(1f, width - padding.X * 2f); - ImGuiChildFlags childFlags = ImGuiChildFlags.AutoResizeY | - ImGuiChildFlags.AlwaysAutoResize; + ImGuiChildFlags childFlags = ImGuiChildFlags.AutoResizeY | ImGuiChildFlags.AlwaysAutoResize; if (useWindowPadding) childFlags |= ImGuiChildFlags.AlwaysUseWindowPadding; - ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoSavedSettings; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; NativeImGui.SetNextWindowContentSize(new Vector2(contentWidth, 0f)); bool visible = NativeImGui.BeginChild(id, new Vector2(width, 0f), (int)(childFlags), (int)(windowFlags)); try @@ -161,36 +156,29 @@ public static void ConstrainedContent( /// /// Thrown when is . /// - public static void HeaderSurface( + public void HeaderSurface( string id, Action drawContent, bool spanWindowPadding = false ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(drawContent); - ImGuiWindow* parentWindow = NativeImGui.Raw.GetCurrentWindow(); Vector2 contentCursor = NativeImGui.GetCursorScreenPos(); - Vector2 parentPadding = spanWindowPadding - ? ImGui.FromNative(parentWindow->WindowPadding) - : Vector2.Zero; + Vector2 parentPadding = spanWindowPadding ? ImGui.FromNative(parentWindow->WindowPadding) : Vector2.Zero; Vector2 headerOrigin = contentCursor - parentPadding; - float width = MathF.Max( - 1f, - NativeImGui.GetContentRegionAvail().X + parentPadding.X * 2f); + float width = MathF.Max(1f, NativeImGui.GetContentRegionAvail().X + parentPadding.X * 2f); NativeImGui.SetCursorScreenPos(headerOrigin); - - NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), EditorPalette.inspectorTargetHeader); - NativeImGui.PushStyleColor((int)(ImGuiCol.Border), EditorPalette.inspectorTargetHeaderBorder); - NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.inspectorTargetHeaderPadding); + NativeImGui.PushStyleColor((int)(ImGuiCol.FrameBg), EditorPalette.targetHeader); + NativeImGui.PushStyleColor((int)(ImGuiCol.Border), EditorPalette.targetHeaderBorder); + NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FramePadding), style.targetHeaderPadding); NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameBorderSize), style.borderSize); NativeImGui.PushStyleVar((int)(ImGuiStyleVar.FrameRounding), 0f); try { ImGuiChildFlags childFlags = ImGuiChildFlags.FrameStyle | ImGuiChildFlags.AutoResizeY; - ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoSavedSettings; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings; bool visible = NativeImGui.BeginChild(id, new Vector2(width, 0f), (int)(childFlags), (int)(windowFlags)); // FrameStyle consumed the square outer-container rounding in BeginChild. Restore the // normal editor rounding before drawing controls inside the header. @@ -211,21 +199,17 @@ public static void HeaderSurface( NativeImGui.PopStyleColor(2); } - NativeImGui.SetCursorScreenPos(new Vector2( - contentCursor.X, - NativeImGui.GetCursorScreenPos().Y)); + NativeImGui.SetCursorScreenPos(new Vector2(contentCursor.X, NativeImGui.GetCursorScreenPos().Y)); } - private static bool DrawPanelCloseButton(string title) + private bool DrawPanelCloseButton(string title) { if (!NativeImGui.IsWindowDocked()) return DrawFloatingPanelCloseButton(title); - uint dockId = NativeImGui.GetWindowDockID(); ImGuiDockNode* dockNode = NativeImGui.Raw.DockBuilderGetNode(dockId); if (dockNode == null || NativeImGui.Raw.DockNodeBeginAmendTabBar(dockNode) == 0) return false; - try { ImGuiTabBar* tabBar = dockNode->TabBar; @@ -234,9 +218,7 @@ private static bool DrawPanelCloseButton(string title) ImRect tabBarBounds = tabBar->BarRect; float tabBarHeight = MathF.Max(1f, tabBarBounds.Max.Y - tabBarBounds.Min.Y); float iconSlotSize = MathF.Min(GetCompactIconSize().X, tabBarHeight); - Vector2 itemCenter = new( - tabBarBounds.Max.X - iconSlotSize * 0.5f, - (tabBarBounds.Min.Y + tabBarBounds.Max.Y) * 0.5f); + Vector2 itemCenter = new(tabBarBounds.Max.X - iconSlotSize * 0.5f, (tabBarBounds.Min.Y + tabBarBounds.Max.Y) * 0.5f); Vector2 itemMinimum = itemCenter - new Vector2(iconSlotSize * 0.5f); Vector2 itemMaximum = itemCenter + new Vector2(iconSlotSize * 0.5f); ImRect itemBounds = new() @@ -247,29 +229,16 @@ private static bool DrawPanelCloseButton(string title) uint itemId = NativeImGui.GetID($"##panel_close_{dockId}"); bool hovered = false; bool held = false; - ImGuiButtonFlags buttonFlags = (ImGuiButtonFlags)( - (int)ImGuiButtonFlagsPrivate.AllowOverlap | - (int)ImGuiButtonFlagsPrivate.NoNavFocus | - (int)ImGuiButtonFlagsPrivate.PressedOnClickRelease); + ImGuiButtonFlags buttonFlags = (ImGuiButtonFlags)((int)ImGuiButtonFlagsPrivate.AllowOverlap | (int)ImGuiButtonFlagsPrivate.NoNavFocus | (int)ImGuiButtonFlagsPrivate.PressedOnClickRelease); ImGuiWindow* hoveredWindow = NativeImGui.Raw.GetCurrentContext()->HoveredWindow; - bool hostHovered = hoveredWindow != null && - hoveredWindow->RootWindowDockTree == dockNode->HostWindow->RootWindowDockTree; + bool hostHovered = hoveredWindow != null && hoveredWindow->RootWindowDockTree == dockNode->HostWindow->RootWindowDockTree; bool mouseHovered = hostHovered && NativeImGui.IsMouseHoveringRect(itemMinimum, itemMaximum); bool itemAdded = NativeImGui.Raw.ItemAdd(&itemBounds, itemId, null, 0) != 0; - bool pressed = hostHovered && itemAdded && - NativeImGui.ButtonBehavior(itemBounds, itemId, out hovered, out held, (int)buttonFlags); + bool pressed = hostHovered && itemAdded && NativeImGui.ButtonBehavior(itemBounds, itemId, out hovered, out held, (int)buttonFlags); hovered |= mouseHovered; pressed |= mouseHovered && NativeImGui.IsMouseClicked((int)(ImGuiMouseButton.Left)); - - uint iconColor = hovered || held - ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) - : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); - DrawPanelCloseMark( - NativeImGui.Raw.GetWindowDrawList(), - itemCenter, - iconSlotSize, - iconColor); - + uint iconColor = hovered || held ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); + DrawPanelCloseMark(NativeImGui.Raw.GetWindowDrawList(), itemCenter, iconSlotSize, iconColor); if (hovered && BeginMenuTooltip()) { NativeImGui.TextUnformatted($"Close {title}"); @@ -284,32 +253,47 @@ private static bool DrawPanelCloseButton(string title) } } - private static bool DrawFloatingPanelCloseButton(string title) + private bool DrawFloatingPanelCloseButton(string title) { ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); if ((window->Flags & (int)ImGuiWindowFlags.NoTitleBar) != 0) return false; ImGuiStyle* nativeStyle = NativeImGui.Raw.GetStyle(); float size = GetCompactIconSize().X; - Vector2 maximum = new(window->Pos.X + window->Size.X - nativeStyle->WindowBorderSize - nativeStyle->FramePadding.X, - window->Pos.Y + (window->TitleBarHeight + size) * 0.5f); + Vector2 maximum = new(window->Pos.X + window->Size.X - nativeStyle->WindowBorderSize - nativeStyle->FramePadding.X, window->Pos.Y + (window->TitleBarHeight + size) * 0.5f); Vector2 minimum = maximum - new Vector2(size); - ImRect bounds = new() { Min = ImGui.ToNative(minimum), Max = ImGui.ToNative(maximum) }; + ImRect bounds = new() + { + Min = ImGui.ToNative(minimum), + Max = ImGui.ToNative(maximum) + }; ImRect previousClip = window->ClipRect; ImDrawList* draw = NativeImGui.Raw.GetWindowDrawList(); Vector2 windowMaximum = ImGui.FromNative(window->Pos) + ImGui.FromNative(window->Size); - window->ClipRect = new ImRect { Min = window->Pos, Max = ImGui.ToNative(windowMaximum) }; + window->ClipRect = new ImRect + { + Min = window->Pos, + Max = ImGui.ToNative(windowMaximum) + }; ImGui.PushNativeClipRectangle(draw, ImGui.FromNative(window->Pos), windowMaximum, 0); try { uint id = NativeImGui.GetID("##floating_panel_close"); bool hovered = false, held = false; - bool pressed = NativeImGui.Raw.ItemAdd(&bounds, id, null, 0) != 0 && NativeImGui.ButtonBehavior( - bounds, id, out hovered, out held, - (int)ImGuiButtonFlagsPrivate.NoNavFocus | (int)ImGuiButtonFlagsPrivate.PressedOnClickRelease); - uint iconColor = hovered || held - ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) - : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); + bool pressed = NativeImGui.Raw.ItemAdd( + &bounds, + id, + null, + 0 + ) != 0 + && NativeImGui.ButtonBehavior( + bounds, + id, + out hovered, + out held, + (int)ImGuiButtonFlagsPrivate.NoNavFocus | (int)ImGuiButtonFlagsPrivate.PressedOnClickRelease + ); + uint iconColor = hovered || held ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); DrawPanelCloseMark(draw, minimum + new Vector2(size * 0.5f), size, iconColor); if (hovered) DrawItemTooltip($"Close {title}"); @@ -322,25 +306,17 @@ private static bool DrawFloatingPanelCloseButton(string title) } } - private static void DrawPanelCloseMark( + private void DrawPanelCloseMark( ImDrawList* drawList, Vector2 center, float slotSize, uint color ) { float thickness = MathF.Max(1f, style.borderSize); - float extent = MathF.Max( - thickness, - slotSize * 0.5f * 0.7071f - thickness); + float extent = MathF.Max(thickness, slotSize * 0.5f * 0.7071f - thickness); var diagonal = new Vector2(extent); ImGui.DrawNativeLine(drawList, center - diagonal, center + diagonal, color, thickness); - ImGui.DrawNativeLine( - drawList, - center + new Vector2(-extent, extent), - center + new Vector2(extent, -extent), - color, - thickness - ); + ImGui.DrawNativeLine(drawList, center + new Vector2(-extent, extent), center + new Vector2(extent, -extent), color, thickness); } /// @@ -352,14 +328,14 @@ uint color /// /// Thrown when is . /// - public static void Hint(string text) + public void Hint(string text) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); NativeImGui.BeginDisabled(true); try { - NativeImGui.PushTextWrapPos( - NativeImGui.GetCursorPosX() + MathF.Max(1f, NativeImGui.GetContentRegionAvail().X)); + NativeImGui.PushTextWrapPos(NativeImGui.GetCursorPosX() + MathF.Max(1f, NativeImGui.GetContentRegionAvail().X)); try { NativeImGui.TextUnformatted(text); diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs index 2a6ae67a0..5c4439f5a 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Property.cs @@ -1,20 +1,16 @@ using System; using System.Numerics; -using System.Text; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { private const string C_COMPACT_FLOAT_FORMAT = "%.1f"; private const string C_PRECISE_FLOAT_FORMAT = "%.9g"; - /// /// Draws a two-column property row with a stable internal identifier. /// @@ -34,19 +30,17 @@ public static unsafe partial class ImGuiWidget /// Thrown when is empty or whitespace. /// /// - /// Thrown when or is . + /// Thrown when or is . /// - public static void PropertyRow( + public void PropertyRow( string id, string label, Action drawValue, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); - PropertyRow( - id, - () => PropertyLabel(label, tooltip), - drawValue); + PropertyRow(id, () => PropertyLabel(label, tooltip), drawValue); } /// @@ -65,20 +59,18 @@ public static void PropertyRow( /// Thrown when is empty or whitespace. /// /// - /// Thrown when or is . + /// Thrown when or is . /// - public static void PropertyRow( + public void PropertyRow( string id, Action drawLabel, Action drawValue ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(drawLabel); ArgumentNullException.ThrowIfNull(drawValue); - - ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp - | ImGuiTableFlags.NoSavedSettings - | ImGuiTableFlags.NoPadOuterX; + ImGuiTableFlags flags = ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.NoPadOuterX; if (!NativeImGui.BeginTable($"##property_{id}", 2, (int)flags, Vector2.Zero, 0f)) { return; @@ -105,8 +97,9 @@ Action drawValue /// Configures the label and input columns of an active table with a shared two-to-three ratio. /// A following fixed-width action column may be added by the caller. /// - public static void SetupPropertyColumns() + public void SetupPropertyColumns() { + m_session.EnsureDrawing(); NativeImGui.TableSetupColumn("##label", (int)(ImGuiTableColumnFlags.WidthStretch), style.propertyLabelRatio); NativeImGui.TableSetupColumn("##value", (int)(ImGuiTableColumnFlags.WidthStretch), 1f - style.propertyLabelRatio); } @@ -123,10 +116,11 @@ public static void SetupPropertyColumns() /// /// Thrown when is null. /// - public static void PropertyLabel( + public void PropertyLabel( string label, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(label); NativeImGui.AlignTextToFramePadding(); WrappedText(label); @@ -146,14 +140,15 @@ public static void PropertyLabel( /// Optional explanation displayed while the metadata is hovered. /// /// - /// Thrown when or is + /// Thrown when or is /// . /// - public static void MetadataValue( + public void MetadataValue( string metadata, Action drawValue, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(metadata); ArgumentNullException.ThrowIfNull(drawValue); NativeImGui.AlignTextToFramePadding(); @@ -179,14 +174,15 @@ public static void MetadataValue( /// Optional explanation displayed while the combined value is hovered. /// /// - /// Thrown when or is + /// Thrown when or is /// . /// - public static void MetadataValue( + public void MetadataValue( string metadata, string value, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(metadata); ArgumentNullException.ThrowIfNull(value); NativeImGui.PushStyleColor((int)(ImGuiCol.Text), EditorPalette.textDisabled); @@ -222,13 +218,14 @@ public static void MetadataValue( /// /// when the value changed. /// - public static bool AxisDragFloat( + public bool AxisDragFloat( string id, string axis, ref float value, float width, float speed = 0.1f ) { + m_session.EnsureDrawing(); DrawAxisPrefix(id, axis, width); return CompactDragFloat($"##axis_float_{id}_{axis}", ref value, speed); } @@ -254,29 +251,20 @@ public static bool AxisDragFloat( /// /// when the exact value changed. /// - public static bool CompactDragFloat( + public bool CompactDragFloat( string label, ref float value, float speed = 0.1f, float? minimum = null, float? maximum = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(label); ValidateFloatBounds(minimum, maximum); bool bounded = minimum.HasValue; - ImGuiSliderFlags flags = ImGuiSliderFlags.NoRoundToFormat | - (bounded ? ImGuiSliderFlags.AlwaysClamp : ImGuiSliderFlags.None); - string format = WantsPreciseFloatInput(label) - ? C_PRECISE_FLOAT_FORMAT - : C_COMPACT_FLOAT_FORMAT; - return NativeImGui.DragFloat( - label, - ref value, - speed, - minimum ?? 0f, - maximum ?? 0f, - format, - (int)(flags)); + ImGuiSliderFlags flags = ImGuiSliderFlags.NoRoundToFormat | (bounded ? ImGuiSliderFlags.AlwaysClamp : ImGuiSliderFlags.None); + string format = WantsPreciseFloatInput(label) ? C_PRECISE_FLOAT_FORMAT : C_COMPACT_FLOAT_FORMAT; + return NativeImGui.DragFloat(label, ref value, speed, minimum ?? 0f, maximum ?? 0f, format, (int)(flags)); } /// @@ -297,34 +285,23 @@ public static bool CompactDragFloat( /// /// when the exact value changed. /// - public static bool CompactSliderFloat( + public bool CompactSliderFloat( string label, ref float value, float minimum, float maximum ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(label); ValidateFloatBounds(minimum, maximum); ImGuiSliderFlags flags = ImGuiSliderFlags.NoRoundToFormat | ImGuiSliderFlags.AlwaysClamp; if (WantsPreciseFloatInput(label)) { float speed = MathF.Max((maximum - minimum) * 0.01f, 0.0001f); - return NativeImGui.DragFloat( - label, - ref value, - speed, - minimum, - maximum, - C_PRECISE_FLOAT_FORMAT, - (int)(flags)); + return NativeImGui.DragFloat(label, ref value, speed, minimum, maximum, C_PRECISE_FLOAT_FORMAT, (int)(flags)); } - return NativeImGui.SliderFloat( - label, - ref value, - minimum, - maximum, - C_COMPACT_FLOAT_FORMAT, - (int)(flags)); + + return NativeImGui.SliderFloat(label, ref value, minimum, maximum, C_COMPACT_FLOAT_FORMAT, (int)(flags)); } /// @@ -348,13 +325,14 @@ float maximum /// /// when the value changed. /// - public static bool AxisDragInt( + public bool AxisDragInt( string id, string axis, ref int value, float width, float speed = 1f ) { + m_session.EnsureDrawing(); DrawAxisPrefix(id, axis, width); return NativeImGui.DragInt($"##axis_int_{id}_{axis}", ref value, speed, 0, 0, "%d", 0); } @@ -368,10 +346,11 @@ public static bool AxisDragInt( /// /// Drawing callback. /// - public static void Disabled( + public void Disabled( bool disabled, Action draw ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(draw); NativeImGui.BeginDisabled(disabled); try @@ -396,19 +375,14 @@ Action draw /// /// Marker Y coordinate. /// - public static void InsertionLine( + public void InsertionLine( float fromX, float toX, float y ) { + m_session.EnsureDrawing(); uint color = NativeImGui.GetColorU32((int)(ImGuiCol.DragDropTarget)); - ImGui.DrawNativeLine( - NativeImGui.Raw.GetForegroundDrawList((ImGuiViewport*)null), - new Vector2(fromX, y), - new Vector2(toX, y), - color, - style.interactionOverlayThickness - ); + ImGui.DrawNativeLine(NativeImGui.Raw.GetForegroundDrawList((ImGuiViewport*)null), new Vector2(fromX, y), new Vector2(toX, y), color, style.interactionOverlayThickness); } /// @@ -421,10 +395,11 @@ float y /// /// Maximum target coordinate. /// - public static void DropTargetHighlight( + public void DropTargetHighlight( Vector2 min, Vector2 max ) { + m_session.EnsureDrawing(); ImGui.DrawNativeRectangle( NativeImGui.Raw.GetForegroundDrawList((ImGuiViewport*)null), min, @@ -436,98 +411,39 @@ Vector2 max ); } - /// - /// Converts an identifier into a readable editor label. - /// - /// - /// Source identifier. - /// - /// - /// A label with separators and title casing. - /// - public static string NicifyName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - return string.Empty; - } - - var builder = new StringBuilder(name.Length + 8); - char previous = '\0'; - for (int i = 0; i < name.Length; i++) - { - char current = name[i]; - if (current == '_' || current == '-') - { - if (builder.Length > 0 && builder[builder.Length - 1] != ' ') - { - builder.Append(' '); - } - - previous = current; - continue; - } - - if (i > 0 && char.IsUpper(current) && char.IsLower(previous) && builder[builder.Length - 1] != ' ') - { - builder.Append(' '); - } - - builder.Append(builder.Length == 0 ? char.ToUpperInvariant(current) : current); - previous = current; - } - - return builder.ToString(); - } - - private static void DrawAxisPrefix( + private void DrawAxisPrefix( string id, string axis, float width ) { ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentException.ThrowIfNullOrWhiteSpace(axis); - float height = NativeImGui.GetFrameHeight(); - float axisWidth = MathF.Min( - MathF.Max(style.axisPrefixMinimumWidth, height), - width * style.axisPrefixWidthRatio); + float axisWidth = MathF.Min(MathF.Max(style.axisPrefixMinimumWidth, height), width * style.axisPrefixWidthRatio); Vector2 prefixMin = NativeImGui.GetCursorScreenPos(); Vector2 prefixMax = prefixMin + new Vector2(axisWidth, height); - ImGui.DrawNativeFilledRectangle( - NativeImGui.Raw.GetWindowDrawList(), - prefixMin, - prefixMax, - NativeImGui.ColorConvertFloat4ToU32(GetAxisColor(axis)), - 1f, - 0 - ); - + ImGui.DrawNativeFilledRectangle(NativeImGui.Raw.GetWindowDrawList(), prefixMin, prefixMax, NativeImGui.ColorConvertFloat4ToU32(GetAxisColor(axis)), 1f, 0); Vector2 textSize = NativeImGui.CalcTextSize(axis); Vector2 textPosition = prefixMin + (new Vector2(axisWidth, height) - textSize) * 0.5f; ImGui.DrawNativeText(NativeImGui.Raw.GetWindowDrawList(), textPosition, NativeImGui.ColorConvertFloat4ToU32(EditorPalette.text), axis); - NativeImGui.Dummy(new Vector2(axisWidth, height)); NativeImGui.SameLine(0f, 0f); NativeImGui.SetNextItemWidth(MathF.Max(1f, width - axisWidth)); } - private static bool WantsPreciseFloatInput(string label) + private bool WantsPreciseFloatInput(string label) { - bool inputRequested = NativeImGui.IsMouseDoubleClicked((int)(ImGuiMouseButton.Left)) || - (NativeImGui.Raw.GetIO()->KeyCtrl != 0 && - NativeImGui.IsMouseClicked((int)(ImGuiMouseButton.Left))); + bool inputRequested = NativeImGui.IsMouseDoubleClicked((int)(ImGuiMouseButton.Left)) + || (NativeImGui.Raw.GetIO()->KeyCtrl != 0 + && NativeImGui.IsMouseClicked((int)(ImGuiMouseButton.Left))); if (!inputRequested) return false; Vector2 minimum = NativeImGui.GetCursorScreenPos(); - Vector2 maximum = minimum + new Vector2( - MathF.Max(1f, NativeImGui.CalcItemWidth()), - NativeImGui.GetFrameHeight()); - return NativeImGui.IsWindowHovered((int)(ImGuiHoveredFlags.RootAndChildWindows)) - && NativeImGui.IsMouseHoveringRect(minimum, maximum, true); + Vector2 maximum = minimum + new Vector2(MathF.Max(1f, NativeImGui.CalcItemWidth()), NativeImGui.GetFrameHeight()); + return NativeImGui.IsWindowHovered((int)(ImGuiHoveredFlags.RootAndChildWindows)) && NativeImGui.IsMouseHoveringRect(minimum, maximum, true); } - private static void ValidateFloatBounds( + private void ValidateFloatBounds( float? minimum, float? maximum ) { @@ -535,13 +451,14 @@ private static void ValidateFloatBounds( { throw new ArgumentException("Floating-point bounds must either both be supplied or both be omitted."); } + if (minimum is not float lower || maximum is not float upper) return; if (!float.IsFinite(lower) || !float.IsFinite(upper) || lower > upper) throw new ArgumentOutOfRangeException(nameof(maximum), "Floating-point bounds must be finite and ordered."); } - private static Vector4 GetAxisColor(string axis) + private Vector4 GetAxisColor(string axis) { return axis.ToUpperInvariant() switch { diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs index 47d3cb16e..692b20460 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Search.cs @@ -1,18 +1,19 @@ using System; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static partial class ImGuiWidget +public sealed partial class ImGuiWidget { /// /// Draws a single-line search field with a stable identifier. /// + /// + /// Escape clears a nonempty query before leaving text input on a subsequent press. + /// /// /// The stable control identifier. /// @@ -31,16 +32,17 @@ public static partial class ImGuiWidget /// /// when the query changed; otherwise, . /// - public static bool SearchInput( + public bool SearchInput( string id, string hint, ref string query, nuint capacity = 256, float width = -1f ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); NativeImGui.SetNextItemWidth(width < 0f ? MathF.Max(1f, NativeImGui.GetContentRegionAvail().X) : width); - return ImGuiUtf8Buffer.InputText($"##search_{id}", hint, ref query, capacity, ImGuiInputTextFlags.None); + return ImGuiUtf8Buffer.InputText($"##search_{id}", hint, ref query, capacity, ImGuiInputTextFlags.EscapeClearsAll); } /// @@ -64,13 +66,14 @@ public static bool SearchInput( /// /// when popup content should be drawn; otherwise, . /// - public static bool BeginSearchPopup( + public bool BeginSearchPopup( string id, ref string query, string hint, nuint capacity = 256, float width = -1f ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); if (width < 0f) width = style.searchPopupWidth; @@ -86,5 +89,5 @@ public static bool BeginSearchPopup( /// /// Ends a popup opened by . /// - public static void EndSearchPopup() => EndMenuPopup(); + public void EndSearchPopup() => EndMenuPopup(); } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs new file mode 100644 index 000000000..2d1d62f43 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selection.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using Inno.Core.Identity; +using Inno.Core.Input; +using Inno.Editor.Core; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui.ImGuiWidget; +/// +/// Applies ordered selection gestures through the authoritative interaction owner. +/// +public sealed unsafe partial class ImGuiWidget +{ + /// + /// Applies ordinary, additive or visible-range selection using the active keyboard modifiers. + /// + /// + /// The session's authoritative interaction service. + /// + /// + /// The freshly resolved clicked target. + /// + /// + /// Current runtime-identity view order; collapsed or filtered items must be omitted. + /// + public void SelectTarget( + EditorInteractions interactions, + IdentityObject target, + IReadOnlyList visibleTargets + ) { + m_session.EnsureDrawing(); + ArgumentNullException.ThrowIfNull(interactions); + ArgumentNullException.ThrowIfNull(target); + ArgumentNullException.ThrowIfNull(visibleTargets); + ImGuiIO* io = NativeImGui.Raw.GetIO(); + bool additive = HasPrimaryModifier(interactions.keyboard, io); + if (io->KeyShift != 0) + interactions.SelectRange(visibleTargets, target, additive); + else if (additive) + interactions.ToggleSelection(target); + else + interactions.SetSelection(target); + } + + /// + /// Reads one focused list's keyboard navigation without retaining its entries or changing domain selection. + /// + /// + /// The authoritative interaction owner supplying the product modifier policy. + /// + /// + /// The number of currently visible, ordered entries. + /// + /// + /// The current visible focus index, or minus one when absent. + /// + /// + /// The positive number of visible rows advanced by Page Up or Page Down. + /// + /// + /// Neutral focus and selection intents; text editing, active controls and popups produce no intent. + /// + /// + /// The interaction owner is null. + /// + /// + /// The count, focus index or page size is invalid. + /// + /// + /// No matching owner-thread drawing session is active. + /// + public ListNavigationResult NavigateList( + EditorInteractions interactions, + int itemCount, + int focusedIndex, + int pageSize = 10 + ) { + m_session.EnsureDrawing(); + ArgumentNullException.ThrowIfNull(interactions); + ArgumentOutOfRangeException.ThrowIfNegative(itemCount); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageSize); + if (focusedIndex < -1 || focusedIndex >= itemCount && focusedIndex != -1) + throw new ArgumentOutOfRangeException(nameof(focusedIndex)); + ImGuiIO* io = NativeImGui.Raw.GetIO(); + ImGuiNativeContext* context = NativeImGui.Raw.GetCurrentContext(); + bool ownsActiveRow = RouteTreeNavigationKeys(context); + if (!NativeImGui.IsWindowFocused((int)Inno.Native.ImGui.ImGuiFocusedFlags.ChildWindows) + || io->WantTextInput != 0 || (context->ActiveId != 0 && !ownsActiveRow) || ImGui.IsAnyPopupOpen()) + return new(focusedIndex, false, false, false, false, false, false, false); + int next = focusedIndex; + bool navigation = false; + if (itemCount > 0) + { + navigation = true; + if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.DownArrow)) + next = focusedIndex < 0 ? 0 : Math.Min(itemCount - 1, focusedIndex + 1); + else if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.UpArrow)) + next = focusedIndex < 0 ? itemCount - 1 : Math.Max(0, focusedIndex - 1); + else if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.Home)) + next = 0; + else if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.End)) + next = itemCount - 1; + else if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.PageDown)) + next = focusedIndex < 0 ? 0 : (int)Math.Min(itemCount - 1L, (long)focusedIndex + pageSize); + else if (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.PageUp)) + next = focusedIndex < 0 ? 0 : Math.Max(0, focusedIndex - pageSize); + else + navigation = false; + } + bool primary = HasPrimaryModifier(interactions.keyboard, io); + bool extend = io->KeyShift != 0; + bool space = itemCount > 0 && NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.Space, false); + bool activate = itemCount > 0 && (NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.Enter, false) + || NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.KeypadEnter, false)); + bool cancel = NativeImGui.IsKeyPressed(Inno.Native.ImGui.ImGuiKey.Escape, false); + if ((space || activate) && next < 0) + next = 0; + return new(next, next != focusedIndex, space || navigation && (!primary || extend), + extend, primary, space && primary, activate, cancel); + } + + private bool RouteTreeNavigationKeys(ImGuiNativeContext* context) + { + uint window = NativeImGui.Raw.GetCurrentWindow()->ID; + if (!m_treeStatesByWindow.TryGetValue((m_session.owner, window), out TreeWidgetWindowState? state) + || state.lastFrame != NativeImGui.GetFrameCount() + || !NativeImGui.IsWindowFocused((int)Inno.Native.ImGui.ImGuiFocusedFlags.ChildWindows) + || NativeImGui.Raw.GetIO()->WantTextInput != 0 || ImGui.IsAnyPopupOpen()) + return false; + bool ownsActiveRow = context->ActiveId != 0 + && context->ActiveIdSource == ImGuiInputSource.Keyboard && state.navigationItemIds.Contains(context->ActiveId); + if (state.navigationItemIds.Contains(context->NavId) && (context->ActiveId == 0 || ownsActiveRow)) + { + // Native navigation must not activate the disclosure while the list owns selection/activation. + // A distinct owner preserves text fields and overlay buttons, including their native navigation. + uint owner = NativeImGui.GetID("##inno_list_navigation"); + NativeImGui.SetKeyOwner(Inno.Native.ImGui.ImGuiKey.Space, owner, 0); + NativeImGui.SetKeyOwner(Inno.Native.ImGui.ImGuiKey.Enter, owner, 0); + NativeImGui.SetKeyOwner(Inno.Native.ImGui.ImGuiKey.KeypadEnter, owner, 0); + } + return ownsActiveRow; + } + + private static bool HasPrimaryModifier( + EditorKeyboardPolicy keyboard, + ImGuiIO* io + ) { + var input = new EditorUiInput(io); + KeyModifier modifiers = KeyModifier.None; + if (input.control) + modifiers |= KeyModifier.Control; + if (input.super) + modifiers |= KeyModifier.Super; + if (input.alt) + modifiers |= KeyModifier.Alt; + if (input.shift) + modifiers |= KeyModifier.Shift; + return (modifiers & keyboard.primaryModifier) == keyboard.primaryModifier; + } +} + +/// +/// Carries frame-local list navigation intents over visible indices without owning objects or selection state. +/// +public readonly struct ListNavigationResult +{ + internal ListNavigationResult( + int focusedIndex, + bool focusChanged, + bool selectionRequested, + bool extendRange, + bool additive, + bool toggle, + bool activate, + bool cancel + ) { + this.focusedIndex = focusedIndex; + this.focusChanged = focusChanged; + this.selectionRequested = selectionRequested; + this.extendRange = extendRange; + this.additive = additive; + this.toggle = toggle; + this.activate = activate; + this.cancel = cancel; + } + + /// + /// Gets the resulting visible focus index, or minus one for no focus. + /// + public int focusedIndex { get; } + /// + /// Gets whether the visible focus index changed. + /// + public bool focusChanged { get; } + /// + /// Gets whether the caller should apply a domain selection operation. + /// + public bool selectionRequested { get; } + /// + /// Gets whether the selection should extend from its existing anchor. + /// + public bool extendRange { get; } + /// + /// Gets whether the product's primary modifier requests preserving other selected entries. + /// + public bool additive { get; } + /// + /// Gets whether Space with the primary modifier requests toggling the focused entry. + /// + public bool toggle { get; } + /// + /// Gets whether Enter requests activation of the focused entry. + /// + public bool activate { get; } + /// + /// Gets whether Escape requests canceling the current list workflow. + /// + public bool cancel { get; } +} diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs index 6ce56b59b..70cf5fe24 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Selector.cs @@ -1,16 +1,13 @@ using System; using System.Numerics; - using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { /// /// Begins a combo whose popup opens below the control and stays within its containing window. @@ -37,28 +34,25 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when contains a presentation mode unsupported by the editor selector. /// - public static bool BeginBoundedCombo( + public bool BeginBoundedCombo( string id, string preview, ImGuiComboFlags flags = ImGuiComboFlags.None ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(preview); if ((flags & ~ImGuiComboFlags.WidthFitPreview) != 0) throw new ArgumentOutOfRangeException(nameof(flags), flags, "The combo flag is not supported by the editor selector."); - ImGuiStyle* nativeStyle = NativeImGui.Raw.GetStyle(); - float width = (flags & ImGuiComboFlags.WidthFitPreview) != 0 - ? NativeImGui.CalcTextSize(preview).X + NativeImGui.GetFrameHeight() + nativeStyle->FramePadding.X * 2f - : NativeImGui.CalcItemWidth(); + float width = (flags & ImGuiComboFlags.WidthFitPreview) != 0 ? NativeImGui.CalcTextSize(preview).X + NativeImGui.GetFrameHeight() + nativeStyle->FramePadding.X * 2f : NativeImGui.CalcItemWidth(); return BeginMenuSelector(id, preview, MathF.Max(1f, width), MathF.Max(1f, width)); } /// /// Ends a combo opened by . /// - public static void EndBoundedCombo() => EndMenuSelector(); - + public void EndBoundedCombo() => EndMenuSelector(); /// /// Draws a compact selector control and begins a work-area-bounded menu popup. /// @@ -85,24 +79,22 @@ public static bool BeginBoundedCombo( /// Thrown when is . /// /// - /// Thrown when or is not positive. + /// Thrown when or is not positive. /// - public static bool BeginMenuSelector( + public bool BeginMenuSelector( string id, string preview, float width, float minimumPopupWidth ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(preview); if (width <= 0f) throw new ArgumentOutOfRangeException(nameof(width), width, "Selector width must be positive."); if (minimumPopupWidth <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(minimumPopupWidth), - minimumPopupWidth, - "Popup width must be positive."); + throw new ArgumentOutOfRangeException(nameof(minimumPopupWidth), minimumPopupWidth, "Popup width must be positive."); } string popupId = $"##menu_selector_popup_{id}"; @@ -115,25 +107,20 @@ float minimumPopupWidth bool open = NativeImGui.IsPopupOpen(popupId); if (pressed) NativeImGui.OpenPopup(popupId); - DrawMenuSelectorFrame(minimum, size, preview, hovered, active || open); Vector2 popupOrigin = new(minimum.X, minimum.Y + height); NativeImGui.SetNextWindowPos(popupOrigin, (int)ImGuiCond.Always, Vector2.Zero); ImGuiViewport* parentViewport = NativeImGui.Raw.GetWindowViewport(); NativeImGui.SetNextWindowViewport(parentViewport->ID); - SetWorkAreaPopupSize( - MathF.Max(width, minimumPopupWidth), - popupOrigin, - parentViewport); + SetWorkAreaPopupSize(MathF.Max(width, minimumPopupWidth), popupOrigin, parentViewport); return BeginMenuPopup(popupId); } /// /// Ends a selector popup opened by . /// - public static void EndMenuSelector() => EndMenuPopup(); - - private static void SetWorkAreaPopupSize( + public void EndMenuSelector() => EndMenuPopup(); + private void SetWorkAreaPopupSize( float requestedWidth, Vector2 popupOrigin, ImGuiViewport* viewport @@ -145,12 +132,10 @@ private static void SetWorkAreaPopupSize( float maximumHeight = MathF.Min(availableHeight, containingWindow->Size.Y * 0.45f); float availableWidth = MathF.Max(1f, MathF.Min(windowMaximum.X, viewportMaximum.X) - popupOrigin.X); float popupWidth = Math.Clamp(requestedWidth, 1f, availableWidth); - SetMenuPopupSizeConstraints( - new Vector2(popupWidth, 0f), - new Vector2(popupWidth, MathF.Max(1f, maximumHeight))); + SetMenuPopupSizeConstraints(new Vector2(popupWidth, 0f), new Vector2(popupWidth, MathF.Max(1f, maximumHeight))); } - private static void DrawMenuSelectorFrame( + private void DrawMenuSelectorFrame( Vector2 minimum, Vector2 size, string preview, @@ -158,25 +143,14 @@ private static void DrawMenuSelectorFrame( bool active ) { ImGuiStyle* nativeStyle = NativeImGui.Raw.GetStyle(); - uint background = NativeImGui.GetColorU32( - (int)(active ? ImGuiCol.FrameBgActive : hovered ? ImGuiCol.FrameBgHovered : ImGuiCol.FrameBg)); + uint background = NativeImGui.GetColorU32((int)(active ? ImGuiCol.FrameBgActive : hovered ? ImGuiCol.FrameBgHovered : ImGuiCol.FrameBg)); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); Vector2 maximum = minimum + size; float arrowSlot = size.Y; float arrowMinimumX = MathF.Max(minimum.X, maximum.X - arrowSlot); - ImDrawFlags previewRounding = size.X <= arrowSlot - ? ImDrawFlags.RoundCornersAll - : ImDrawFlags.RoundCornersLeft; - ImGui.DrawNativeFilledRectangle( - drawList, - minimum, - new Vector2(arrowMinimumX, maximum.Y), - background, - nativeStyle->FrameRounding, - (int)(previewRounding) - ); - uint arrowBackground = NativeImGui.GetColorU32( - (int)(active || hovered ? ImGuiCol.ButtonHovered : ImGuiCol.Button)); + ImDrawFlags previewRounding = size.X <= arrowSlot ? ImDrawFlags.RoundCornersAll : ImDrawFlags.RoundCornersLeft; + ImGui.DrawNativeFilledRectangle(drawList, minimum, new Vector2(arrowMinimumX, maximum.Y), background, nativeStyle->FrameRounding, (int)(previewRounding)); + uint arrowBackground = NativeImGui.GetColorU32((int)(active || hovered ? ImGuiCol.ButtonHovered : ImGuiCol.Button)); ImGui.DrawNativeFilledRectangle( drawList, new Vector2(arrowMinimumX, minimum.Y), @@ -185,7 +159,6 @@ bool active nativeStyle->FrameRounding, (int)(size.X <= arrowSlot ? ImDrawFlags.RoundCornersAll : ImDrawFlags.RoundCornersRight) ); - Vector2 textMinimum = minimum + ImGui.FromNative(nativeStyle->FramePadding); Vector2 textMaximum = new(arrowMinimumX, maximum.Y); ImGui.PushNativeClipRectangle(drawList, textMinimum, textMaximum, 1); @@ -196,7 +169,8 @@ bool active new Vector2(arrowMinimumX + nativeStyle->FramePadding.Y, minimum.Y + nativeStyle->FramePadding.Y), NativeImGui.GetColorU32((int)(ImGuiCol.Text)), ImGuiDir.Down, - 1f); + 1f + ); NativeImGui.RenderFrameBorder(minimum, maximum, nativeStyle->FrameRounding); } } diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs index c1cd3daee..0f0ca7bbd 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Style.cs @@ -1,45 +1,43 @@ using System; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { - private static float s_appliedZoom = float.NaN; - private static bool? s_appliedCompactMode; - + private float m_appliedZoom = float.NaN; + private bool? m_appliedCompactMode; /// /// Gets the centralized editor layout metrics shared by every widget and feature panel. /// - public static EditorStyleMetrics style { get; } = new(); + public EditorStyleMetrics style { get; } /// /// Applies the centralized editor layout metrics and palette to the current ImGui context. /// - public static void SetupStyle() + public void SetupStyle() { + m_session.EnsureDrawing(); + m_session.PrepareStyleInput(); ImGuiStyle* nativeStyle = NativeImGui.Raw.GetStyle(); ApplyLayoutStyle(nativeStyle); EditorPalette.Apply(nativeStyle); - s_appliedZoom = style.zoom; - s_appliedCompactMode = style.isCompact; + m_appliedZoom = style.zoom; + m_appliedCompactMode = style.isCompact; } - internal static void ApplyPendingStyle() + internal void ApplyPendingStyle() { - if (float.IsNaN(s_appliedZoom) - || MathF.Abs(s_appliedZoom - style.zoom) > 0.0001f - || s_appliedCompactMode != style.isCompact) + m_session.PrepareStyleInput(); + if (float.IsNaN(m_appliedZoom) || MathF.Abs(m_appliedZoom - style.zoom) > 0.0001f || m_appliedCompactMode != style.isCompact) SetupStyle(); } - private static void ApplyLayoutStyle(ImGuiStyle* nativeStyle) + private void ApplyLayoutStyle(ImGuiStyle* nativeStyle) { nativeStyle->Alpha = EditorPalette.opacityOpaque; nativeStyle->DisabledAlpha = style.disabledAlpha; diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs index d3f09720f..2a4058778 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Text.cs @@ -1,18 +1,90 @@ using System; using System.Numerics; - using Inno.Scripting.Api; using Inno.Native.ImGui; -using Inno.Adapter.Presentation.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { + private int m_iconCatalogFrame = -1; + private float m_iconCatalogWidth; + /// + /// Measures the standard glyph column once per native frame in this UI session. + /// + /// + /// The widest visible standard glyph at the current font size, at least one text-line height. + /// + public float GetIconCatalogWidth() + { + m_session.EnsureDrawing(); + int frame = NativeImGui.GetFrameCount(); + if (m_iconCatalogFrame == frame) + return m_iconCatalogWidth; + float fontSize = NativeImGui.GetFontSize(); + float width = NativeImGui.GetTextLineHeight(); + foreach (var entry in EditorIconCatalog.standard) + { + Vector4 bounds = GetGlyphVisualBounds(fontSize, entry.Glyph); + width = MathF.Max(width, bounds.Z - bounds.X); + } + + m_iconCatalogWidth = width; + m_iconCatalogFrame = frame; + return width; + } + + /// + /// Measures a glyph using the current font without exposing its native font handle. + /// + /// + /// The positive baked font size. + /// + /// + /// A nonempty string whose first Unicode scalar identifies the glyph. + /// + /// + /// The glyph's visible left, top, right and bottom offsets. + /// + /// + /// The glyph text is empty. + /// + public Vector4 GetGlyphVisualBounds( + float fontSize, + string text + ) { + m_session.EnsureDrawing(); + return GetGlyphVisualBoundsNative(NativeImGui.Raw.GetFont(), fontSize, text); + } + + /// + /// Centers a glyph in the current window without exposing a native draw list. + /// + /// + /// The positive baked font size. + /// + /// + /// The glyph text. + /// + /// + /// The requested visible center in screen coordinates. + /// + /// + /// Packed text color. + /// + public void AddGlyphCentered( + float fontSize, + string text, + Vector2 center, + uint color + ) { + m_session.EnsureDrawing(); + AddGlyphCenteredNative(NativeImGui.Raw.GetWindowDrawList(), NativeImGui.Raw.GetFont(), fontSize, text, center, color); + } + /// /// Gets the visible bounds of the first glyph in a string at a requested font size. /// @@ -32,23 +104,19 @@ public static unsafe partial class ImGuiWidget /// /// Thrown when is empty. /// - [ScriptingApiIgnore] - public static Vector4 GetGlyphVisualBounds( + private Vector4 GetGlyphVisualBoundsNative( ImFont* font, float fontSize, string text ) { + m_session.EnsureDrawing(); if (string.IsNullOrEmpty(text)) throw new ArgumentException("Glyph text cannot be empty.", nameof(text)); - int codepoint = char.ConvertToUtf32(text, 0); ImFontBaked* baked = NativeImGui.Raw.GetFontBaked(font, fontSize, -1f); - ImFontGlyph* glyph = codepoint <= ushort.MaxValue - ? NativeImGui.Raw.FindGlyph(baked, (ushort)codepoint) - : null; + ImFontGlyph* glyph = codepoint <= ushort.MaxValue ? NativeImGui.Raw.FindGlyph(baked, (ushort)codepoint) : null; if (glyph != null) return new Vector4(glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); - NativeImGui.Raw.PushFont(font, fontSize); Vector2 fallbackSize = NativeImGui.CalcTextSize(text); NativeImGui.PopFont(); @@ -76,8 +144,7 @@ string text /// /// Packed ImGui text color. /// - [ScriptingApiIgnore] - public static void AddGlyphCentered( + private void AddGlyphCenteredNative( ImDrawList* drawList, ImFont* font, float fontSize, @@ -85,17 +152,10 @@ public static void AddGlyphCentered( Vector2 center, uint color ) { - Vector4 bounds = GetGlyphVisualBounds(font, fontSize, text); - Vector2 visibleCenterOffset = new( - (bounds.X + bounds.Z) * 0.5f, - (bounds.Y + bounds.W) * 0.5f); - ImGui.DrawNativeText( - drawList, - font, - fontSize, - center - visibleCenterOffset, - color, - text); + m_session.EnsureDrawing(); + Vector4 bounds = GetGlyphVisualBoundsNative(font, fontSize, text); + Vector2 visibleCenterOffset = new((bounds.X + bounds.Z) * 0.5f, (bounds.Y + bounds.W) * 0.5f); + ImGui.DrawNativeText(drawList, font, fontSize, center - visibleCenterOffset, color, text); } /// @@ -119,13 +179,11 @@ uint color /// /// Thrown when is . /// - public static bool ClickableText( + public bool ClickableText( string id, string text, string? tooltip = null - ) - => ClickableText(id, text, GetCompactClickableTextSize(), tooltip); - + ) => ClickableText(id, text, GetCompactClickableTextSize(), tooltip); /// /// Draws a text link with no button background and highlights it only through text presentation. /// @@ -144,10 +202,11 @@ public static bool ClickableText( /// /// Thrown when is . /// - public static bool HoverText( + public bool HoverText( string id, string text ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(text); Vector2 cursor = NativeImGui.GetCursorScreenPos(); @@ -155,25 +214,16 @@ string text bool pressed = NativeImGui.InvisibleButton($"##hover_text_{id}", size); bool hovered = NativeImGui.IsItemHovered(); bool active = NativeImGui.IsItemActive(); - Vector4 color = active - ? EditorPalette.accentActive - : hovered - ? EditorPalette.accentHovered - : EditorPalette.accentActive; + Vector4 color = active ? EditorPalette.accentActive : hovered ? EditorPalette.accentHovered : EditorPalette.accentActive; uint packedColor = NativeImGui.ColorConvertFloat4ToU32(color); ImGui.DrawNativeText(NativeImGui.Raw.GetWindowDrawList(), cursor, packedColor, text); if (hovered) { NativeImGui.SetMouseCursor((int)(ImGuiMouseCursor.Hand)); float underlineY = cursor.Y + size.Y; - ImGui.DrawNativeLine( - NativeImGui.Raw.GetWindowDrawList(), - new Vector2(cursor.X, underlineY), - new Vector2(cursor.X + size.X, underlineY), - packedColor, - 1f - ); + ImGui.DrawNativeLine(NativeImGui.Raw.GetWindowDrawList(), new Vector2(cursor.X, underlineY), new Vector2(cursor.X + size.X, underlineY), packedColor, 1f); } + return pressed; } @@ -199,11 +249,12 @@ string text /// /// Thrown when is . /// - public static bool ClickableIcon( + public bool ClickableIcon( string id, string icon, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(icon); Vector2 controlSize = GetCompactIconSize(); @@ -211,22 +262,14 @@ public static bool ClickableIcon( bool pressed = NativeImGui.InvisibleButton($"##clickable_icon_{id}", controlSize); bool hovered = NativeImGui.IsItemHovered(); bool active = NativeImGui.IsItemActive(); - uint color = hovered || active - ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) - : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); - AddGlyphCentered( - NativeImGui.Raw.GetWindowDrawList(), - NativeImGui.Raw.GetFont(), - NativeImGui.GetFontSize(), - icon, - cursor + controlSize * 0.5f, - color); - + uint color = hovered || active ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); + AddGlyphCenteredNative(NativeImGui.Raw.GetWindowDrawList(), NativeImGui.Raw.GetFont(), NativeImGui.GetFontSize(), icon, cursor + controlSize * 0.5f, color); if (!string.IsNullOrWhiteSpace(tooltip) && hovered && BeginMenuTooltip()) { NativeImGui.TextUnformatted(tooltip); EndMenuTooltip(); } + return pressed; } @@ -257,20 +300,18 @@ public static bool ClickableIcon( /// /// Thrown when either component of is not positive. /// - public static bool ClickableText( + public bool ClickableText( string id, string text, Vector2 controlSize, string? tooltip = null ) { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(text); if (controlSize.X <= 0f || controlSize.Y <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(controlSize), - controlSize, - "Clickable text size must be positive."); + throw new ArgumentOutOfRangeException(nameof(controlSize), controlSize, "Clickable text size must be positive."); } Vector2 textSize = NativeImGui.CalcTextSize(text); @@ -278,15 +319,7 @@ public static bool ClickableText( bool pressed = NativeImGui.InvisibleButton($"##clickable_text_{id}", controlSize); bool hovered = NativeImGui.IsItemHovered(); bool active = NativeImGui.IsItemActive(); - DrawClickableTextPresentation( - NativeImGui.Raw.GetWindowDrawList(), - cursor, - controlSize, - text, - textSize, - hovered, - active); - + DrawClickableTextPresentation(NativeImGui.Raw.GetWindowDrawList(), cursor, controlSize, text, textSize, hovered, active); if (!string.IsNullOrWhiteSpace(tooltip) && hovered && BeginMenuTooltip()) { NativeImGui.TextUnformatted(tooltip); @@ -311,17 +344,15 @@ public static bool ClickableText( /// /// Thrown when either component of is not positive. /// - public static void CenteredText( + public void CenteredText( string text, Vector2 areaSize ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); if (areaSize.X <= 0f || areaSize.Y <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(areaSize), - areaSize, - "Centered text area size must be positive."); + throw new ArgumentOutOfRangeException(nameof(areaSize), areaSize, "Centered text area size must be positive."); } Vector2 cursor = NativeImGui.GetCursorScreenPos(); @@ -348,53 +379,42 @@ Vector2 areaSize /// /// Thrown when an area component is not positive or a padding component is negative. /// - public static void CenteredWrappedText( + public void CenteredWrappedText( string text, Vector2 areaSize, Vector2 padding ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); if (areaSize.X <= 0f || areaSize.Y <= 0f) { - throw new ArgumentOutOfRangeException( - nameof(areaSize), - areaSize, - "Centered wrapped text area size must be positive."); + throw new ArgumentOutOfRangeException(nameof(areaSize), areaSize, "Centered wrapped text area size must be positive."); } + if (padding.X < 0f || padding.Y < 0f) { - throw new ArgumentOutOfRangeException( - nameof(padding), - padding, - "Centered wrapped text padding cannot be negative."); + throw new ArgumentOutOfRangeException(nameof(padding), padding, "Centered wrapped text padding cannot be negative."); } Vector2 origin = NativeImGui.GetCursorScreenPos(); - Vector2 effectivePadding = new( - MathF.Min(padding.X, MathF.Max(0f, (areaSize.X - 1f) * 0.5f)), - MathF.Min(padding.Y, MathF.Max(0f, (areaSize.Y - 1f) * 0.5f))); + Vector2 effectivePadding = new(MathF.Min(padding.X, MathF.Max(0f, (areaSize.X - 1f) * 0.5f)), MathF.Min(padding.Y, MathF.Max(0f, (areaSize.Y - 1f) * 0.5f))); float wrapWidth = MathF.Max(1f, areaSize.X - effectivePadding.X * 2f); Vector2 textSize = NativeImGui.CalcTextSize(text, hide_text_after_double_hash: false, wrapWidth); Vector2 textPosition = origin + new Vector2( MathF.Max(effectivePadding.X, (areaSize.X - textSize.X) * 0.5f), - MathF.Max(effectivePadding.Y, (areaSize.Y - textSize.Y) * 0.5f)); + MathF.Max(effectivePadding.Y, (areaSize.Y - textSize.Y) * 0.5f) + ); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); ImGui.PushNativeClipRectangle(drawList, origin + effectivePadding, origin + areaSize - effectivePadding, 1); try { - ImGui.DrawNativeText( - drawList, - NativeImGui.Raw.GetFont(), - NativeImGui.GetFontSize(), - textPosition, - NativeImGui.GetColorU32((int)(ImGuiCol.Text)), - text, - wrapWidth); + ImGui.DrawNativeText(drawList, NativeImGui.Raw.GetFont(), NativeImGui.GetFontSize(), textPosition, NativeImGui.GetColorU32((int)(ImGuiCol.Text)), text, wrapWidth); } finally { NativeImGui.Raw.PopClipRect(drawList); } + NativeImGui.Dummy(areaSize); } @@ -410,10 +430,11 @@ Vector2 padding /// /// Thrown when is . /// - public static void ColoredText( + public void ColoredText( Vector4 color, string text ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); NativeImGui.PushStyleColor((int)(ImGuiCol.Text), color); try @@ -436,11 +457,11 @@ string text /// /// Thrown when is . /// - public static void WrappedText(string text) + public void WrappedText(string text) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); - NativeImGui.PushTextWrapPos( - NativeImGui.GetCursorPosX() + MathF.Max(1f, NativeImGui.GetContentRegionAvail().X)); + NativeImGui.PushTextWrapPos(NativeImGui.GetCursorPosX() + MathF.Max(1f, NativeImGui.GetContentRegionAvail().X)); try { NativeImGui.TextUnformatted(text); @@ -469,17 +490,15 @@ public static void WrappedText(string text) /// /// Thrown when either component of is negative. /// - public static Vector2 GetClickableTextSize( + public Vector2 GetClickableTextSize( string text, Vector2 padding ) { + m_session.EnsureDrawing(); ArgumentNullException.ThrowIfNull(text); if (padding.X < 0f || padding.Y < 0f) { - throw new ArgumentOutOfRangeException( - nameof(padding), - padding, - "Clickable text padding cannot be negative."); + throw new ArgumentOutOfRangeException(nameof(padding), padding, "Clickable text padding cannot be negative."); } Vector2 textSize = NativeImGui.CalcTextSize(text); @@ -492,10 +511,11 @@ Vector2 padding /// /// A fixed-size area that follows the editor icon slot convention. /// - public static Vector2 GetCompactClickableTextSize() + public Vector2 GetCompactClickableTextSize() { + m_session.EnsureDrawing(); float iconSlotWidth = NativeImGui.GetTextLineHeight(); - return new Vector2(iconSlotWidth + style.iconLabelSpacing, NativeImGui.GetFrameHeight()); + return new Vector2(iconSlotWidth + this.style.iconLabelSpacing, NativeImGui.GetFrameHeight()); } /// @@ -504,13 +524,14 @@ public static Vector2 GetCompactClickableTextSize() /// /// A square size based on the current ImGui font size. /// - public static Vector2 GetCompactIconSize() + public Vector2 GetCompactIconSize() { + m_session.EnsureDrawing(); float iconSlotSize = NativeImGui.GetFontSize(); return new Vector2(iconSlotSize); } - private static void DrawClickableTextPresentation( + private void DrawClickableTextPresentation( ImDrawList* drawList, Vector2 minimum, Vector2 controlSize, @@ -519,9 +540,7 @@ private static void DrawClickableTextPresentation( bool hovered, bool active ) { - uint color = hovered || active - ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) - : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); + uint color = hovered || active ? NativeImGui.ColorConvertFloat4ToU32(EditorPalette.compactControlHovered) : NativeImGui.GetColorU32((int)(ImGuiCol.Text)); ImGui.DrawNativeText(drawList, minimum + (controlSize - textSize) * 0.5f, color, text); } @@ -538,14 +557,13 @@ bool active /// /// Whether to underline and emphasize the drawn icon and text. /// - public static void IconText( + public void IconText( string icon, string text, bool highlight ) { - ImGuiFontScope fontScope = highlight - ? ImGuiFont.PushStyle(ImGuiFontStyle.Bold | ImGuiFontStyle.Italic) - : default; + m_session.EnsureDrawing(); + ImGuiFontScope fontScope = highlight ? ImGuiFont.PushStyle(ImGuiFontStyle.Bold | ImGuiFontStyle.Italic) : default; try { Vector2 cursor = NativeImGui.GetCursorScreenPos(); @@ -553,38 +571,21 @@ bool highlight ImFont* font = NativeImGui.Raw.GetFont(); float fontSize = NativeImGui.GetFontSize(); float lineHeight = NativeImGui.GetTextLineHeight(); - Vector4 glyphBounds = GetGlyphVisualBounds(font, fontSize, icon); + Vector4 glyphBounds = GetGlyphVisualBoundsNative(font, fontSize, icon); float iconSlotWidth = MathF.Max(lineHeight, glyphBounds.Z - glyphBounds.X); Vector2 textSize = NativeImGui.CalcTextSize(text); Vector2 textPos = new(cursor.X + iconSlotWidth + style->ItemInnerSpacing.X, cursor.Y); - uint color = NativeImGui.GetColorU32((int)(ImGuiCol.Text)); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); - AddGlyphCentered( - drawList, - font, - fontSize, - icon, - new Vector2(cursor.X + iconSlotWidth * 0.5f, cursor.Y + lineHeight * 0.5f), - color); + AddGlyphCenteredNative(drawList, font, fontSize, icon, new Vector2(cursor.X + iconSlotWidth * 0.5f, cursor.Y + lineHeight * 0.5f), color); ImGui.DrawNativeText(drawList, textPos, color, text); - if (highlight) { - float lineY = cursor.Y + NativeImGui.GetTextLineHeight() - - ImGuiWidget.style.textDecorationOffset; - ImGui.DrawNativeLine( - drawList, - new Vector2(cursor.X, lineY), - new Vector2(textPos.X + textSize.X, lineY), - color, - ImGuiWidget.style.borderSize - ); + float lineY = cursor.Y + NativeImGui.GetTextLineHeight() - this.style.textDecorationOffset; + ImGui.DrawNativeLine(drawList, new Vector2(cursor.X, lineY), new Vector2(textPos.X + textSize.X, lineY), color, this.style.borderSize); } - NativeImGui.Dummy(new Vector2( - iconSlotWidth + style->ItemInnerSpacing.X + textSize.X, - lineHeight)); + NativeImGui.Dummy(new Vector2(iconSlotWidth + style->ItemInnerSpacing.X + textSize.X, lineHeight)); } finally { @@ -592,7 +593,7 @@ bool highlight } } - private static void IconTextAt( + private void IconTextAt( Vector2 screenPos, string icon, string text, diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs index 32221b71d..b2bc23807 100644 --- a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Tree.cs @@ -1,32 +1,24 @@ using System; using System.Collections.Generic; using System.Numerics; - using Inno.Native.ImGui; using NativeImGui = Inno.Native.ImGui.ImGui; namespace Inno.Editor.ImGui.ImGuiWidget; - /// /// Provides reusable editor controls and rendering helpers built on the native ImGui API. /// -public static unsafe partial class ImGuiWidget +public sealed unsafe partial class ImGuiWidget { - private static readonly Dictionary s_treeStatesByWindow = []; - private static bool s_hasNextTreeNodeOpen; - private static bool s_nextTreeNodeOpen; - - private static List s_treeNodeStack => GetTreeWindowState().treeNodeStack; - private static List s_lastNodeIdsByDepth => GetTreeWindowState().lastNodeIdsByDepth; - private static List s_lineSegments => GetTreeWindowState().lineSegments; - private static Dictionary s_hasNextSiblingById => GetTreeWindowState().hasNextSiblingById; - private static Dictionary s_openStatesById => GetTreeWindowState().openStatesById; - - private static int s_lastFrame - { - get => GetTreeWindowState().lastFrame; - set => GetTreeWindowState().lastFrame = value; - } + private readonly Dictionary<(string Owner, uint Window), TreeWidgetWindowState> m_treeStatesByWindow = []; + private bool m_hasNextTreeNodeOpen; + private bool m_nextTreeNodeOpen; + private List m_treeNodeStack => GetTreeWindowState().treeNodeStack; + private List m_lastNodeIdsByDepth => GetTreeWindowState().lastNodeIdsByDepth; + private List m_lineSegments => GetTreeWindowState().lineSegments; + private Dictionary m_hasNextSiblingById => GetTreeWindowState().hasNextSiblingById; + private Dictionary m_openStatesById => GetTreeWindowState().openStatesById; + private int m_lastFrame { get => GetTreeWindowState().lastFrame; set => GetTreeWindowState().lastFrame = value; } /// /// Draws a full-width interactive tree row whose non-leaf content toggles expansion when @@ -36,7 +28,7 @@ private static int s_lastFrame /// Stable row identifier. /// /// - /// Content drawing callback that receives the native row geometry established by ImGui. + /// Frame-local content drawing for visible or explicitly preserved rows; clipped ordinary rows do not invoke it. /// /// /// Tree row options. @@ -44,22 +36,128 @@ private static int s_lastFrame /// /// Interaction and row geometry for the submitted item. /// - public static TreeNodeResult TreeNode( + /// + /// An explicit flattened depth is negative. + /// + public TreeNodeResult TreeNode( string id, Action onDraw, in TreeNodeOptions options ) { + m_session.EnsureDrawing(); + if (options.depth < 0) + throw new ArgumentOutOfRangeException(nameof(options), "A flattened tree depth cannot be negative."); + float indentation = (options.depth ?? 0) * NativeImGui.Raw.GetStyle()->IndentSpacing; + if (indentation > 0f) + NativeImGui.Indent(indentation); + try + { + return DrawTreeRow(id, onDraw, options); + } + finally + { + if (indentation > 0f) + NativeImGui.Unindent(indentation); + } + } + + /// + /// Gets this window and owner's expansion change counter for invalidating neutral flattened views. + /// + /// + /// No valid drawing session or native frame is active. + /// + public ulong treeExpansionRevision + { + get + { + m_session.EnsureDrawing(); + return GetTreeWindowState().expansionRevision; + } + } + + /// + /// Reads a retained expansion value without submitting a native row or retaining domain objects. + /// + /// + /// The stable row identifier within this window and owner. + /// + /// + /// True when the row was explicitly expanded; unknown rows are collapsed. + /// + /// + /// The row identifier is empty or whitespace. + /// + /// + /// No valid drawing session or native frame is active. + /// + public bool IsTreeNodeOpen(string id) + { + m_session.EnsureDrawing(); ArgumentException.ThrowIfNullOrWhiteSpace(id); - ArgumentNullException.ThrowIfNull(onDraw); + return m_openStatesById.GetValueOrDefault(id); + } + + /// + /// Changes retained expansion before a flattened view is computed, without submitting a row. + /// + /// + /// The stable row identifier within this window and owner. + /// + /// + /// The requested disclosure state. + /// + /// + /// The row identifier is empty or whitespace. + /// + /// + /// No valid drawing session or native frame is active. + /// + public void SetTreeNodeOpen( + string id, + bool open + ) { + m_session.EnsureDrawing(); + ArgumentException.ThrowIfNullOrWhiteSpace(id); + if (m_openStatesById.GetValueOrDefault(id) == open) + return; + m_openStatesById[id] = open; + GetTreeWindowState().expansionRevision++; + } + private TreeNodeResult DrawTreeRow( + string id, + Action onDraw, + in TreeNodeOptions options + ) { + m_session.EnsureDrawing(); + ArgumentException.ThrowIfNullOrWhiteSpace(id); + ArgumentNullException.ThrowIfNull(onDraw); bool isLeaf = options.isLeaf; BeginTreeFrameIfNeeded(); + ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); + Vector2 cursor = NativeImGui.GetCursorScreenPos(); + TreeWidgetWindowState state = GetTreeWindowState(); + bool visible = cursor.Y <= window->ClipRect.Max.Y + && cursor.Y + NativeImGui.GetFrameHeight() >= window->ClipRect.Min.Y; + bool drawContent = visible || options.requestFocus || options.keepContentActive + || state.activeContentOwner == id; + uint activeBefore = NativeImGui.Raw.GetCurrentContext()->ActiveId; + if (!drawContent) + return DrawTreeNode(id, onDraw, options, isLeaf, null, false); ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); NativeImGui.Raw.ChannelsSplit(drawList, 2); NativeImGui.Raw.ChannelsSetCurrent(drawList, 1); try { - return DrawTreeNode(id, onDraw, options, isLeaf, drawList); + TreeNodeResult result = DrawTreeNode(id, onDraw, options, isLeaf, drawList, true); + uint active = NativeImGui.Raw.GetCurrentContext()->ActiveId; + if (active != 0 && (active != activeBefore || active == NativeImGui.Raw.GetCurrentContext()->LastItemData.ID)) + { + state.activeContentOwner = id; + state.activeContentId = active; + } + return result; } finally { @@ -67,23 +165,23 @@ in TreeNodeOptions options } } - private static TreeNodeResult DrawTreeNode( + private TreeNodeResult DrawTreeNode( string id, Action onDraw, in TreeNodeOptions options, bool isLeaf, - ImDrawList* drawList + ImDrawList* drawList, + bool drawContent ) { - bool open = !isLeaf && s_openStatesById.TryGetValue(id, out bool storedOpen) && storedOpen; - if (!isLeaf && s_hasNextTreeNodeOpen) + bool open = !isLeaf && m_openStatesById.TryGetValue(id, out bool storedOpen) && storedOpen; + if (!isLeaf && m_hasNextTreeNodeOpen) { - open = s_nextTreeNodeOpen; - s_openStatesById[id] = open; + open = m_nextTreeNodeOpen; + SetTreeNodeOpen(id, open); } - s_hasNextTreeNodeOpen = false; + m_hasNextTreeNodeOpen = false; NativeImGui.SetNextItemOpen(open, (int)(ImGuiCond.Always)); - Vector2 nodeCursor = NativeImGui.GetCursorScreenPos(); // Each disclosure, content row and overlay owns its own hit region. ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None; @@ -91,77 +189,64 @@ private static TreeNodeResult DrawTreeNode( flags |= ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen; else flags |= ImGuiTreeNodeFlags.OpenOnArrow; - - int depth = Math.Max(0, NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth); + if (options.depth is not null) + flags |= ImGuiTreeNodeFlags.NoTreePushOnOpen; + int depth = options.depth ?? Math.Max(0, NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth); PruneTreeNodeStack(depth); bool hasNextSibling = TrackSiblingState(id, depth); - if (!options.hideGuideLines) + if (drawContent && !options.hideGuideLines) DrawTreeGuideLines(nodeCursor, hasNextSibling, !isLeaf); - PushTransparentTreeNodeHeaderColors(); + if (drawContent) + PushTransparentTreeNodeHeaderColors(); bool isOpen; try { + if (options.requestFocus && NativeImGui.IsWindowFocused((int)Inno.Native.ImGui.ImGuiFocusedFlags.ChildWindows)) + NativeImGui.SetKeyboardFocusHere(); isOpen = NativeImGui.TreeNodeEx($"##{id}", (int)(flags)); + GetTreeWindowState().navigationItemIds.Add(NativeImGui.Raw.GetCurrentContext()->LastItemData.ID); } finally { - NativeImGui.PopStyleColor(3); + if (drawContent) + NativeImGui.PopStyleColor(3); } + float nativeRowMaxY = NativeImGui.GetItemRectMax().Y; + if (!drawContent) + { + ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); + if (isOpen && !isLeaf) + m_treeNodeStack.Add(new TreeWidgetNodeState { id = id, cursor = nodeCursor, rowMaxY = nativeRowMaxY, hasNextSibling = hasNextSibling }); + return new TreeNodeResult(isOpen && !isLeaf, false, false, false, + new Vector2(window->Pos.X, nodeCursor.Y), new Vector2(window->WorkRect.Max.X, nativeRowMaxY), + new Vector2(nodeCursor.X + NativeImGui.GetTreeNodeToLabelSpacing(), nodeCursor.Y), false); + } try { if (!isLeaf && NativeImGui.IsItemToggledOpen()) - s_openStatesById[id] = isOpen; - - TreeWidgetHighlightRect contentRect = DrawTreeNodeContentContainer( - id, - nodeCursor, - nativeRowMaxY, - onDraw, - options.drawViewportOverlay, - out Vector2 interactionMin); + SetTreeNodeOpen(id, isOpen); + TreeWidgetHighlightRect contentRect = DrawTreeNodeContentContainer(id, nodeCursor, nativeRowMaxY, onDraw, options.drawViewportOverlay, out Vector2 interactionMin); bool hovered = NativeImGui.IsItemHovered((int)(ImGuiHoveredFlags.AllowWhenOverlappedByItem)); bool clicked = hovered && NativeImGui.IsItemClicked((int)(ImGuiMouseButton.Left)); bool doubleClicked = hovered && NativeImGui.IsMouseDoubleClicked((int)(ImGuiMouseButton.Left)); if (!isLeaf && doubleClicked) - s_openStatesById[id] = !isOpen; - bool showHoverHighlight = hovered && - !options.suppressHoverHighlight && - NativeImGui.Raw.IsDragDropActive() == 0; + SetTreeNodeOpen(id, !isOpen); + bool showHoverHighlight = hovered && !options.suppressHoverHighlight && NativeImGui.Raw.IsDragDropActive() == 0; if (options.selected || showHoverHighlight) - DrawTreeRowBackground( - drawList, - contentRect, - NativeImGui.GetColorU32((int)(options.selected ? ImGuiCol.Header : ImGuiCol.HeaderHovered))); + DrawTreeRowBackground(drawList, contentRect, NativeImGui.GetColorU32((int)(options.selected ? ImGuiCol.Header : ImGuiCol.HeaderHovered))); else if (options.showBackground) - DrawTreeRowBackground( - drawList, - contentRect, - NativeImGui.ColorConvertFloat4ToU32(options.backgroundColor)); - + DrawTreeRowBackground(drawList, contentRect, NativeImGui.ColorConvertFloat4ToU32(options.backgroundColor)); if (isOpen && !isLeaf) { - s_treeNodeStack.Add(new TreeWidgetNodeState - { - id = id, - cursor = nodeCursor, - rowMaxY = contentRect.max.Y, - hasNextSibling = hasNextSibling - }); + m_treeNodeStack.Add(new TreeWidgetNodeState { id = id, cursor = nodeCursor, rowMaxY = contentRect.max.Y, hasNextSibling = hasNextSibling }); } - return new TreeNodeResult( - isOpen && !isLeaf, - clicked, - doubleClicked, - hovered, - contentRect.min, - contentRect.max, - interactionMin); + return new TreeNodeResult(isOpen && !isLeaf, clicked, doubleClicked, hovered, contentRect.min, contentRect.max, interactionMin, true); } catch { - if (isOpen && !isLeaf) + if (isOpen && !isLeaf && options.depth is null) NativeImGui.TreePop(); throw; } @@ -173,25 +258,26 @@ private static TreeNodeResult DrawTreeNode( /// /// Whether the next row should be expanded. /// - public static void SetNextTreeNodeOpen(bool open) + public void SetNextTreeNodeOpen(bool open) { - s_hasNextTreeNodeOpen = true; - s_nextTreeNodeOpen = open; + m_session.EnsureDrawing(); + m_hasNextTreeNodeOpen = true; + m_nextTreeNodeOpen = open; } - private static TreeWidgetWindowState GetTreeWindowState() + private TreeWidgetWindowState GetTreeWindowState() { - int windowKey = ((nint)NativeImGui.Raw.GetWindowDrawList()).GetHashCode(); - if (!s_treeStatesByWindow.TryGetValue(windowKey, out TreeWidgetWindowState? state)) + var windowKey = (m_session.owner, NativeImGui.Raw.GetCurrentWindow()->ID); + if (!m_treeStatesByWindow.TryGetValue(windowKey, out TreeWidgetWindowState? state)) { state = new TreeWidgetWindowState(); - s_treeStatesByWindow[windowKey] = state; + m_treeStatesByWindow[windowKey] = state; } return state; } - private static TreeWidgetHighlightRect DrawTreeNodeContentContainer( + private TreeWidgetHighlightRect DrawTreeNodeContentContainer( string id, Vector2 nodeCursor, float nativeRowMaxY, @@ -204,10 +290,7 @@ out Vector2 interactionMin float contentX = nodeCursor.X + NativeImGui.GetTreeNodeToLabelSpacing(); float contentRightX = window->WorkRect.Max.X; float contentBottomY = window->InnerRect.Max.Y; - float contentOffsetX = contentX - windowPos.X + window->Scroll.X - - window->DC.GroupOffset.X - - window->DC.ColumnsOffset.X; - + float contentOffsetX = contentX - windowPos.X + window->Scroll.X - window->DC.GroupOffset.X - window->DC.ColumnsOffset.X; NativeImGui.SameLine(contentOffsetX, 0f); NativeImGui.BeginGroup(); NativeImGui.PushClipRect(new Vector2(contentX, nodeCursor.Y), new Vector2(contentRightX, contentBottomY), true); @@ -228,7 +311,6 @@ out Vector2 interactionMin min = new Vector2(windowPos.X, MathF.Min(nodeCursor.Y, contentMin.Y)), max = new Vector2(contentRightX, MathF.Max(nativeRowMaxY, contentMax.Y)) }; - Vector2 hitMin = new(contentX, rect.min.Y); Vector2 hitMax = rect.max; interactionMin = hitMin; @@ -242,13 +324,12 @@ out Vector2 interactionMin return rect; } - private static void DrawTreeNodeViewportOverlay( + private void DrawTreeNodeViewportOverlay( ImGuiWindow* window, Action? drawViewportOverlay ) { if (drawViewportOverlay is null) return; - float contentBoundaryX = window->DC.CursorMaxPos.X; float idealBoundaryX = window->DC.IdealMaxPos.X; try @@ -262,81 +343,76 @@ private static void DrawTreeNodeViewportOverlay( } } - private static void PushTransparentTreeNodeHeaderColors() + private void PushTransparentTreeNodeHeaderColors() { NativeImGui.PushStyleColor((int)(ImGuiCol.Header), EditorPalette.transparent); NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderHovered), EditorPalette.transparent); NativeImGui.PushStyleColor((int)(ImGuiCol.HeaderActive), EditorPalette.transparent); } - private static void BeginTreeFrameIfNeeded() + private void BeginTreeFrameIfNeeded() { int frame = NativeImGui.GetFrameCount(); - if (frame == s_lastFrame) + if (frame == m_lastFrame) return; - TreeWidgetWindowState state = GetTreeWindowState(); state.treeNodeStack.Clear(); state.lastNodeIdsByDepth.Clear(); state.lineSegments.Clear(); - s_lastFrame = frame; + state.navigationItemIds.Clear(); + if (state.activeContentId != NativeImGui.Raw.GetCurrentContext()->ActiveId) + { + state.activeContentId = 0; + state.activeContentOwner = null; + } + m_lastFrame = frame; } - private static void PruneTreeNodeStack(int nativeDepth) + private void PruneTreeNodeStack(int nativeDepth) { - while (s_treeNodeStack.Count > nativeDepth) - s_treeNodeStack.RemoveAt(s_treeNodeStack.Count - 1); + while (m_treeNodeStack.Count > nativeDepth) + m_treeNodeStack.RemoveAt(m_treeNodeStack.Count - 1); } - private static bool TrackSiblingState( + private bool TrackSiblingState( string id, int depth ) { - bool hasNextSibling = s_hasNextSiblingById.TryGetValue(id, out bool cachedHasNextSibling) && cachedHasNextSibling; - - while (s_lastNodeIdsByDepth.Count <= depth) - s_lastNodeIdsByDepth.Add(string.Empty); - - string previousId = s_lastNodeIdsByDepth[depth]; + bool hasNextSibling = m_hasNextSiblingById.TryGetValue(id, out bool cachedHasNextSibling) && cachedHasNextSibling; + while (m_lastNodeIdsByDepth.Count <= depth) + m_lastNodeIdsByDepth.Add(string.Empty); + string previousId = m_lastNodeIdsByDepth[depth]; if (!string.IsNullOrEmpty(previousId) && !string.Equals(previousId, id, StringComparison.Ordinal)) - s_hasNextSiblingById[previousId] = true; - - s_hasNextSiblingById[id] = false; - s_lastNodeIdsByDepth[depth] = id; - - for (int i = depth + 1; i < s_lastNodeIdsByDepth.Count; i++) - s_lastNodeIdsByDepth[i] = string.Empty; - + m_hasNextSiblingById[previousId] = true; + m_hasNextSiblingById[id] = false; + m_lastNodeIdsByDepth[depth] = id; + for (int i = depth + 1; i < m_lastNodeIdsByDepth.Count; i++) + m_lastNodeIdsByDepth[i] = string.Empty; return hasNextSibling; } - private static void DrawTreeGuideLines( + private void DrawTreeGuideLines( Vector2 nodeCursor, bool hasNextSibling, bool hasDisclosureArrow ) { - if (s_treeNodeStack.Count == 0) + if (m_treeNodeStack.Count == 0) return; - ImGuiStyle* style = NativeImGui.Raw.GetStyle(); float textLineHeight = NativeImGui.GetTextLineHeight(); - float lineOverlap = ImGuiWidget.style.treeGuideLineOverlap; + float lineOverlap = this.style.treeGuideLineOverlap; float rowMaxY = nodeCursor.Y + textLineHeight + style->ItemSpacing.Y + lineOverlap; float rowCenterY = nodeCursor.Y + textLineHeight * 0.5f; float nodeToLabel = NativeImGui.GetTreeNodeToLabelSpacing(); - float guideOffset = nodeToLabel * 0.5f - ImGuiWidget.style.treeGuideLeftOffset; + float guideOffset = nodeToLabel * 0.5f - this.style.treeGuideLeftOffset; float labelStartX = nodeCursor.X + nodeToLabel; float fileConnectorPadding = MathF.Max(1f, style->ItemInnerSpacing.X); - float folderConnectorPadding = fileConnectorPadding * 2f + - ImGuiWidget.style.treeFolderConnectorPadding; - float targetX = hasDisclosureArrow - ? nodeCursor.X + guideOffset - folderConnectorPadding - : labelStartX - fileConnectorPadding; - - for (int i = 0; i < s_treeNodeStack.Count - 1; i++) + float folderConnectorPadding = fileConnectorPadding * 2f + this.style.treeFolderConnectorPadding; + float targetX = hasDisclosureArrow ? nodeCursor.X + guideOffset - folderConnectorPadding : labelStartX - fileConnectorPadding; + for (int i = 0; i < m_treeNodeStack.Count - 1; i++) { - TreeWidgetNodeState ancestorState = s_treeNodeStack[i]; - TreeWidgetNodeState pathChildState = s_treeNodeStack[i + 1]; + TreeWidgetNodeState ancestorState = m_treeNodeStack[i]; + TreeWidgetNodeState pathChildState = m_treeNodeStack[i + 1]; if (pathChildState.hasNextSibling) { float ancestorX = ancestorState.cursor.X + guideOffset; @@ -345,7 +421,7 @@ bool hasDisclosureArrow } } - TreeWidgetNodeState parentState = s_treeNodeStack[^1]; + TreeWidgetNodeState parentState = m_treeNodeStack[^1]; float branchX = parentState.cursor.X + guideOffset; float branchStartY = MathF.Max(parentState.rowMaxY, nodeCursor.Y); AddTreeLine(new Vector2(branchX, branchStartY), new Vector2(branchX, hasNextSibling ? rowMaxY : rowCenterY)); @@ -353,7 +429,7 @@ bool hasDisclosureArrow AddTreeLine(new Vector2(branchX, rowCenterY), new Vector2(targetX, rowCenterY)); } - private static void AddTreeLine( + private void AddTreeLine( Vector2 from, Vector2 to ) { @@ -362,22 +438,16 @@ Vector2 to from = SnapTreeLinePoint(from), to = SnapTreeLinePoint(to) }; - s_lineSegments.Add(line); - ImGui.DrawNativeLine( - NativeImGui.Raw.GetWindowDrawList(), - line.from, - line.to, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.treeGuide), - style.borderSize - ); + m_lineSegments.Add(line); + ImGui.DrawNativeLine(NativeImGui.Raw.GetWindowDrawList(), line.from, line.to, NativeImGui.ColorConvertFloat4ToU32(EditorPalette.treeGuide), this.style.borderSize); } - private static Vector2 SnapTreeLinePoint(Vector2 point) + private Vector2 SnapTreeLinePoint(Vector2 point) { return new Vector2(MathF.Floor(point.X) + 0.5f, MathF.Floor(point.Y) + 0.5f); } - private static void DrawTreeRowBackground( + private void DrawTreeRowBackground( ImDrawList* drawList, TreeWidgetHighlightRect rect, uint color @@ -386,7 +456,6 @@ uint color ImGui.DrawNativeFilledRectangle(drawList, rect.min, rect.max, color, 0, 0); NativeImGui.Raw.ChannelsSetCurrent(drawList, 1); } - } /// @@ -410,36 +479,45 @@ internal TreeNodeDrawContext(float rowHeight) /// public readonly struct TreeNodeOptions { + /// + /// Gets explicit indentation for a flattened row, or null for a nested native tree. + /// Explicit-depth rows never push a native tree scope; callers must not call TreePop for them. + /// + public int? depth { get; init; } + /// + /// Gets whether this row should receive keyboard focus during this submission when its tree window is focused. + /// + public bool requestFocus { get; init; } + + /// + /// Gets whether an active edit requires content submission even while its row is clipped. + /// + public bool keepContentActive { get; init; } + /// /// Gets whether the row is selected. /// public bool selected { get; init; } - /// /// Gets whether the row has no expandable children. /// public bool isLeaf { get; init; } - /// /// Gets whether a custom background is drawn behind an unselected row. /// public bool showBackground { get; init; } - /// /// Gets the custom background color used when is enabled. /// public Vector4 backgroundColor { get; init; } - /// /// Gets whether the row keeps its configured background while hovered. /// public bool suppressHoverHighlight { get; init; } - /// /// Gets whether ancestor and branch guide lines are omitted for this row. /// public bool hideGuideLines { get; init; } - /// /// Gets the callback that draws controls fixed to the current viewport without extending the /// tree's horizontal content boundary. @@ -459,7 +537,8 @@ internal TreeNodeResult( bool isHovered, Vector2 min, Vector2 max, - Vector2 contentMin + Vector2 contentMin, + bool isSubmitted ) { this.isOpen = isOpen; this.isClicked = isClicked; @@ -468,39 +547,39 @@ Vector2 contentMin this.min = min; this.max = max; this.contentMin = contentMin; + this.isSubmitted = isSubmitted; } + /// + /// Gets whether row controls were submitted; ordinary clipped rows are false, while preserved active edits remain true. + /// + public bool isSubmitted { get; } + /// /// Gets whether child content should be rendered. /// public bool isOpen { get; } - /// /// Gets whether the content row was clicked. /// public bool isClicked { get; } - /// /// Gets whether the content row was double-clicked. Double-clicking a non-leaf content row /// also toggles its retained expansion state for the next frame. /// public bool isDoubleClicked { get; } - /// /// Gets whether the full row is hovered. /// public bool isHovered { get; } - /// /// Gets the row minimum screen coordinate. /// public Vector2 min { get; } - /// /// Gets the row maximum screen coordinate. /// public Vector2 max { get; } - /// /// Gets the minimum screen coordinate of the row's interactive content, excluding tree indentation. /// @@ -509,6 +588,10 @@ Vector2 contentMin internal sealed class TreeWidgetWindowState { + internal ulong expansionRevision; + internal uint activeContentId; + internal string? activeContentOwner; + internal readonly HashSet navigationItemIds = []; internal readonly List treeNodeStack = []; internal readonly List lastNodeIdsByDepth = []; internal readonly List lineSegments = []; diff --git a/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Trend.cs b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Trend.cs new file mode 100644 index 000000000..a62cd51f7 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.ImGui/Widgets/ImGuiWidget.Trend.cs @@ -0,0 +1,72 @@ +using System; +using System.Numerics; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.ImGui.ImGuiWidget; + +/// +/// Provides session-owned numeric trend presentation over borrowed sample buffers. +/// +public sealed unsafe partial class ImGuiWidget +{ + /// + /// Draws a bounded line plot from borrowed numeric samples without copying their buffer. + /// + /// + /// The visible plot title and stable item identifier in its current owner. + /// + /// + /// Finite samples borrowed only for this call; an empty span draws an empty plot. + /// + /// + /// The first chronological sample in a circular buffer, or zero for empty input. + /// + /// + /// The label is empty or a sample is not finite. + /// + /// + /// The offset is outside the supplied sample span. + /// + /// + /// No matching owner-thread drawing session is active. + /// + public void Trend( + string label, + ReadOnlySpan values, + int offset = 0 + ) { + m_session.EnsureDrawing(); + ArgumentException.ThrowIfNullOrWhiteSpace(label); + if (offset < 0 || (values.Length == 0 ? offset != 0 : offset >= values.Length)) + throw new ArgumentOutOfRangeException(nameof(offset)); + float maximum = 0f; + float minimum = 0f; + foreach (float value in values) + { + if (!float.IsFinite(value)) + throw new ArgumentException("Trend samples must be finite.", nameof(values)); + maximum = MathF.Max(maximum, value); + minimum = MathF.Min(minimum, value); + } + NativeImGui.TextUnformatted(label); + Vector2 origin = NativeImGui.GetCursorScreenPos(); + Vector2 size = new(MathF.Max(1f, NativeImGui.GetContentRegionAvail().X), style.trendHeight); + _ = NativeImGui.InvisibleButton(label, size); + EditorDrawList draw = ImGui.GetDrawList(); + draw.FilledRectangle(origin, origin + size, NativeImGui.ColorConvertFloat4ToU32(EditorPalette.collectionRow)); + double range = maximum > minimum ? (double)maximum - minimum : 1d; + uint color = NativeImGui.GetColorU32((int)ImGuiCol.PlotLines); + Vector2 previous = default; + for (int index = 0; index < values.Length; index++) + { + float value = values[(offset + index) % values.Length]; + float x = values.Length <= 1 ? 0f : (float)index / (values.Length - 1); + float normalized = (float)(((double)value - minimum) / range); + Vector2 point = origin + new Vector2(x * size.X, (1f - normalized) * size.Y); + if (index > 0) + draw.Line(previous, point, color); + previous = point; + } + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Inno.Editor.Shaders.ImGui.csproj b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Inno.Editor.Shaders.ImGui.csproj new file mode 100644 index 000000000..aa60e9a6f --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Inno.Editor.Shaders.ImGui.csproj @@ -0,0 +1,40 @@ + + + net10.0 + 14.0 + disable + enable + true + $(WarningsAsErrors);CS1572;CS1573;CS1591 + Editor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialInspectionDrawer.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialInspectionDrawer.cs similarity index 62% rename from src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialInspectionDrawer.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialInspectionDrawer.cs index 9cb69387f..839cca60a 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialInspectionDrawer.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialInspectionDrawer.cs @@ -1,21 +1,23 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Shaders; using System; using System.Linq; using Inno.Assets.Pipeline; using Inno.Editor.Inspection; using Inno.Rendering; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Assets; -namespace Inno.Editor.Shaders; - +namespace Inno.Editor.Shaders.ImGui; [InspectionDrawer(typeof(MaterialAsset))] internal sealed class MaterialInspectionDrawer(IInspectionIconProvider icons) : InspectionDrawer { /// /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + public override string icon => Inno.Editor.ImGui.ImGuiIcon.File; + /// /// Retrieves the current icon from authoritative state. /// @@ -28,12 +30,15 @@ internal sealed class MaterialInspectionDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - MaterialAsset target -) - => context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetFileSystemEntry(target.assetPath, out AssetFileEntry entry) ? icons.GetIcon(entry) : icon; + protected override string GetIcon( + InspectionDrawContext context, + MaterialAsset target + ) => context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetFileSystemEntry( + target.assetPath, + out AssetFileEntry entry + ) ? icons.GetIcon(entry) : icon; /// /// Binds a caller-visible label to the current inspection target. /// @@ -46,11 +51,10 @@ MaterialAsset target /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - MaterialAsset target -) - => (target.name, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + MaterialAsset target + ) => (target.name, null); /// /// Renders the header presentation for the current editor frame. /// @@ -60,13 +64,14 @@ MaterialAsset target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - MaterialAsset target -) { + protected override void DrawHeader( + InspectionDrawContext context, + MaterialAsset target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) MaterialInspector.DrawHeader(context, documents, documents.Open(target)); } + /// /// Renders the value presentation for the current editor frame. /// @@ -76,10 +81,10 @@ MaterialAsset target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - MaterialAsset target -) { + protected override void Draw( + InspectionDrawContext context, + MaterialAsset target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) MaterialInspector.Draw(context, documents, documents.Open(target)); } @@ -91,7 +96,8 @@ internal sealed class MaterialSourceDrawer(IInspectionIconProvider /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + public override string icon => Inno.Editor.ImGui.ImGuiIcon.File; + /// /// Retrieves the current icon from authoritative state. /// @@ -104,10 +110,10 @@ internal sealed class MaterialSourceDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - AssetFileEntry target -) => icons.GetIcon(target); + protected override string GetIcon( + InspectionDrawContext context, + AssetFileEntry target + ) => icons.GetIcon(target); /// /// Checks whether this drawer supports the selected Inspector target. /// @@ -117,8 +123,7 @@ AssetFileEntry target /// /// when the documented condition is satisfied; otherwise, . /// -protected override bool CanInspect(AssetFileEntry target) - => !target.isDirectory && target.extension.Equals(".imaterial", StringComparison.OrdinalIgnoreCase); + protected override bool CanInspect(AssetFileEntry target) => !target.isDirectory && target.extension.Equals(".imaterial", StringComparison.OrdinalIgnoreCase); /// /// Binds a caller-visible label to the current inspection target. /// @@ -131,11 +136,10 @@ protected override bool CanInspect(AssetFileEntry target) /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - AssetFileEntry target -) - => (target.nameWithoutExtension, null); + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + AssetFileEntry target + ) => (target.nameWithoutExtension, null); /// /// Renders the header presentation for the current editor frame. /// @@ -145,13 +149,14 @@ AssetFileEntry target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - AssetFileEntry target -) { + protected override void DrawHeader( + InspectionDrawContext context, + AssetFileEntry target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) MaterialInspector.DrawHeader(context, documents, documents.OpenDraft(target.assetPath)); } + /// /// Renders the value presentation for the current editor frame. /// @@ -161,10 +166,10 @@ AssetFileEntry target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - AssetFileEntry target -) { + protected override void Draw( + InspectionDrawContext context, + AssetFileEntry target + ) { if (context.interactions.TryGetModule(out var documents) && documents is not null) MaterialInspector.Draw(context, documents, documents.OpenDraft(target.assetPath)); } @@ -177,12 +182,12 @@ internal static void DrawHeader( MaterialDocuments documents, Inno.Editor.Assets.AssetDraftDocuments.Draft draft ) { - EditorWidget.Disabled(draft.readOnly, () => + EditorUI.current.widgets.Disabled(draft.readOnly, () => { - if (NativeImGui.Button("Save")) + if (Ui.Button("Save")) _ = context.interactions.documents.Save(draft.documentId); - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.Button("Revert")) + Ui.SameLine(0f, -1f); + if (Ui.Button("Revert")) _ = context.interactions.documents.Revert(draft.documentId); }); } @@ -192,78 +197,86 @@ internal static void Draw( MaterialDocuments documents, Inno.Editor.Assets.AssetDraftDocuments.Draft draft ) { - documents.TouchInspection(draft); MaterialAsset material = documents.Read(draft.id); var edits = new DraftEdits(documents, draft, material); - NativeImGui.PushID(draft.id.ToString("N")); + Ui.PushId(draft.id.ToString("N")); try { - if (EditorWidget.SectionHeader("Preview", "Shows an isolated preview of the current Material draft.")) + if (EditorUI.current.widgets.SectionHeader("Preview", "Shows an isolated preview of the current Material draft.")) { if (material.shader is null || material.shader.isMissing) - EditorWidget.Hint("Choose an available Shader to preview this Material."); + EditorUI.current.widgets.Hint("Choose an available Shader to preview this Material."); else if (context.interactions.TryGetModule(out var shaderPreviews) && shaderPreviews is not null) - shaderPreviews.DrawMaterial(draft.id, material, MathF.Max(1f, MathF.Min(256f, NativeImGui.GetContentRegionAvail().X))); + shaderPreviews.DrawMaterial(draft.id, material, MathF.Max(1f, MathF.Min(256f, Ui.GetContentRegionAvailable().X))); else - EditorWidget.Hint("Material preview is unavailable."); + EditorUI.current.widgets.Hint("Material preview is unavailable."); } - bool materialOpen = EditorWidget.SectionHeader("Material", "Edits stay in this draft. Save publishes through normal asset import; Revert reloads the source."); + bool materialOpen = EditorUI.current.widgets.SectionHeader( + "Material", + "Edits stay in this draft. Save publishes through normal asset import; Revert reloads the source." + ); if (materialOpen) { - EditorWidget.Hint(draft.isDirty ? "Unsaved changes · Scene/Game unchanged" : "Saved · compilation and import are separate"); + EditorUI.current.widgets.Hint(draft.isDirty ? "Unsaved changes · Scene/Game unchanged" : "Saved · compilation and import are separate"); if (draft.error.Length != 0) - EditorWidget.Hint(draft.error); + EditorUI.current.widgets.Hint(draft.error); if (draft.readOnly) - EditorWidget.Hint("Installed material · copy to the project to edit"); - Draw("shader", "Shader", typeof(ShaderAsset), () => material.shader, value => material.shader = (ShaderAsset?)value); + EditorUI.current.widgets.Hint("Installed material · copy to the project to edit"); + Draw("shader", "Shader", typeof(ShaderAsset), () => material.shader, value => material.shader = (ShaderAsset? )value); } + ShaderDefinition? definition = material.shader?.definition; if (material.shader is null || material.shader.isMissing || definition is null) { if (materialOpen) - EditorWidget.Hint("Choose an available Shader. Existing overrides are retained until its interface can be resolved."); + EditorUI.current.widgets.Hint("Choose an available Shader. Existing overrides are retained until its interface can be resolved."); return; } - if (materialOpen && NativeImGui.SmallButton("Open Shader Editor")) + + if (materialOpen && Ui.SmallButton("Open Shader Editor")) { context.interactions.SetSelection(material.shader); context.interactions.OpenPanel("rendering.shader-editor"); } + if (materialOpen) { - NativeImGui.SameLine(0f, -1f); - if (NativeImGui.SmallButton("Locate Shader") - && documents.assets.TryGetFileSystemEntry(material.shader.assetPath, out AssetFileEntry shaderEntry)) + Ui.SameLine(0f, -1f); + if (Ui.SmallButton("Locate Shader") && documents.assets.TryGetFileSystemEntry(material.shader.assetPath, out AssetFileEntry shaderEntry)) context.interactions.SetSelection(shaderEntry); } if (materialOpen && (definition.techniques.Length > 1 || material.techniqueId.isValid)) { - if (EditorWidget.BeginBoundedCombo("##technique", material.techniqueId.isValid ? material.techniqueId.value : "Automatic")) + if (EditorUI.current.widgets.BeginBoundedCombo("##technique", material.techniqueId.isValid ? material.techniqueId.value : "Automatic")) { try { - if (NativeImGui.Selectable("Automatic", !material.techniqueId.isValid) && !draft.readOnly) + if (Ui.Selectable("Automatic", !material.techniqueId.isValid) && !draft.readOnly) { material.techniqueId = default; - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } + foreach (ShaderTechniqueDefinition technique in definition.techniques) - if (NativeImGui.Selectable(technique.id.value, material.techniqueId == technique.id) && !draft.readOnly) + if (Ui.Selectable(technique.id.value, material.techniqueId == technique.id) && !draft.readOnly) { material.techniqueId = technique.id; - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } } - bool parametersOpen = EditorWidget.SectionHeader("Parameters", "Inherited values follow Shader defaults. Changing a field creates a Material override; Reset restores inheritance."); + bool parametersOpen = EditorUI.current.widgets.SectionHeader( + "Parameters", + "Inherited values follow Shader defaults. Changing a field creates a Material override; Reset restores inheritance." + ); bool groupOpen = parametersOpen; string currentGroup = ""; int visibleParameters = 0; @@ -271,15 +284,16 @@ Inno.Editor.Assets.AssetDraftDocuments.Draft draft { ShaderParameterPresentation presentation = documents.Presentation(material.shader, property.id, out string presentationError); if (presentationError.Length != 0) - EditorWidget.Hint("Parameter presentation unavailable: " + presentationError); + EditorUI.current.widgets.Hint("Parameter presentation unavailable: " + presentationError); if (!presentation.visible) continue; visibleParameters++; if (presentation.group != currentGroup) { currentGroup = presentation.group; - groupOpen = EditorWidget.SectionHeader(currentGroup.Length == 0 ? "Parameters" : currentGroup); + groupOpen = EditorUI.current.widgets.SectionHeader(currentGroup.Length == 0 ? "Parameters" : currentGroup); } + if (!groupOpen) continue; bool overridden = material.TryGet(property.id, out MaterialValue value); @@ -287,122 +301,138 @@ Inno.Editor.Assets.AssetDraftDocuments.Draft draft continue; if (!overridden) value = property.defaultValue; - NativeImGui.PushID(property.id.value); + Ui.PushId(property.id.value); try { - ShaderPropertyInspector.Draw(context, draft, "material." + property.id.value, property, value, - next => material.Set(property.id, next), edits, draft.readOnly, presentation); - EditorWidget.Hint(overridden ? "Material override" : "Inherited from Shader"); - if (overridden && !draft.readOnly && NativeImGui.SmallButton("Reset override")) + ShaderPropertyInspector.Draw( + context, + draft, + "material." + property.id.value, + property, + value, + next => material.Set(property.id, next), + edits, + draft.readOnly, + presentation + ); + EditorUI.current.widgets.Hint(overridden ? "Material override" : "Inherited from Shader"); + if (overridden && !draft.readOnly && Ui.SmallButton("Reset override")) { material.ReplaceProperties(material.properties.Where(entry => entry.id != property.id).ToArray()); - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } + if (value.kind == MaterialValueKind.Texture) { if (value.texture is { isMissing: false } texture && documents.previews.TryGetTexture(texture, out var preview)) { - float width = MathF.Max(1f, MathF.Min(128f, NativeImGui.GetContentRegionAvail().X)); + float width = MathF.Max(1f, MathF.Min(128f, Ui.GetContentRegionAvailable().X)); float height = width * preview.pixelHeight / preview.pixelWidth; if (height > 128f) { width *= 128f / height; height = 128f; } + documents.previews.Draw(preview, new(width, height)); } } } finally { - NativeImGui.PopID(); + Ui.PopId(); } } if (parametersOpen && visibleParameters == 0 && definition.keywords.Length == 0) - EditorWidget.Hint("No editable parameters."); - - if (parametersOpen && material.properties.Count != 0 && !draft.readOnly && NativeImGui.Button("Reset all overrides")) + EditorUI.current.widgets.Hint("No editable parameters."); + if (parametersOpen && material.properties.Count != 0 && !draft.readOnly && Ui.Button("Reset all overrides")) { material.ReplaceProperties([]); - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } foreach (ShaderKeywordDefinition keyword in parametersOpen ? definition.keywords : []) { string selected = keyword.options.FirstOrDefault(option => material.keywords.Contains(option)) ?? "None"; - if (!EditorWidget.BeginBoundedCombo(keyword.id, selected)) + if (!EditorUI.current.widgets.BeginBoundedCombo(keyword.id, selected)) continue; try { - if (NativeImGui.Selectable("None", selected == "None")) + if (Ui.Selectable("None", selected == "None")) SetOption(null); foreach (string option in keyword.options) - if (NativeImGui.Selectable(option, selected == option)) + if (Ui.Selectable(option, selected == option)) SetOption(option); } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } + void SetOption(string? next) { if (draft.readOnly) return; foreach (string option in keyword.options) material.SetKeyword(option, option == next); - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } } - MaterialPropertyEntry[] orphaned = material.properties.Where(entry => !definition.properties.Any(property => property.id == entry.id - && IsEditable(property) && ShaderPropertyInspector.Compatible(property.type, entry.value.kind))).ToArray(); + MaterialPropertyEntry[] orphaned = material.properties.Where(entry => !definition.properties.Any(property => property.id == entry.id && IsEditable(property) && ShaderPropertyInspector.Compatible(property.type, entry.value.kind))).ToArray(); if (orphaned.Length != 0) { - if (EditorWidget.SectionHeader("Unresolved Overrides", "These values were retained after an interface change. Remove them explicitly or restore a compatible Shader before publishing.")) + if (EditorUI.current.widgets.SectionHeader( + "Unresolved Overrides", + "These values were retained after an interface change. Remove them explicitly or restore a compatible Shader before publishing." + )) { foreach (MaterialPropertyEntry entry in orphaned) { - NativeImGui.TextUnformatted(entry.id.value + " · " + entry.value.kind); - if (!draft.readOnly && NativeImGui.SmallButton("Remove##" + entry.id.value)) + Ui.Text(entry.id.value + " · " + entry.value.kind); + if (!draft.readOnly && Ui.SmallButton("Remove##" + entry.id.value)) { material.ReplaceProperties(material.properties.Where(value => value.id != entry.id).ToArray()); - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } } } } + ShaderPropertyDefinition[] passBindings = definition.properties.Where(property => !IsEditable(property)).ToArray(); string[] unknownKeywords = material.keywords.Where(value => !definition.keywords.Any(keyword => keyword.options.Contains(value))).ToArray(); if (unknownKeywords.Length != 0) { - if (EditorWidget.SectionHeader("Unresolved Keywords", "These overrides no longer belong to the Shader interface. They remain until explicitly removed.")) + if (EditorUI.current.widgets.SectionHeader( + "Unresolved Keywords", + "These overrides no longer belong to the Shader interface. They remain until explicitly removed." + )) { foreach (string keyword in unknownKeywords) { - NativeImGui.TextUnformatted(keyword); - if (!draft.readOnly && NativeImGui.SmallButton("Remove##keyword." + keyword)) + Ui.Text(keyword); + if (!draft.readOnly && Ui.SmallButton("Remove##keyword." + keyword)) { material.SetKeyword(keyword, false); - documents.Edit(draft, material, true); + documents.Replace(draft.id, material); } } } } + if (passBindings.Length != 0) { - if (EditorWidget.SectionHeader("Pipeline Bindings", "These resources are supplied by the Render Pass, not by this Material.")) + if (EditorUI.current.widgets.SectionHeader("Pipeline Bindings", "These resources are supplied by the Render Pass, not by this Material.")) { foreach (ShaderPropertyDefinition property in passBindings) - EditorWidget.Hint(property.displayName + " · " + property.type + " · " + property.bindingOwner); + EditorUI.current.widgets.Hint(property.displayName + " · " + property.type + " · " + property.bindingOwner); } } - if (!NativeImGui.IsAnyItemActive()) - documents.Commit(draft); } finally { - NativeImGui.PopID(); + Ui.PopId(); } void Draw( @@ -411,45 +441,28 @@ void Draw( Type type, Func getter, Action setter - ) - => context.properties.DrawValue(context.editorContext, draft, "material." + path, label, type, getter, setter, edits, draft.readOnly); + ) => context.properties.DrawValue(context.editorContext, draft, "material." + path, label, type, getter, setter, edits, draft.readOnly); } private static bool IsEditable(ShaderPropertyDefinition property) => property.bindingOwner == ShaderPropertyBindingOwner.Material && property.bindingKind is ShaderPropertyBindingKind.Uniform or ShaderPropertyBindingKind.SampledTexture; - private sealed class DraftEdits( MaterialDocuments documents, Inno.Editor.Assets.AssetDraftDocuments.Draft draft, MaterialAsset material ) : IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) { + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) { mutation(); - documents.Replace(draft.id, material, !NativeImGui.IsAnyItemActive()); + documents.Replace(draft.id, material, mergeKey); return true; } } diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialSelectionDrawer.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialSelectionDrawer.cs similarity index 62% rename from src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialSelectionDrawer.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialSelectionDrawer.cs index 1e74a0292..fc5373a7d 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Materials/MaterialSelectionDrawer.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/MaterialSelectionDrawer.cs @@ -1,3 +1,8 @@ +using Inno.Editor.ImGui; +using Inno.Editor.Interactions; +using Inno.Core.Identity; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Shaders; using System; using System.Collections.Generic; using System.Linq; @@ -5,19 +10,21 @@ using Inno.Assets.Pipeline; using Inno.Editor.Inspection; using Inno.Rendering; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using ImGuiApi = Inno.Native.ImGui.ImGui; +using ImGuiApi = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Assets; -namespace Inno.Editor.Shaders; - -[InspectionDrawer(typeof(AssetInspectionSelection))] -internal sealed class MaterialSelectionDrawer(IInspectionIconProvider icons) : InspectionDrawer +namespace Inno.Editor.Shaders.ImGui; +[InspectionDrawer(typeof(EditorSelectionSnapshot), priority: 100, conditional: true)] +internal sealed class MaterialSelectionDrawer( + IInspectionIconProvider icons, + EditorInteractions interactions +) : InspectionDrawer { /// /// Gets the icon glyph used to represent this item in the editor. /// -public override string icon => Inno.Adapter.Presentation.ImGui.ImGuiIcon.File; + public override string icon => Inno.Editor.ImGui.ImGuiIcon.File; + /// /// Retrieves the current icon from authoritative state. /// @@ -30,16 +37,25 @@ internal sealed class MaterialSelectionDrawer(IInspectionIconProvider /// The validated text representation owned by the caller. /// -protected override string GetIcon( - InspectionDrawContext context, - AssetInspectionSelection target -) { - Guid id = target.assetIds.FirstOrDefault(); + protected override string GetIcon( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) { + Guid id = GetAssetIds(target).FirstOrDefault(); return id != Guid.Empty - && context.interactions.TryGetModule(out var documents) && documents is not null - && documents.assets.TryGetInfo(id, out AssetInfo? info) && info is not null - && documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry) ? icons.GetIcon(entry) : icon; + && context.interactions.TryGetModule(out var documents) + && documents is not null + && documents.assets.TryGetInfo( + id, + out AssetInfo? info + ) + && info is not null + && documents.assets.TryGetFileSystemEntry( + info.assetPath, + out AssetFileEntry entry + ) ? icons.GetIcon(entry) : icon; } + /// /// Binds a caller-visible label to the current inspection target. /// @@ -52,12 +68,10 @@ AssetInspectionSelection target /// /// The validated (string name, actionstring? setter) that represents the completed operation. /// -protected override (string name, Action? setter) BindName( - InspectionDrawContext context, - AssetInspectionSelection target -) - => (target.assetIds.Count + " Assets", null); - + protected override (string name, Action? setter) BindName( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) => (GetAssetIds(target).Count + " Assets", null); /// /// Renders the header presentation for the current editor frame. /// @@ -67,10 +81,10 @@ AssetInspectionSelection target /// /// The existing target that receives the validated result. /// -protected override void DrawHeader( - InspectionDrawContext context, - AssetInspectionSelection target -) { + protected override void DrawHeader( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) { if (!TryOpenDrafts(context, target, out MaterialDocuments? documents, out var drafts)) return; bool readOnly = drafts.Any(value => value.readOnly); @@ -79,14 +93,15 @@ AssetInspectionSelection target { if (ImGuiApi.Button("Save Selected")) { - documents!.CommitMany(target.assetIds); + EditorUiSession.current.widgets.CommitEdits(); foreach (var draft in drafts) _ = context.interactions.documents.Save(draft.documentId); } + ImGuiApi.SameLine(0f, -1f); if (ImGuiApi.Button("Revert Selected")) { - documents!.CommitMany(target.assetIds); + EditorUiSession.current.widgets.CommitEdits(); using var transaction = context.interactions.history.BeginTransaction("Revert Materials"); foreach (var draft in drafts) _ = context.interactions.documents.Revert(draft.documentId); @@ -108,55 +123,58 @@ AssetInspectionSelection target /// /// The existing target that receives the validated result. /// -protected override void Draw( - InspectionDrawContext context, - AssetInspectionSelection target -) { + protected override void Draw( + InspectionDrawContext context, + EditorSelectionSnapshot target + ) { if (!TryOpenDrafts(context, target, out MaterialDocuments? documents, out var drafts)) { - Widget.Hint("Select Materials with compatible Shader interfaces to edit their common parameters."); + EditorUI.current.widgets.Hint("Select Materials with compatible Shader interfaces to edit their common parameters."); return; } + MaterialDocuments owner = documents!; var materials = drafts.ToDictionary(value => value.id, value => owner.Read(value.id)); bool readOnly = drafts.Any(value => value.readOnly); - ImGuiApi.PushID("materials." + string.Join(".", target.assetIds)); + ImGuiApi.PushId("materials." + string.Join(".", GetAssetIds(target))); ImGuiApi.BeginDisabled(readOnly); try { - Widget.Hint("Save runs per document. Unsaved edits do not change Scene/Game."); + EditorUI.current.widgets.Hint("Save runs per document. Unsaved edits do not change Scene/Game."); if (readOnly) - Widget.Hint("Selection includes installed read-only content. Copy it to the project before editing."); + EditorUI.current.widgets.Hint("Selection includes installed read-only content. Copy it to the project before editing."); foreach (var draft in drafts.Where(value => value.error.Length != 0)) - Widget.Hint(draft.path.localPath + ": " + draft.error); + EditorUI.current.widgets.Hint(draft.path.localPath + ": " + draft.error); var edits = new SelectionEdits(owner, materials); MaterialAsset first = materials.Values.First(); - bool selectionOpen = Widget.SectionHeader("Material Selection", "Changes are recorded together. Changing Shader retains each Material's independent overrides."); + bool selectionOpen = EditorUI.current.widgets.SectionHeader( + "Material Selection", + "Changes are recorded together. Changing Shader retains each Material's independent overrides." + ); if (selectionOpen) { if (materials.Values.Any(value => value.shader?.identity.persistentId != first.shader?.identity.persistentId)) - Widget.Hint("Shader · Mixed values"); - context.properties.DrawValue(context.editorContext, target, "materials.shader", "Shader", typeof(ShaderAsset), - () => first.shader, value => - { - foreach (MaterialAsset material in materials.Values) - material.shader = (ShaderAsset?)value; - }, edits, readOnly); + EditorUI.current.widgets.Hint("Shader · Mixed values"); + context.properties.DrawValue(context.editorContext, target, "materials.shader", "Shader", typeof(ShaderAsset), () => first.shader, value => + { + foreach (MaterialAsset material in materials.Values) + material.shader = (ShaderAsset? )value; + }, edits, readOnly); } + if (first.shader is null || first.shader.isMissing || first.shader.definition is not { } firstDefinition) { if (selectionOpen) - Widget.Hint("A selected Shader is unavailable."); + EditorUI.current.widgets.Hint("A selected Shader is unavailable."); return; } - ShaderTechniqueId[] commonTechniques = firstDefinition.techniques.Select(value => value.id) - .Where(id => materials.Values.All(material => material.shader is { isMissing: false, definition: { } definition } - && definition.techniques.Any(technique => technique.id == id))).ToArray(); + + ShaderTechniqueId[] commonTechniques = firstDefinition.techniques.Select(value => value.id).Where(id => materials.Values.All(material => material.shader is { isMissing: false, definition: { } definition } && definition.techniques.Any(technique => technique.id == id))).ToArray(); bool mixedTechnique = materials.Values.Any(value => value.techniqueId != first.techniqueId); if (selectionOpen && (commonTechniques.Length > 1 || materials.Values.Any(value => value.techniqueId.isValid))) { string current = mixedTechnique ? "Mixed Techniques" : first.techniqueId.isValid ? first.techniqueId.value : "Automatic"; - if (Widget.BeginBoundedCombo("##materials.technique", current)) + if (EditorUI.current.widgets.BeginBoundedCombo("##materials.technique", current)) { try { @@ -168,20 +186,19 @@ AssetInspectionSelection target } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } - } - } + } + } foreach (ShaderKeywordDefinition keyword in selectionOpen ? firstDefinition.keywords : []) { - if (!materials.Values.All(material => material.shader is { isMissing: false, definition: { } definition } - && definition.keywords.Any(value => value.id == keyword.id && value.options.SequenceEqual(keyword.options)))) + if (!materials.Values.All(material => material.shader is { isMissing: false, definition: { } definition } && definition.keywords.Any(value => value.id == keyword.id && value.options.SequenceEqual(keyword.options)))) continue; string Option(MaterialAsset material) => keyword.options.FirstOrDefault(material.keywords.Contains) ?? "None"; string selected = Option(first); bool mixed = materials.Values.Any(material => Option(material) != selected); - if (!Widget.BeginBoundedCombo(keyword.id, mixed ? "Mixed values" : selected)) + if (!EditorUI.current.widgets.BeginBoundedCombo(keyword.id, mixed ? "Mixed values" : selected)) continue; try { @@ -193,8 +210,9 @@ AssetInspectionSelection target } finally { - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } + void SetOption(string? next) { foreach (MaterialAsset material in materials.Values) @@ -203,37 +221,45 @@ void SetOption(string? next) owner.ReplaceMany(materials); } } - bool parametersOpen = Widget.SectionHeader("Common Parameters", "Only compatible Material-owned inputs appear here. Unrelated overrides are retained per Material."); + + bool parametersOpen = EditorUI.current.widgets.SectionHeader( + "Common Parameters", + "Only compatible Material-owned inputs appear here. Unrelated overrides are retained per Material." + ); bool groupOpen = parametersOpen; string currentGroup = ""; foreach (ShaderPropertyDefinition property in firstDefinition.properties) { if (property.bindingOwner != ShaderPropertyBindingOwner.Material || property.bindingKind is not (ShaderPropertyBindingKind.Uniform or ShaderPropertyBindingKind.SampledTexture)) - continue; + continue; var values = new List(); ShaderParameterPresentation presentation = owner.Presentation(first.shader, property.id, out string presentationError); if (presentationError.Length != 0) - Widget.Hint("Parameter presentation unavailable: " + presentationError); + EditorUI.current.widgets.Hint("Parameter presentation unavailable: " + presentationError); if (!presentation.visible) continue; bool compatible = true; foreach (MaterialAsset material in materials.Values) { ShaderPropertyDefinition[] declarations = material.shader is { isMissing: false } shader ? shader.definition?.properties ?? [] : []; - int index = Array.FindIndex(declarations, value => value.id == property.id && value.type == property.type - && value.bindingKind == property.bindingKind && value.bindingOwner == property.bindingOwner); + int index = Array.FindIndex( + declarations, + value => value.id == property.id && value.type == property.type && value.bindingKind == property.bindingKind && value.bindingOwner == property.bindingOwner + ); if (index < 0) { compatible = false; break; } + ShaderParameterPresentation other = owner.Presentation(material.shader!, property.id, out _); if (!other.visible) { compatible = false; break; } + if (other.hasRange) { if (presentation.hasRange) @@ -253,42 +279,46 @@ void SetOption(string? next) presentation.maximum = other.maximum; } } + MaterialValue value = material.TryGet(property.id, out var overridden) ? overridden : declarations[index].defaultValue; if (!ShaderPropertyInspector.Compatible(property.type, value.kind)) { compatible = false; break; } + values.Add(value); } + if (!compatible) continue; if (presentation.group != currentGroup) { currentGroup = presentation.group; - groupOpen = Widget.SectionHeader(currentGroup.Length == 0 ? "Common Parameters" : currentGroup); + groupOpen = EditorUI.current.widgets.SectionHeader(currentGroup.Length == 0 ? "Common Parameters" : currentGroup); } + if (!groupOpen) continue; bool mixed = values.Skip(1).Any(value => !value.Equals(values[0])); - ImGuiApi.PushID(property.id.value); + ImGuiApi.PushId(property.id.value); try { if (mixed) - Widget.Hint(property.displayName + " · Mixed values (editing applies to all selected Materials)"); - ShaderPropertyInspector.Draw(context, target, "materials." + property.id.value, property, values[0], - value => - { - int selectedIndex = 0; - foreach (MaterialAsset material in materials.Values) - material.Set(property.id, ShaderPropertyInspector.ApplyEdit(property.type, values[0], value, values[selectedIndex++])); - }, edits, readOnly, presentation); + EditorUI.current.widgets.Hint(property.displayName + " · Mixed values (editing applies to all selected Materials)"); + ShaderPropertyInspector.Draw(context, target, "materials." + property.id.value, property, values[0], value => + { + int selectedIndex = 0; + foreach (MaterialAsset material in materials.Values) + material.Set(property.id, ShaderPropertyInspector.ApplyEdit(property.type, values[0], value, values[selectedIndex++])); + }, edits, readOnly, presentation); if (mixed && ImGuiApi.SmallButton("Use First Value for Selection")) { foreach (MaterialAsset material in materials.Values) material.Set(property.id, values[0]); owner.ReplaceMany(materials); } + if (ImGuiApi.SmallButton("Reset Selected Overrides")) { foreach (MaterialAsset material in materials.Values) @@ -298,17 +328,16 @@ void SetOption(string? next) } finally { - ImGuiApi.PopID(); + ImGuiApi.PopId(); } } + if (parametersOpen && materials.Values.Any(value => value.properties.Count != 0) && ImGuiApi.Button("Reset All Selected Overrides")) { foreach (MaterialAsset material in materials.Values) material.ReplaceProperties([]); owner.ReplaceMany(materials); } - if (!ImGuiApi.IsAnyItemActive()) - owner.CommitMany(target.assetIds); void SetTechnique(ShaderTechniqueId technique) { @@ -320,29 +349,70 @@ void SetTechnique(ShaderTechniqueId technique) finally { ImGuiApi.EndDisabled(); - ImGuiApi.PopID(); + ImGuiApi.PopId(); } } - private static bool TryOpenDrafts( + /// + protected override bool CanInspect(EditorSelectionSnapshot target) => target.identities.Count > 1 && GetAssetIds(target).Count == target.identities.Count; + private IReadOnlyList GetAssetIds(EditorSelectionSnapshot target) + { + if (!interactions.TryGetModule(out var materials) || materials is null) + return Array.Empty(); + var ids = new List(target.identities.Count); + var entries = new List(target.identities.Count); + target.CopyTargets(interactions, entries); + if (entries.Count != target.identities.Count) + return Array.Empty(); + entries.Reverse(); + foreach (AssetFileEntry entry in entries) + { + if (entry.isDirectory + || !entry.extension.Equals( + ".imaterial", + StringComparison.OrdinalIgnoreCase + ) + || !materials.assets.TryGetInfo( + entry.assetPath, + out AssetInfo? info + ) + || info is null) + return Array.Empty(); + ids.Add(info.persistentId); + } + + return ids; + } + + private bool TryOpenDrafts( InspectionDrawContext context, - AssetInspectionSelection target, + EditorSelectionSnapshot target, out MaterialDocuments? documents, out List.Draft> drafts ) { drafts = []; if (!context.interactions.TryGetModule(out documents) || documents is null) return false; - foreach (Guid id in target.assetIds) + foreach (Guid id in GetAssetIds(target)) { - if (!documents.assets.TryGetInfo(id, out AssetInfo? info) || info is null - || !documents.assets.TryGetFileSystemEntry(info.assetPath, out AssetFileEntry entry) - || !entry.extension.Equals(".imaterial", StringComparison.OrdinalIgnoreCase)) + if (!documents.assets.TryGetInfo( + id, + out AssetInfo? info + ) + || info is null + || !documents.assets.TryGetFileSystemEntry( + info.assetPath, + out AssetFileEntry entry + ) + || !entry.extension.Equals( + ".imaterial", + StringComparison.OrdinalIgnoreCase + )) return false; var draft = documents.OpenDraft(info.assetPath); - documents.TouchInspection(draft); drafts.Add(draft); } + return drafts.Count != 0; } @@ -351,32 +421,17 @@ private sealed class SelectionEdits( IReadOnlyDictionary materials ) : IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) { + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) { mutation(); - documents.ReplaceMany(materials, !ImGuiApi.IsAnyItemActive()); + documents.ReplaceMany(materials, mergeKey); return true; } } diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs similarity index 70% rename from src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs index 48a49638c..4b63d7825 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Materials/ShaderPropertyInspector.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Materials/ShaderPropertyInspector.cs @@ -1,11 +1,12 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Shaders; using System; using Inno.Core.Mathematics; using Inno.Editor.Inspection; using Inno.Rendering; using Inno.Rendering.Assets; -namespace Inno.Editor.Shaders; - +namespace Inno.Editor.Shaders.ImGui; /// /// Uses the shared Inspector drawers for both Shader defaults and Material overrides. /// @@ -52,17 +53,31 @@ public static void Draw( bool readOnly, ShaderParameterPresentation? presentation = null ) { - context.properties.DrawValue(context.editorContext, owner, path, property.displayName, ValueType(property.type), - () => Unbox(property.type, value), next => setter(Box(property.type, next, value)), edits, readOnly, - hdrColor: property.type == ShaderPropertyType.Color, tooltip: presentation?.description, + context.properties.DrawValue( + context.editorContext, + owner, + path, + property.displayName, + ValueType(property.type), + () => Unbox(property.type, value), + next => setter(Box(property.type, next, value)), + edits, + readOnly, + hdrColor: property.type == ShaderPropertyType.Color, + tooltip: presentation?.description, minimum: property.type == ShaderPropertyType.Float && presentation?.hasRange == true ? presentation.minimum : null, - maximum: property.type == ShaderPropertyType.Float && presentation?.hasRange == true ? presentation.maximum : null); + maximum: property.type == ShaderPropertyType.Float && presentation?.hasRange == true ? presentation.maximum : null + ); if (property.type == ShaderPropertyType.Color) - Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget.Hint("Linear RGBA · HDR values are preserved"); + EditorUI.current.widgets.Hint("Linear RGBA · HDR values are preserved"); if (property.bindingKind == ShaderPropertyBindingKind.SampledTexture) - context.properties.DrawValue(context.editorContext, owner, path + ".sampler", "Sampler", typeof(RenderSamplerState), - () => value.sampler, next => { var candidate = value; candidate.kind = MaterialValueKind.Texture; - candidate.sampler = (RenderSamplerState)next!; setter(candidate); }, edits, readOnly); + context.properties.DrawValue(context.editorContext, owner, path + ".sampler", "Sampler", typeof(RenderSamplerState), () => value.sampler, next => + { + var candidate = value; + candidate.kind = MaterialValueKind.Texture; + candidate.sampler = (RenderSamplerState)next!; + setter(candidate); + }, edits, readOnly); } /// @@ -89,7 +104,6 @@ MaterialValueKind kind ShaderPropertyType.Texture2D or ShaderPropertyType.Texture2DArray or ShaderPropertyType.Texture3D or ShaderPropertyType.TextureCube => kind == MaterialValueKind.Texture, _ => false }; - /// /// Applies the edited components of a representative value without replacing unrelated mixed components. /// @@ -126,51 +140,68 @@ MaterialValue target before.sampler.filter == edited.sampler.filter ? target.sampler.filter : edited.sampler.filter, before.sampler.addressU == edited.sampler.addressU ? target.sampler.addressU : edited.sampler.addressU, before.sampler.addressV == edited.sampler.addressV ? target.sampler.addressV : edited.sampler.addressV, - before.sampler.addressW == edited.sampler.addressW ? target.sampler.addressW : edited.sampler.addressW); + before.sampler.addressW == edited.sampler.addressW ? target.sampler.addressW : edited.sampler.addressW + ); } else if (edited.kind == MaterialValueKind.Matrix) { Matrix b = before.matrix, e = edited.matrix, t = target.matrix; target.matrix = new( - Merge(b.m11,e.m11,t.m11), Merge(b.m12,e.m12,t.m12), Merge(b.m13,e.m13,t.m13), Merge(b.m14,e.m14,t.m14), - Merge(b.m21,e.m21,t.m21), Merge(b.m22,e.m22,t.m22), Merge(b.m23,e.m23,t.m23), Merge(b.m24,e.m24,t.m24), - Merge(b.m31,e.m31,t.m31), Merge(b.m32,e.m32,t.m32), Merge(b.m33,e.m33,t.m33), Merge(b.m34,e.m34,t.m34), - Merge(b.m41,e.m41,t.m41), Merge(b.m42,e.m42,t.m42), Merge(b.m43,e.m43,t.m43), Merge(b.m44,e.m44,t.m44)); + Merge(b.m11, e.m11, t.m11), + Merge(b.m12, e.m12, t.m12), + Merge(b.m13, e.m13, t.m13), + Merge(b.m14, e.m14, t.m14), + Merge(b.m21, e.m21, t.m21), + Merge(b.m22, e.m22, t.m22), + Merge(b.m23, e.m23, t.m23), + Merge(b.m24, e.m24, t.m24), + Merge(b.m31, e.m31, t.m31), + Merge(b.m32, e.m32, t.m32), + Merge(b.m33, e.m33, t.m33), + Merge(b.m34, e.m34, t.m34), + Merge(b.m41, e.m41, t.m41), + Merge(b.m42, e.m42, t.m42), + Merge(b.m43, e.m43, t.m43), + Merge(b.m44, e.m44, t.m44) + ); } else { Vector4 b = before.vector, e = edited.vector, t = target.vector; - target.vector = new(Merge(b.x,e.x,t.x), Merge(b.y,e.y,t.y), Merge(b.z,e.z,t.z), Merge(b.w,e.w,t.w)); + target.vector = new(Merge(b.x, e.x, t.x), Merge(b.y, e.y, t.y), Merge(b.z, e.z, t.z), Merge(b.w, e.w, t.w)); } + return target; static float Merge( float previous, float next, float current - ) - => BitConverter.SingleToUInt32Bits(previous) == BitConverter.SingleToUInt32Bits(next) ? current : next; + ) => BitConverter.SingleToUInt32Bits(previous) == BitConverter.SingleToUInt32Bits(next) ? current : next; } private static Type ValueType(ShaderPropertyType type) => type switch { - ShaderPropertyType.Float => typeof(float), ShaderPropertyType.Vector2 => typeof(Vector2), - ShaderPropertyType.Vector3 => typeof(Vector3), ShaderPropertyType.Vector4 => typeof(Vector4), - ShaderPropertyType.Color => typeof(Color), ShaderPropertyType.Matrix4x4 => typeof(Matrix), + ShaderPropertyType.Float => typeof(float), + ShaderPropertyType.Vector2 => typeof(Vector2), + ShaderPropertyType.Vector3 => typeof(Vector3), + ShaderPropertyType.Vector4 => typeof(Vector4), + ShaderPropertyType.Color => typeof(Color), + ShaderPropertyType.Matrix4x4 => typeof(Matrix), ShaderPropertyType.Texture2D or ShaderPropertyType.Texture2DArray or ShaderPropertyType.Texture3D or ShaderPropertyType.TextureCube => typeof(TextureAsset), - _ => throw new InvalidOperationException("No material editor exists for " + type) - }; - + _ => throw new InvalidOperationException("No material editor exists for " + type)}; private static object? Unbox( ShaderPropertyType type, MaterialValue value ) => type switch { - ShaderPropertyType.Float => value.vector.x, ShaderPropertyType.Vector2 => new Vector2(value.vector.x, value.vector.y), - ShaderPropertyType.Vector3 => new Vector3(value.vector.x, value.vector.y, value.vector.z), ShaderPropertyType.Vector4 => value.vector, + ShaderPropertyType.Float => value.vector.x, + ShaderPropertyType.Vector2 => new Vector2(value.vector.x, value.vector.y), + ShaderPropertyType.Vector3 => new Vector3(value.vector.x, value.vector.y, value.vector.z), + ShaderPropertyType.Vector4 => value.vector, ShaderPropertyType.Color => new Color(value.vector.x, value.vector.y, value.vector.z, value.vector.w), - ShaderPropertyType.Matrix4x4 => value.matrix, _ => value.texture + ShaderPropertyType.Matrix4x4 => value.matrix, + _ => value.texture }; - private static MaterialValue Box( ShaderPropertyType type, object? value, @@ -180,8 +211,14 @@ MaterialValue previous ShaderPropertyType.Float => MaterialValue.FromFloat((float)value!), ShaderPropertyType.Vector2 => MaterialValue.FromVector(new Vector4(((Vector2)value!).x, ((Vector2)value!).y, 0, 0)), ShaderPropertyType.Vector3 => MaterialValue.FromVector(new Vector4(((Vector3)value!).x, ((Vector3)value!).y, ((Vector3)value!).z, 0)), - ShaderPropertyType.Vector4 => MaterialValue.FromVector((Vector4)value!), ShaderPropertyType.Color => MaterialValue.FromColor((Color)value!), + ShaderPropertyType.Vector4 => MaterialValue.FromVector((Vector4)value!), + ShaderPropertyType.Color => MaterialValue.FromColor((Color)value!), ShaderPropertyType.Matrix4x4 => MaterialValue.FromMatrix((Matrix)value!), - _ => new MaterialValue { kind = MaterialValueKind.Texture, texture = (TextureAsset?)value, sampler = previous.sampler } + _ => new MaterialValue + { + kind = MaterialValueKind.Texture, + texture = (TextureAsset? )value, + sampler = previous.sampler + } }; } diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs similarity index 82% rename from src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs index ed8108230..ac92c4ee0 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawer.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawer.cs @@ -1,12 +1,11 @@ +using Inno.Editor.Shaders; using System; using Inno.Core.Graphs; using Inno.Core.Serialization; using Inno.Editor.Rendering; using Inno.Editor.Inspection; -using ImGuiApi = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Shaders; +namespace Inno.Editor.Shaders.ImGui; /// /// Registers Editor-only node controls independently from a node's shader compiler. /// @@ -45,6 +44,7 @@ public ShaderNodeDrawerAttribute( this.createOrder = createOrder; this.separatorBefore = separatorBefore; } + /// /// Gets the stable node identity handled by this drawer. /// @@ -103,17 +103,14 @@ bool separatorBefore /// Gets the optional artist-facing node name. /// public string displayName { get; } - /// /// Gets the optional slash-delimited creation-menu path. /// public string createPath { get; } - /// /// Gets the stable ordering value within the creation menu. /// public int createOrder { get; } - /// /// Gets whether the contributed group starts a visual section. /// @@ -142,7 +139,7 @@ public sealed class ShaderNodeDrawContext private readonly GraphNodeRecord m_node; private readonly SerializationRegistry m_serialization; private readonly SerializationContext m_context; - private readonly Action m_write; + private readonly Action m_write; private readonly InspectionDrawContext m_inspection; private readonly bool m_readOnly; /// @@ -173,7 +170,7 @@ public ShaderNodeDrawContext( GraphNodeRecord node, SerializationRegistry serialization, SerializationContext context, - Action write, + Action write, IEditorPreviewService previews, InspectionDrawContext inspection, bool readOnly @@ -191,7 +188,6 @@ bool readOnly /// Gets the shared generation-scoped preview service for optional inline texture previews; valid only during this draw. /// public IEditorPreviewService previews { get; } - /// /// Gets the stable node identity for Editor widget IDs. /// @@ -215,9 +211,7 @@ bool readOnly public T Read( string key, T defaultValue - ) - => Inno.Rendering.Shaders.ShaderGraphDocument.Read(m_node, key, defaultValue, m_serialization, m_context); - + ) => Inno.Rendering.Shaders.ShaderGraphDocument.Read(m_node, key, defaultValue, m_serialization, m_context); /// /// Records an unsaved draft edit through shared history; only an explicit document save applies it to the asset. /// @@ -230,17 +224,17 @@ T defaultValue /// /// New detached value. /// - /// - /// Whether this sample belongs to the active text or numeric gesture. + /// + /// The current shared gesture key, or null for an independent edit. /// public void Write( string key, T value, - bool continuous = false + string? mergeKey = null ) { if (m_readOnly) throw new InvalidOperationException("Copy this installed Shader into the project before editing."); - m_write(key, Inno.Rendering.Shaders.ShaderGraphDocument.Encode(value, m_serialization, m_context), continuous); + m_write(key, Inno.Rendering.Shaders.ShaderGraphDocument.Encode(value, m_serialization, m_context), mergeKey); } /// @@ -264,41 +258,28 @@ public void DrawProperty( T defaultValue ) { T value = Read(key, defaultValue); - m_inspection.properties.DrawValue(m_inspection.editorContext, m_node, "shader.node." + key, label, typeof(T), - () => value, edited => value = (T)edited!, new Edits(mutation => - { - mutation(); - Write(key, value, ImGuiApi.IsAnyItemActive()); - }), m_readOnly); + m_inspection.properties.DrawValue(m_inspection.editorContext, m_node, "shader.node." + key, label, typeof(T), () => value, edited => value = (T)edited!, new Edits(( + mutation, + mergeKey + ) => + { + mutation(); + Write(key, value, mergeKey); + }), m_readOnly); } - private sealed class Edits(Action apply) : IInspectionPropertyEditService + private sealed class Edits(Action apply) : IInspectionPropertyEditService { - /// - /// Applies one serialized property edit and records its reversible history payload. - /// - /// - /// The object that owns the resulting lifetime and state. - /// - /// - /// The property name text validated by the change property operation. - /// - /// - /// The callback invoked by change property within the operation's owned lifetime. - /// - /// - /// The history name text validated by the change property operation. - /// - /// - /// when the documented condition is satisfied; otherwise, . - /// -public bool ChangeProperty( - object owner, - string propertyName, - Action mutation, - string historyName -) { - apply(mutation); + /// + public bool ChangeProperty( + object owner, + string propertyName, + Action mutation, + string historyName, + string? mergeKey, + string? componentName + ) { + apply(mutation, mergeKey); return true; } } diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs similarity index 93% rename from src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs index 7f7e36387..6ffb0b791 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Nodes/ShaderNodeDrawerRegistry.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Nodes/ShaderNodeDrawerRegistry.cs @@ -1,11 +1,11 @@ +using Inno.Editor.Shaders; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Inno.Extensibility.Types; -namespace Inno.Editor.Shaders; - +namespace Inno.Editor.Shaders.ImGui; /// /// Shares generation-scoped node presentation between graph and Inspector hosts. /// @@ -13,7 +13,6 @@ public sealed class ShaderNodeDrawerRegistry : IDisposable { private readonly TypeCatalog m_types; private readonly Registry m_registry; - /// /// Creates a presentation registry owned by the current editor feature lifetime. /// @@ -88,7 +87,6 @@ out ShaderNodePresentation presentation /// Retires the current presentation providers through the shared lifecycle. /// public void Dispose() => m_registry.Dispose(); - private sealed class Registry(TypeCatalog types) : TypeRegistry(types) { internal Snapshot snapshot => current; @@ -102,7 +100,7 @@ private sealed class Registry(TypeCatalog types) : TypeRegistry(types) /// /// The validated snapshot that represents the completed operation. /// -protected override Snapshot Build(TypeCacheSnapshot snapshot) + protected override Snapshot Build(TypeCacheSnapshot snapshot) { var result = new Dictionary(StringComparer.Ordinal); var names = new Dictionary(StringComparer.Ordinal); @@ -119,6 +117,7 @@ protected override Snapshot Build(TypeCacheSnapshot snapshot) names.Add(id, attribute.displayName); presentations.Add(id, new(attribute.displayName, Normalize(attribute.createPath), attribute.createOrder, attribute.separatorBefore)); } + return new(result, names, presentations); } catch (Exception failure) @@ -131,6 +130,7 @@ protected override Snapshot Build(TypeCacheSnapshot snapshot) { throw new AggregateException(failure, retirement); } + throw; } } @@ -141,12 +141,10 @@ protected override Snapshot Build(TypeCacheSnapshot snapshot) /// /// The immutable state snapshot consumed by this operation. /// -protected override void DisposeSnapshot(Snapshot snapshot) => DisposeExtensions(snapshot.drawers.Values); + protected override void DisposeSnapshot(Snapshot snapshot) => DisposeExtensions(snapshot.drawers.Values); } - private static string Normalize(string value) - => string.Join('/', value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); - + private static string Normalize(string value) => string.Join('/', value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); private sealed record Snapshot( IReadOnlyDictionary drawers, IReadOnlyDictionary names, diff --git a/src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs similarity index 82% rename from src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs rename to src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs index 2908242ee..286b829e2 100644 --- a/src/composition/editor/features/Inno.Editor.Shaders/Previews/ShaderPreviews.cs +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Previews/ShaderPreviews.cs @@ -1,3 +1,5 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Shaders; using System; using System.Collections.Generic; using System.Linq; @@ -8,12 +10,10 @@ using Inno.Editor.Rendering; using Inno.Extensibility.Types; using Inno.Rendering; -using ImGuiApi = Inno.Native.ImGui.ImGui; -using Widget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using ImGuiApi = Inno.Editor.ImGui.ImGui; using Inno.Rendering.Assets; -namespace Inno.Editor.Shaders; - +namespace Inno.Editor.Shaders.ImGui; /// /// Hosts isolated Shader and Material images using domain-contributed preview pipelines. /// @@ -26,7 +26,6 @@ public sealed class ShaderPreviews : EditorModule private readonly EditorShaderCompilation m_compilation; private readonly Dictionary m_states = []; private ulong m_frame; - internal ShaderPreviews( TypeCatalog types, IEditorPreviewService previews, @@ -57,16 +56,17 @@ float logicalSize ) { if (material.shader is null || material.shader.isMissing) { - Widget.Hint("Preview requires an available Shader."); + EditorUI.current.widgets.Hint("Preview requires an available Shader."); return; } + try { Draw(ownerId, material, m_compilation.RequestArtifact(material.shader, RenderShaderVariant.FromMaterial(material)), logicalSize); } catch (Exception error) when (Recoverable(error)) { - Widget.Hint("Preview: " + error.Message); + EditorUI.current.widgets.Hint("Preview: " + error.Message); } } @@ -93,14 +93,16 @@ float logicalSize ) { if (ownerId == Guid.Empty) throw new ArgumentException("A preview owner identity is required.", nameof(ownerId)); - ArgumentNullException.ThrowIfNull(material); ArgumentNullException.ThrowIfNull(compilation); + ArgumentNullException.ThrowIfNull(material); + ArgumentNullException.ThrowIfNull(compilation); if (!(logicalSize > 0f) || !float.IsFinite(logicalSize)) throw new ArgumentOutOfRangeException(nameof(logicalSize)); if (compilation.artifact is not { } artifact) { - Widget.Hint("Preview: " + compilation.state); + EditorUI.current.widgets.Hint("Preview: " + compilation.state); return; } + if (!m_states.TryGetValue(ownerId, out State? state)) m_states.Add(ownerId, state = new("shader-preview/" + ownerId.ToString("N"))); state.frame = m_frame; @@ -108,18 +110,19 @@ float logicalSize { using IDisposable operation = m_types.AcquireOperation("Build isolated Shader preview"); ShaderDefinition definition = m_compilation.ReadDefinition(artifact); - string[] contracts = definition.techniques.Where(value => !material.techniqueId.isValid || value.id == material.techniqueId) - .Select(value => value.contract.value).Distinct(StringComparer.Ordinal).Where(m_registry.providers.ContainsKey).ToArray(); + string[] contracts = definition.techniques.Where(value => !material.techniqueId.isValid || value.id == material.techniqueId).Select(value => value.contract.value).Distinct(StringComparer.Ordinal).Where(m_registry.providers.ContainsKey).ToArray(); if (contracts.Length == 0) { - Widget.Hint("No preview provider for this Shader contract."); + EditorUI.current.widgets.Hint("No preview provider for this Shader contract."); return; } + if (contracts.Length != 1) { - Widget.Hint("Select a Technique to choose an unambiguous preview contract."); + EditorUI.current.widgets.Hint("Select a Technique to choose an unambiguous preview contract."); return; } + int pixels = Math.Clamp((int)MathF.Ceiling(logicalSize * ImGuiApi.GetWindowDpiScale()), 1, 2048); var context = new ShaderPreviewContext(new(state.viewportId), material, artifact, definition, state, pixels, pixels); EditorViewportLayer layer = m_registry.providers[contracts[0]].CreateLayer(context); @@ -127,13 +130,13 @@ float logicalSize m_previews.Draw(handle, new(logicalSize, logicalSize)); else ImGuiApi.Dummy(new(logicalSize, logicalSize)); - Widget.Hint(compilation.usingLastGood ? "Draft preview · last-good candidate · Scene/Game unchanged" : "Isolated preview · Scene/Game unchanged"); + EditorUI.current.widgets.Hint(compilation.usingLastGood ? "Draft preview · last-good candidate · Scene/Game unchanged" : "Isolated preview · Scene/Game unchanged"); foreach (Diagnostic error in state.errors.Values) - Widget.WrappedText(error.message); + EditorUI.current.widgets.WrappedText(error.message); } catch (Exception error) when (Recoverable(error)) { - Widget.Hint("Preview: " + error.Message); + EditorUI.current.widgets.Hint("Preview: " + error.Message); } } @@ -161,6 +164,7 @@ protected override void OnUpdate(EditorContext context) foreach (Guid id in m_states.Where(pair => m_frame - pair.Value.frame > 2).Select(pair => pair.Key).ToArray()) Release(id); } + /// /// Stops this feature before its owning runtime releases the active generation. /// @@ -176,7 +180,6 @@ protected override void OnStop(EditorContext context) private static bool Recoverable(Exception error) => RetirementPendingException.Find(error) is null && error is InvalidOperationException or ArgumentException or FormatException; - private sealed class State(string viewportId) : IDiagnosticReporter { internal readonly string viewportId = viewportId; @@ -188,7 +191,7 @@ private sealed class State(string viewportId) : IDiagnosticReporter /// /// The diagnostic consumed by publish; ownership remains with the caller unless explicitly stated otherwise. /// -public void Publish(Diagnostic diagnostic) => errors[(diagnostic.code, diagnostic.semanticId, diagnostic.objectId)] = diagnostic; + public void Publish(Diagnostic diagnostic) => errors[(diagnostic.code, diagnostic.semanticId, diagnostic.objectId)] = diagnostic; /// /// Resolves the requested stable identity to its current-generation value. /// @@ -201,28 +204,29 @@ private sealed class State(string viewportId) : IDiagnosticReporter /// /// The object id consumed by resolve; ownership remains with the caller unless explicitly stated otherwise. /// -public void Resolve( - string code, - string? semanticId = null, - Guid? objectId = null -) => errors.Remove((code, semanticId, objectId)); + public void Resolve( + string code, + string? semanticId = null, + Guid? objectId = null + ) => errors.Remove((code, semanticId, objectId)); /// /// Records errors from the current diagnostic report. /// /// /// The diagnostics consumed by replace; ownership remains with the caller unless explicitly stated otherwise. /// -public void Replace(IEnumerable diagnostics) -{ - errors.Clear(); - foreach (Diagnostic value in diagnostics) - Publish(value); -} + public void Replace(IEnumerable diagnostics) + { + errors.Clear(); + foreach (Diagnostic value in diagnostics) + Publish(value); + } } private sealed class Registry(TypeCatalog types) : TypeRegistry>(types) { internal IReadOnlyDictionary providers => current; + /// /// Builds a validated result from the current immutable input snapshot. /// @@ -232,7 +236,7 @@ private sealed class Registry(TypeCatalog types) : TypeRegistry /// An immutable snapshot of the values selected by the operation. /// -protected override IReadOnlyDictionary Build(TypeCacheSnapshot snapshot) + protected override IReadOnlyDictionary Build(TypeCacheSnapshot snapshot) { var result = new Dictionary(StringComparer.Ordinal); try @@ -244,6 +248,7 @@ protected override IReadOnlyDictionary Build(Type throw new InvalidOperationException("Duplicate Shader preview contract: " + id); result.Add(id, CreateExtension(type)); } + return result; } catch (Exception failure) @@ -256,15 +261,17 @@ protected override IReadOnlyDictionary Build(Type { throw new AggregateException(failure, retirement); } + throw; } } + /// /// Releases the generation lease retained by an immutable registry snapshot. /// /// /// The immutable state snapshot consumed by this operation. /// -protected override void DisposeSnapshot(IReadOnlyDictionary snapshot) => DisposeExtensions(snapshot.Values); + protected override void DisposeSnapshot(IReadOnlyDictionary snapshot) => DisposeExtensions(snapshot.Values); } } diff --git a/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Properties/ScriptingApi.cs b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Properties/ScriptingApi.cs new file mode 100644 index 000000000..a5f010ab8 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Shaders.ImGui/Properties/ScriptingApi.cs @@ -0,0 +1,9 @@ +using Inno.Editor.Shaders.ImGui; +using Inno.Scripting.Api; + +[assembly: ScriptingApiNamespace("InnoEditor.Shaders", "Inno.Editor.Shaders.ImGui", ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ShaderNodeDrawerAttribute), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ShaderNodeDrawer), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ShaderNodeDrawContext), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ShaderPropertyInspector), ScriptingApiScope.Editor)] +[assembly: ScriptingApiExport(typeof(ShaderPreviews), ScriptingApiScope.Editor)] diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/HistoryActions.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/HistoryActions.cs similarity index 74% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/HistoryActions.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Actions/HistoryActions.cs index 1e328542b..5202c7bc9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/HistoryActions.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/HistoryActions.cs @@ -1,8 +1,7 @@ using Inno.Core.Input; using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; [EditorAction(GlobalInteractionIds.C_UNDO, priority: 1000)] [EditorMenu(GlobalInteractionIds.C_MAIN_MENU_AREA, "Edit/Undo", order: 100)] [EditorShortcut(KeyCode.Z, primary: true)] @@ -17,16 +16,12 @@ internal sealed class UndoEditorAction : EditorAction /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => new( - isVisible: true, - isEnabled: context.history.canUndo, - displayName: context.history.undoName is { } name - ? context.history.undoUnavailableReason is { } reason - ? $"Undo {name} ({reason})" - : $"Undo {name}" - : "Undo"); - + protected override EditorActionState Query(EditorActionContext context) => new( + isVisible: true, + isEnabled: context.history.canUndo, + displayName: context.history.undoName is { } name ? $"Undo {name}" : "Undo", + unavailableReason: context.history.undoUnavailableReason + ); /// /// Executes the prepared operation and publishes only a completed result. /// @@ -51,16 +46,12 @@ internal sealed class RedoEditorAction : EditorAction /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => new( - isVisible: true, - isEnabled: context.history.canRedo, - displayName: context.history.redoName is { } name - ? context.history.redoUnavailableReason is { } reason - ? $"Redo {name} ({reason})" - : $"Redo {name}" - : "Redo"); - + protected override EditorActionState Query(EditorActionContext context) => new( + isVisible: true, + isEnabled: context.history.canRedo, + displayName: context.history.redoName is { } name ? $"Redo {name}" : "Redo", + unavailableReason: context.history.redoUnavailableReason + ); /// /// Executes the prepared operation and publishes only a completed result. /// diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/SelectionActions.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/SelectionActions.cs similarity index 96% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/SelectionActions.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Actions/SelectionActions.cs index 9984397cb..e5a24c756 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/SelectionActions.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/SelectionActions.cs @@ -1,7 +1,6 @@ using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; [EditorAction(GlobalInteractionIds.C_CLEAR_SELECTION)] internal sealed class ClearEditorSelectionAction : EditorAction { diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/TogglePanelAction.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/TogglePanelAction.cs similarity index 62% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/TogglePanelAction.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Actions/TogglePanelAction.cs index d17e47d38..14ab346e4 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Actions/TogglePanelAction.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Actions/TogglePanelAction.cs @@ -1,10 +1,18 @@ using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; [EditorAction(GlobalInteractionIds.C_TOGGLE_PANEL, GlobalInteractionIds.C_MAIN_MENU_AREA)] internal sealed class TogglePanelAction : EditorArgumentAction { + /// + protected override EditorActionState Query(EditorActionArgumentContext context) => context.interactions.TryGetPanelState( + context.argument, + out bool isOpen + ) ? new EditorActionState( + true, + true, + isChecked: isOpen + ) : EditorActionState.hidden; /// /// Executes the prepared operation and publishes only a completed result. /// diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchAction.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchAction.cs new file mode 100644 index 000000000..c38394789 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchAction.cs @@ -0,0 +1,14 @@ +using Inno.Core.Input; +using Inno.Editor.Interactions; + +namespace Inno.Editor.Workbench; +[EditorAction(GlobalInteractionIds.C_FIND_COMMAND)] +[EditorMenu(GlobalInteractionIds.C_MAIN_MENU_AREA, "Tools/Find Command...", order: 0)] +[EditorShortcut(KeyCode.P, KeyModifier.Shift, primary: true)] +internal sealed class CommandSearchAction(CommandSearchModule commands) : EditorAction +{ + /// + protected override EditorActionState Query(EditorActionContext context) => commands.isOpen ? EditorActionState.disabled : EditorActionState.enabled; + /// + protected override void Execute(EditorActionContext context) => commands.Open(); +} diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModal.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModal.cs new file mode 100644 index 000000000..7bb0bc72d --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModal.cs @@ -0,0 +1,47 @@ +using System.Numerics; +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Workbench; +[EditorModal(GlobalInteractionIds.C_COMMAND_SEARCH_MODAL, "Find Command", order: 50)] +internal sealed class CommandSearchModal( + CommandSearchModule commands, + EditorUiSession ui +) : EditorModal +{ + /// + public override bool isVisible => commands.isOpen; + /// + public override bool allowScrolling => false; + /// + public override Vector2 initialSize => new(620f, 440f); + + /// + protected override void OnDraw(EditorContext context) + { + if (commands.requestsFocus) + { + Ui.SetKeyboardFocusHere(); + commands.requestsFocus = false; + } + + string search = commands.search; + _ = ui.widgets.SearchInput("commands", "Search commands...", ref search); + commands.search = search; + bool opened = Ui.BeginChild("##command-results", new Vector2(0f, -Ui.GetFrameHeightWithSpacing()), 0, ImGuiWindowFlags.NoSavedSettings); + bool chosen = false; + try + { + if (opened) + chosen = commands.Draw(ui); + } + finally + { + Ui.EndChild(); + } + + if (chosen || ui.widgets.CenteredButton("Close") || Ui.IsKeyPressed(ImGuiKey.Escape, false)) + commands.Close(); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModule.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModule.cs new file mode 100644 index 000000000..3f093368c --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Commands/CommandSearchModule.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Inno.Core.Identity; +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using Inno.Editor.Interactions; + +namespace Inno.Editor.Workbench; +[EditorModule(GlobalInteractionIds.C_COMMAND_SEARCH_MODULE, order: 20)] +internal sealed class CommandSearchModule(EditorInteractions interactions) : EditorModule +{ + private string m_area = "editor/global"; + private RuntimeIdentity? m_target; + internal bool isOpen { get; private set; } + internal string search { get; set; } = string.Empty; + internal bool requestsFocus { get; set; } + + internal void Open() + { + m_area = interactions.focusedArea; + m_target = (interactions.focusedTarget as IdentityObject)?.identity.runtimeIdentity; + search = string.Empty; + requestsFocus = true; + isOpen = true; + } + + internal void Close() + { + isOpen = false; + m_target = null; + search = string.Empty; + } + + internal bool Draw(EditorUiSession ui) + { + EditorInteraction global = interactions.For(GlobalInteractionIds.C_MAIN_MENU_AREA); + EditorMenuModel globalMenu = global.BuildMenu(); + var globalIds = new HashSet(StringComparer.Ordinal); + Collect(globalMenu.items); + if (ui.menus.DrawSearchItems(global, globalMenu.items, search)) + return true; + object? target = m_target is RuntimeIdentity id && interactions.TryResolveIdentity(id, out IdentityObject? live) ? live : null; + EditorInteraction local = interactions.For(m_area, target); + EditorMenuModel commands = local.BuildCommands(); + var localItems = Leaves(commands.items).Where(item => !globalIds.Contains(item.actionId)).ToArray(); + return ui.menus.DrawSearchItems(local, localItems, search); + void Collect(IReadOnlyList items) + { + foreach (EditorMenuItem item in items) + { + if (item.actionId.Length != 0) + _ = globalIds.Add(item.actionId); + Collect(item.children); + } + } + } + + /// + protected override void OnStop(EditorContext context) => Close(); + private static IEnumerable Leaves(IReadOnlyList items) + { + foreach (EditorMenuItem item in items) + { + if (item.children.Count == 0) + yield return item; + else + { + foreach (EditorMenuItem child in Leaves(item.children)) + yield return child; + } + } + } +} diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Interactions/ExportActions.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportActions.cs similarity index 86% rename from src/composition/editor/features/Inno.Editor.Exporting/Interactions/ExportActions.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportActions.cs index e61113bd3..75dcf1557 100644 --- a/src/composition/editor/features/Inno.Editor.Exporting/Interactions/ExportActions.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportActions.cs @@ -1,13 +1,8 @@ using Inno.Editor.Interactions; -namespace Inno.Editor.Exporting; - +namespace Inno.Editor.Workbench; [EditorAction(ExportInteractionIds.C_EXPORT_PLUGIN)] -[EditorMenu( - ExportInteractionIds.C_MAIN_MENU_AREA, - "File/Export as Plugin...", - order: 800, - separatorBefore: true)] +[EditorMenu(ExportInteractionIds.C_MAIN_MENU_AREA, "File/Export as Plugin...", order: 800, separatorBefore: true)] internal sealed class ExportPluginAction(ExportWindowModule window) : EditorAction { /// diff --git a/src/composition/editor/features/Inno.Editor.Exporting/ExportInteractionIds.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportInteractionIds.cs similarity index 87% rename from src/composition/editor/features/Inno.Editor.Exporting/ExportInteractionIds.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportInteractionIds.cs index 56f6d8015..e27e00e2b 100644 --- a/src/composition/editor/features/Inno.Editor.Exporting/ExportInteractionIds.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportInteractionIds.cs @@ -1,5 +1,4 @@ -namespace Inno.Editor.Exporting; - +namespace Inno.Editor.Workbench; internal static class ExportInteractionIds { internal const string C_EXPORT_GAME = "export/game"; diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportWindowModule.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportWindowModule.cs new file mode 100644 index 000000000..f83f8353f --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/ExportWindowModule.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Inno.Build; +using Inno.Build.Managed; +using Inno.Core.Settings; +using Inno.Editor.Core; +using Inno.Editor.Exporting; + +namespace Inno.Editor.Workbench; +[EditorModule("export.windows", order: 310)] +internal sealed class ExportWindowModule( + EditorContext m_editor, + EditorExportService m_exports, + BuildSettingsStore m_buildSettings, + ProjectSettingsStore m_projectSettings +) : EditorModule +{ + private bool m_gameProgressVisible; + private string m_formError = string.Empty; + internal bool isPluginVisible { get; private set; } + internal bool isGameVisible { get; private set; } + internal bool isGameProgressVisible => m_gameProgressVisible && m_exports.isGameBusy; + internal bool isPluginBusy => m_exports.isPluginBusy; + internal bool isGameBusy => m_exports.isGameBusy; + internal string pluginId => m_projectSettings.projectId.value; + internal string pluginDisplayName { get; set; } = string.Empty; + internal string pluginOutputPath { get; set; } = string.Empty; + internal string gameApplicationId => m_projectSettings.projectId.value; + internal string gameProductName { get; set; } = string.Empty; + internal string gamePersistentDataPath { get; set; } = string.Empty; + internal string gameStartupScene { get; set; } = string.Empty; + internal string gameOutputDirectory { get; set; } = string.Empty; + internal int gameWindowWidth { get; set; } = 1280; + internal int gameWindowHeight { get; set; } = 720; + internal BuildTargetId gameTarget { get; set; } + internal ManagedDeploymentId? gameManagedDeployment { get; set; } + internal IReadOnlyList availableManagedDeployments => m_exports.GetManagedDeployments(gameTarget); + internal IReadOnlyList availableGameTargets => m_exports.availableGameTargets; + internal string status => m_exports.status; + internal string error => m_formError.Length == 0 ? m_exports.error : m_formError; + internal float gameProgress => m_exports.gameProgress; + internal bool includePluginDependencies { get; set; } + + internal string GetGameTargetDisplayName(BuildTargetId target) => m_exports.GetGameTargetDisplayName(target); + internal void OpenPlugin() + { + if (isPluginBusy || isGameBusy) + return; + CloseGame(); + BuildSettings defaults = LoadBuildSettings(); + pluginDisplayName = defaults.pluginDisplayName; + pluginOutputPath = defaults.pluginOutputPath; + includePluginDependencies = defaults.includePluginDependencies; + isPluginVisible = true; + } + + internal void OpenGame() + { + if (isPluginBusy || isGameBusy) + return; + ClosePlugin(); + m_gameProgressVisible = false; + BuildSettings defaults = LoadBuildSettings(); + gameProductName = defaults.gameProductName; + gamePersistentDataPath = defaults.gamePersistentDataPath; + gameStartupScene = defaults.gameStartupScene; + gameOutputDirectory = defaults.gameOutputDirectory; + gameWindowWidth = defaults.gameWindowWidth; + gameWindowHeight = defaults.gameWindowHeight; + gameTarget = defaults.gameTarget; + gameManagedDeployment = defaults.gameManagedDeployment; + isGameVisible = true; + } + + internal void BeginPluginExport() + { + m_formError = string.Empty; + m_exports.StartPlugin(new PluginBuildRequest { pluginId = pluginId, displayName = pluginDisplayName, outputPath = ResolveOutputPath(pluginOutputPath), includeDependencies = includePluginDependencies }); + } + + internal void BeginGameExport() + { + m_formError = string.Empty; + m_exports.StartGame(new GameBuildRequest { profile = new BuildProfile { applicationId = gameApplicationId, productName = gameProductName, persistentDataPath = gamePersistentDataPath, startupScene = gameStartupScene, target = gameTarget, managedDeployment = gameManagedDeployment, windowWidth = gameWindowWidth, windowHeight = gameWindowHeight }, outputDirectory = ResolveOutputPath(gameOutputDirectory) }); + isGameVisible = false; + m_gameProgressVisible = true; + } + + internal void CancelPluginExport() => m_exports.Cancel(); + internal void CancelGameExport() => m_exports.Cancel(); + internal void ClosePlugin() + { + if (isPluginBusy) + m_exports.Cancel(); + isPluginVisible = false; + } + + internal void CloseGame() + { + if (isGameBusy) + m_exports.Cancel(); + isGameVisible = false; + } + + private BuildSettings LoadBuildSettings() + { + try + { + return m_buildSettings.Load(); + } + catch (Exception exception) when (exception is IOException or InvalidDataException or InvalidOperationException or UnauthorizedAccessException) + { + m_formError = exception.Message; + return m_buildSettings.defaultSettings; + } + } + + private string ResolveOutputPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return path; + return Path.IsPathFullyQualified(path) ? Path.GetFullPath(path) : Path.GetFullPath(path, m_editor.projectDirectory); + } +} diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportModal.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportModal.cs new file mode 100644 index 000000000..76940a130 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportModal.cs @@ -0,0 +1,222 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using System; +using System.Numerics; +using Inno.Build; +using Inno.Build.Managed; +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Workbench; +[EditorModal("export.game", "Export as Game", order: 310)] +internal sealed class GameExportModal(ExportWindowModule window) : EditorModal +{ + private const int C_TEXT_CAPACITY = 4096; + /// + /// Gets whether this implementation is visible. + /// + public override bool isVisible => window.isGameVisible; + /// + /// Gets whether this implementation can move. + /// + public override bool canMove => true; + /// + /// Gets whether this implementation can resize. + /// + public override bool canResize => true; + /// + /// Gets the preferred initial window size in logical editor units. + /// + public override Vector2 initialSize => new(760f, 540f); + /// + /// Gets the smallest size that keeps the export form usable. + /// + public override Vector2 minimumSize => new(620f, 460f); + + /// + /// Draws this feature using the current editor presentation context. + /// + /// + /// The context that supplies state and services for this operation. + /// + protected override void OnDraw(EditorContext context) + { + EditorUI.current.widgets.WrappedText("Build a standalone Player from the current project. Only imported runtime content " + "is deployed; source files stay in the project."); + Ui.SeparatorText("Application"); + if (BeginFields("##application_fields")) + { + try + { + EditorUI.current.widgets.SetupPropertyColumns(); + DrawReadOnly("Application ID", window.gameApplicationId); + DrawText("Product Name", "game_name", window.gameProductName, value => window.gameProductName = value); + DrawText("Persistent Data Folder", "game_data", window.gamePersistentDataPath, value => window.gamePersistentDataPath = value); + DrawText("Startup Scene", "game_scene", window.gameStartupScene, value => window.gameStartupScene = value); + } + finally + { + Ui.EndTable(); + } + } + + Ui.SeparatorText("Window and Platform"); + if (BeginFields("##platform_fields")) + { + try + { + EditorUI.current.widgets.SetupPropertyColumns(); + DrawWindowSize(); + DrawTarget(); + DrawManagedDeployment(); + } + finally + { + Ui.EndTable(); + } + } + + Ui.SeparatorText("Output"); + if (BeginFields("##output_fields")) + { + try + { + EditorUI.current.widgets.SetupPropertyColumns(); + DrawText("Output Directory", "game_output", window.gameOutputDirectory, value => window.gameOutputDirectory = value); + } + finally + { + Ui.EndTable(); + } + } + + Ui.Spacing(); + EditorUI.current.widgets.WrappedText("Defaults: Settings > Build > Game. Application ID: Settings > Project > Identity."); + if (!string.IsNullOrEmpty(window.error)) + { + Ui.Spacing(); + EditorUI.current.widgets.WrappedText(window.error); + } + + Ui.Separator(); + DrawButtons(); + } + + private static bool BeginFields(string id) => Ui.BeginTable( + id, + 2, + ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.NoPadOuterX, + default(Vector2), + 0f); + + private static void BeginField(string label) + { + Ui.TableNextRow(); + _ = Ui.TableSetColumnIndex(0); + EditorUI.current.widgets.PropertyLabel(label); + _ = Ui.TableSetColumnIndex(1); + } + + private static void DrawReadOnly( + string label, + string value + ) { + BeginField(label); + Ui.PushStyleColor((ImGuiCol.Text), EditorPalette.textDisabled); + try + { + EditorUI.current.widgets.WrappedText(value); + } + finally + { + Ui.PopStyleColor(); + } + } + + private void DrawWindowSize() + { + int width = window.gameWindowWidth; + int height = window.gameWindowHeight; + BeginField("Initial Width"); + Ui.SetNextItemWidth(-1f); + if (Ui.InputInt("##game_window_width", ref width, 1, 100, 0)) + window.gameWindowWidth = Math.Max(1, width); + BeginField("Initial Height"); + Ui.SetNextItemWidth(-1f); + if (Ui.InputInt("##game_window_height", ref height, 1, 100, 0)) + window.gameWindowHeight = Math.Max(1, height); + } + + private void DrawManagedDeployment() + { + BeginField("Managed Deployment"); + Ui.SetNextItemWidth(-1f); + if (!EditorUI.current.widgets.BeginBoundedCombo("##game_deployment", window.gameManagedDeployment?.value ?? "Platform default")) + return; + try + { + if (Ui.Selectable("Platform default", window.gameManagedDeployment is null)) + window.gameManagedDeployment = null; + foreach (ManagedDeploymentId deployment in window.availableManagedDeployments) + if (Ui.Selectable(deployment.value, window.gameManagedDeployment == deployment)) + window.gameManagedDeployment = deployment; + } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } + } + + private void DrawTarget() + { + BeginField("Target"); + Ui.SetNextItemWidth(-1f); + string preview = window.GetGameTargetDisplayName(window.gameTarget); + if (!EditorUI.current.widgets.BeginBoundedCombo("##game_target", preview)) + return; + try + { + foreach (BuildTargetId target in window.availableGameTargets) + DrawTargetChoice(target); + } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } + } + + private void DrawTargetChoice(BuildTargetId target) + { + bool selected = window.gameTarget == target; + if (Ui.Selectable(window.GetGameTargetDisplayName(target), selected)) + window.gameTarget = target; + if (selected) + Ui.SetItemDefaultFocus(); + } + + private void DrawButtons() + { + Ui.Spacing(); + string primaryLabel = "Export"; + EditorUiLayout style = Ui.GetLayout(); + float closeWidth = Ui.MeasureText("Close").X + style.framePadding.X * 2f; + float primaryWidth = Ui.MeasureText(primaryLabel).X + style.framePadding.X * 2f; + Ui.SetCursorPosX(Ui.GetCursorPosX() + MathF.Max(0f, Ui.GetContentRegionAvailable().X - closeWidth - style.itemSpacing.X - primaryWidth)); + if (Ui.Button("Close")) + window.CloseGame(); + Ui.SameLine(0f, -1f); + if (Ui.Button(primaryLabel)) + window.BeginGameExport(); + } + + private static void DrawText( + string label, + string id, + string value, + Action apply + ) { + BeginField(label); + Ui.SetNextItemWidth(-1f); + _ = Ui.InputText($"##{id}", ref value, C_TEXT_CAPACITY); + apply(value); + } +} diff --git a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportProgressModal.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportProgressModal.cs similarity index 70% rename from src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportProgressModal.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportProgressModal.cs index c5c640339..2ad8270f7 100644 --- a/src/composition/editor/features/Inno.Editor.Exporting/Presentation/GameExportProgressModal.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/GameExportProgressModal.cs @@ -1,10 +1,8 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Numerics; - using Inno.Editor.Core; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; - -namespace Inno.Editor.Exporting; +namespace Inno.Editor.Workbench; [EditorModal("export.game.progress", "Exporting Game", order: 311)] internal sealed class GameExportProgressModal(ExportWindowModule window) : EditorModal { @@ -12,7 +10,6 @@ internal sealed class GameExportProgressModal(ExportWindowModule window) : Edito /// Gets whether the export progress overlay should be displayed. /// public override bool isVisible => window.isGameProgressVisible; - /// /// Gets whether export progress prevents interaction with the Editor beneath it. /// @@ -27,13 +24,9 @@ internal sealed class GameExportProgressModal(ExportWindowModule window) : Edito protected override void OnDraw(EditorContext context) { string message = string.IsNullOrEmpty(window.error) ? window.status : window.error; - EditorWidget.WrappedText(message); - EditorWidget.CenteredProgressBar( - window.gameProgress, - new Vector2(-1f, 0f), - $"{window.gameProgress:P0}"); - if (!window.isGameBusy || - !EditorWidget.CenteredButton("Cancel", EditorWidget.style.itemSpacing.Y)) + EditorUI.current.widgets.WrappedText(message); + EditorUI.current.widgets.CenteredProgressBar(window.gameProgress, new Vector2(-1f, 0f), $"{window.gameProgress:P0}"); + if (!window.isGameBusy || !EditorUI.current.widgets.CenteredButton("Cancel", EditorUI.current.widgets.style.itemSpacing.Y)) return; window.CancelGameExport(); } diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/PluginExportModal.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/PluginExportModal.cs new file mode 100644 index 000000000..2c5e8b59b --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Exporting/PluginExportModal.cs @@ -0,0 +1,115 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using System; +using System.Numerics; +using Inno.Editor.Core; +using Inno.Editor.ImGui.ImGuiWidget; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Workbench; +[EditorModal("export.plugin", "Export as Plugin", order: 300)] +internal sealed class PluginExportModal(ExportWindowModule window) : EditorModal +{ + private const int C_TEXT_CAPACITY = 4096; + /// + /// Gets whether this implementation is visible. + /// + public override bool isVisible => window.isPluginVisible; + /// + /// Gets whether this implementation can move. + /// + public override bool canMove => true; + /// + /// Gets the preferred initial window size in logical editor units. + /// + public override Vector2 initialSize => new(680f, 0f); + + /// + /// Draws this feature using the current editor presentation context. + /// + /// + /// The context that supplies state and services for this operation. + /// + protected override void OnDraw(EditorContext context) + { + EditorUI.current.widgets.WrappedText("Packages the current project's complete authored source. Plugin.inno, dependency declarations, " + "settings contributions, and deterministic metadata are generated automatically."); + Ui.Spacing(); + Ui.BeginDisabled(window.isPluginBusy); + try + { + Ui.Text("Plugin ID"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, $"{window.pluginId} (from Project ID)"); + DrawText("Display Name", "plugin_name", window.pluginDisplayName, value => window.pluginDisplayName = value); + DrawText("Destination IPlugin", "plugin_output", window.pluginOutputPath, value => window.pluginOutputPath = value); + bool includeDependencies = window.includePluginDependencies; + if (Ui.Checkbox("Include dependency Plugin packages", ref includeDependencies)) + window.includePluginDependencies = includeDependencies; + } + finally + { + Ui.EndDisabled(); + } + + EditorUI.current.widgets.ColoredText( + Inno.Editor.ImGui.EditorPalette.textDisabled, + "Identity comes from Settings > Project > Identity. Other defaults come from Build > Plugin." + ); + DrawOutcome(); + DrawButtons(); + } + + private void DrawOutcome() + { + if (!string.IsNullOrEmpty(window.error)) + { + Ui.Spacing(); + EditorUI.current.widgets.WrappedText(window.error); + } + else if (!string.IsNullOrEmpty(window.status)) + { + Ui.Spacing(); + EditorUI.current.widgets.WrappedText(window.status); + } + } + + private void DrawButtons() + { + Ui.Spacing(); + EditorUiLayout style = Ui.GetLayout(); + float closeWidth = Ui.MeasureText("Close").X + style.framePadding.X * 2f; + string primaryLabel = window.isPluginBusy ? "Cancel" : "Export"; + float exportWidth = Ui.MeasureText(primaryLabel).X + style.framePadding.X * 2f; + Ui.SetCursorPosX(Ui.GetCursorPosX() + MathF.Max(0f, Ui.GetContentRegionAvailable().X - closeWidth - style.itemSpacing.X - exportWidth)); + Ui.BeginDisabled(window.isPluginBusy); + try + { + if (Ui.Button("Close")) + window.ClosePlugin(); + } + finally + { + Ui.EndDisabled(); + } + + Ui.SameLine(0f, -1f); + if (Ui.Button(primaryLabel)) + { + if (window.isPluginBusy) + window.CancelPluginExport(); + else + window.BeginPluginExport(); + } + } + + private static void DrawText( + string label, + string id, + string value, + Action apply + ) { + Ui.Text(label); + Ui.SetNextItemWidth(-1f); + _ = Ui.InputText($"##{id}", ref value, C_TEXT_CAPACITY); + apply(value); + } +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/GlobalInteractionIds.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/GlobalInteractionIds.cs similarity index 69% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/GlobalInteractionIds.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/GlobalInteractionIds.cs index c4e0d8327..2e2ba8575 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/GlobalInteractionIds.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/GlobalInteractionIds.cs @@ -1,8 +1,10 @@ -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; internal static class GlobalInteractionIds { internal const string C_MAIN_MENU_AREA = "editor/main-menu"; + internal const string C_FIND_COMMAND = "editor/find-command"; + internal const string C_COMMAND_SEARCH_MODULE = "editor.command-search"; + internal const string C_COMMAND_SEARCH_MODAL = "editor.command-search.dialog"; internal const string C_CLEAR_SELECTION = "editor/clear-selection"; internal const string C_REDO = "editor/redo"; internal const string C_SELECT = "editor/select"; diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Inno.Editor.Workbench.csproj b/src/composition/editor/presentation/Inno.Editor.Workbench/Inno.Editor.Workbench.csproj new file mode 100644 index 000000000..d52a5c701 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Inno.Editor.Workbench.csproj @@ -0,0 +1,28 @@ + + + net10.0 + 14.0 + disable + enable + true + $(WarningsAsErrors);CS1572;CS1573;CS1591 + Editor + + + + + + + + + + + + + + + + + + + diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Menus/WorkbenchMenuSource.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Menus/WorkbenchMenuSource.cs new file mode 100644 index 000000000..adad5c98e --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Menus/WorkbenchMenuSource.cs @@ -0,0 +1,26 @@ +using Inno.Editor.Core; +using Inno.Editor.Interactions; + +namespace Inno.Editor.Workbench; +[EditorMenuSource(GlobalInteractionIds.C_MAIN_MENU_AREA)] +internal sealed class WorkbenchMenuSource : EditorMenuSource +{ + /// + public override void Build( + EditorMenuContext context, + EditorMenuBuilder builder + ) { + builder.AddGroup("File", order: 0); + builder.AddGroup("Edit", order: 100); + builder.AddGroup("Create", order: 200); + builder.AddGroup("View", order: 300); + builder.AddGroup("Window", order: 400); + builder.AddGroup("Tools", order: 500); + builder.AddGroup("Help", order: 600); + foreach (EditorPanelAttribute panel in context.interactions.GetPanelDeclarations()) + { + string path = string.IsNullOrEmpty(panel.menuPath) ? "Window/" + panel.title : "Window/" + panel.menuPath + "/" + panel.title; + builder.Add(path, GlobalInteractionIds.C_TOGGLE_PANEL, panel.order, panel.separatorBefore, panel.id); + } + } +} diff --git a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationModal.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Scripting/ScriptCompilationModal.cs similarity index 68% rename from src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationModal.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Scripting/ScriptCompilationModal.cs index 938c3182e..300be3c26 100644 --- a/src/composition/editor/features/Inno.Editor.Scripting/Runtime/ScriptCompilationModal.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Scripting/ScriptCompilationModal.cs @@ -1,10 +1,9 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using Inno.Editor.Scripting; using System.Numerics; - using Inno.Editor.Core; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; - -namespace Inno.Editor.Scripting; +namespace Inno.Editor.Workbench; /// /// Displays blocking script compiler and reload progress with cancellation controls. /// @@ -12,13 +11,12 @@ namespace Inno.Editor.Scripting; /// The scripting used to initialize this instance. /// [EditorModal("scripting.compilation", "Compiling Scripts", order: 100)] -internal sealed class ScriptCompilationModal(EditorScripting scripting) : EditorModal +internal sealed class ScriptCompilationModal(IEditorScriptCompilation scripting) : EditorModal { /// /// Gets whether this implementation is visible. /// public override bool isVisible => scripting.requiresExclusiveProgress; - /// /// Gets whether blocks interaction is enabled for this implementation. /// @@ -33,9 +31,9 @@ internal sealed class ScriptCompilationModal(EditorScripting scripting) : Editor protected override void OnDraw(EditorContext context) { float progress = scripting.progress; - EditorWidget.WrappedText(scripting.status); - EditorWidget.CenteredProgressBar(progress, new Vector2(-1f, 0f), $"{progress:P0}"); - if (EditorWidget.CenteredButton("Cancel", EditorWidget.style.itemSpacing.Y)) + EditorUI.current.widgets.WrappedText(scripting.status); + EditorUI.current.widgets.CenteredProgressBar(progress, new Vector2(-1f, 0f), $"{progress:P0}"); + if (EditorUI.current.widgets.CenteredButton("Cancel", EditorUI.current.widgets.style.itemSpacing.Y)) scripting.CancelCompilation(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/DensitySetting.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/DensitySetting.cs similarity index 78% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/DensitySetting.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Settings/DensitySetting.cs index 3f4e8f807..b9eac6bc9 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/DensitySetting.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/DensitySetting.cs @@ -1,9 +1,9 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Settings; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Panel.Global; +using Ui = Inno.Editor.ImGui.ImGui; +namespace Inno.Editor.Workbench; [EditorSettingPath("Editor/Appearance/Density", order: 50)] internal sealed class DensitySetting : EditorSetting { @@ -11,12 +11,10 @@ internal sealed class DensitySetting : EditorSetting /// Gets the comfortable default density. /// public override EditorSettingObject defaultValue => CreateDefault(); - /// /// Gets the appearance section containing this setting. /// public override string section => "Layout"; - /// /// Gets the user-facing density description. /// @@ -32,19 +30,19 @@ protected override void OnDraw(EditorSettingObject setting) { bool compact = setting.GetAsBoolean("compact", false); string preview = compact ? "Compact" : "Comfortable"; - NativeImGui.SetNextItemWidth(-1f); - if (!ImGuiWidget.BeginBoundedCombo("##editor_density", preview)) + Ui.SetNextItemWidth(-1f); + if (!EditorUI.current.widgets.BeginBoundedCombo("##editor_density", preview)) return; try { - if (NativeImGui.Selectable("Comfortable", !compact)) + if (Ui.Selectable("Comfortable", !compact)) setting.SetAsBoolean("compact", false); - if (NativeImGui.Selectable("Compact", compact)) + if (Ui.Selectable("Compact", compact)) setting.SetAsBoolean("compact", true); } finally { - ImGuiWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/FramePacingSettings.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/FramePacingSettings.cs similarity index 85% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/FramePacingSettings.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Settings/FramePacingSettings.cs index 19675d070..57b0598db 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/FramePacingSettings.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/FramePacingSettings.cs @@ -1,12 +1,11 @@ -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; using Inno.Editor.Core; using Inno.Editor.Settings; using Inno.Platform; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Panel.Global; +using Ui = Inno.Editor.ImGui.ImGui; +namespace Inno.Editor.Workbench; [EditorSettingPath("Editor/Rendering")] internal sealed class EditorRenderingSettingsPage : EditorSetting { @@ -31,6 +30,7 @@ public override EditorSettingObject defaultValue return result; } } + /// /// Gets the presentation section that groups this setting. /// @@ -39,6 +39,7 @@ public override EditorSettingObject defaultValue /// Gets the user-facing explanation of this feature or setting. /// public override string description => "Synchronize presentation to the display refresh rate. Disable to allow higher frame rates."; + /// /// Draws this feature using the current editor presentation context. /// @@ -48,7 +49,7 @@ public override EditorSettingObject defaultValue protected override void OnDraw(EditorSettingObject setting) { bool enabled = setting.GetAsBoolean("enabled", false); - if (NativeImGui.Checkbox("##vsync", ref enabled)) + if (Ui.Checkbox("##vsync", ref enabled)) setting.SetAsBoolean("enabled", enabled); } } @@ -68,6 +69,7 @@ public override EditorSettingObject defaultValue return result; } } + /// /// Gets the presentation section that groups this setting. /// @@ -76,6 +78,7 @@ public override EditorSettingObject defaultValue /// Gets the user-facing explanation of this feature or setting. /// public override string description => "Software frame-rate limit. Zero means Unlimited. Fixed-step simulation is unaffected."; + /// /// Draws this feature using the current editor presentation context. /// @@ -85,10 +88,10 @@ public override EditorSettingObject defaultValue protected override void OnDraw(EditorSettingObject setting) { int rate = setting.GetAsInt32("value", 0); - if (NativeImGui.InputInt("##maximum_fps", ref rate, 1, 100, 0)) + if (Ui.InputInt("##maximum_fps", ref rate, 1, 100, 0)) setting.SetAsInt32("value", Math.Clamp(rate, 0, 1000)); if (rate == 0) - EditorWidget.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Unlimited"); + EditorUI.current.widgets.ColoredText(Inno.Editor.ImGui.EditorPalette.textDisabled, "Unlimited"); } } @@ -98,6 +101,7 @@ internal sealed class EditorFramePacingModule( FramePacingOptions pacing ) : EditorModule { + private IDisposable? m_settingsSubscription; /// /// Initializes this feature when its owning runtime becomes active. /// @@ -107,20 +111,19 @@ FramePacingOptions pacing protected override void OnStart(EditorContext context) { Apply(settings); - settings.changed += Apply; + m_settingsSubscription = settings.ListenChanges(Apply); } + /// /// Stops this feature before its owning runtime releases the active generation. /// /// /// The context that supplies state and services for this operation. /// - protected override void OnStop(EditorContext context) => settings.changed -= Apply; - + protected override void OnStop(EditorContext context) => m_settingsSubscription?.Dispose(); private void Apply(EditorSettings current) { pacing.verticalSync = current.Get("Editor/Rendering/Vertical Sync").GetAsBoolean("enabled", false); - pacing.maximumFrameRate = Math.Clamp( - current.Get("Editor/Rendering/Maximum Frame Rate").GetAsInt32("value", 0), 0, 1000); + pacing.maximumFrameRate = Math.Clamp(current.Get("Editor/Rendering/Maximum Frame Rate").GetAsInt32("value", 0), 0, 1000); } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/GlobalSettingsPages.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/GlobalSettingsPages.cs similarity index 96% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/GlobalSettingsPages.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Settings/GlobalSettingsPages.cs index 353a3395e..401cdb0f1 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Settings/GlobalSettingsPages.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/GlobalSettingsPages.cs @@ -1,7 +1,6 @@ using Inno.Editor.Settings; -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; [EditorSettingPath("Editor")] internal sealed class GlobalSettingsPage : EditorSetting { diff --git a/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/IconSettings.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/IconSettings.cs new file mode 100644 index 000000000..e9d68e178 --- /dev/null +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Settings/IconSettings.cs @@ -0,0 +1,217 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Inno.Editor.ImGui.ImGuiWidget; +using Inno.Editor.Settings; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.Workbench; +internal abstract class IconSetting : EditorSetting +{ + private static readonly IReadOnlyList<(string Name, string Glyph)> C_ICONS = EditorIconCatalog.standard; + private readonly string m_glyph; + /// + /// Creates a validated icon setting instance. + /// + /// + /// The glyph text validated by the icon setting operation. + /// + protected IconSetting(string glyph) + { + ArgumentException.ThrowIfNullOrWhiteSpace(glyph); + m_glyph = glyph; + } + + /// + /// Gets a new value initialized to this setting's canonical default state. + /// + public override EditorSettingObject defaultValue => CreateDefault(m_glyph); + /// + /// Gets the presentation section that groups this setting. + /// + public override string section => "Editor Icons"; + + /// + /// Renders this feature using the current editor presentation context. + /// + /// + /// The mutable editor setting value currently being presented. + /// + protected override void OnDraw(EditorSettingObject setting) + { + string value = setting.GetAsString("value", m_glyph) ?? m_glyph; + float iconSlotWidth = GetIconSlotWidth(); + Ui.SetNextItemWidth(-1f); + bool isOpen = EditorUI.current.widgets.BeginBoundedCombo("##icon", value + " " + GetIconName(value)); + if (isOpen) + { + try + { + for (int i = 0; i < C_ICONS.Count; i++) + { + (string name, string candidate) = C_ICONS[i]; + Ui.PushId(name); + try + { + bool selected = string.Equals(candidate, value, StringComparison.Ordinal); + if (Ui.Selectable("##icon_option", selected, ImGuiSelectableFlags.None, new Vector2(0f, Ui.GetFrameHeight()))) + { + value = candidate; + setting.SetAsString("value", candidate); + } + + Vector2 minimum = Ui.GetItemMinimum(); + Vector2 maximum = Ui.GetItemMaximum(); + EditorUiLayout style = Ui.GetLayout(); + float lineHeight = Ui.GetTextLineHeight(); + DrawIconTextAt( + Ui.GetDrawList(), + new Vector2(minimum.X + style.framePadding.X, minimum.Y + MathF.Max(0f, (maximum.Y - minimum.Y - lineHeight) * 0.5f)), + candidate, + name, + iconSlotWidth + ); + if (selected) + Ui.SetItemDefaultFocus(); + } + finally + { + Ui.PopId(); + } + } + } + finally + { + EditorUI.current.widgets.EndBoundedCombo(); + } + } + } + + private static EditorSettingObject CreateDefault(string glyph) + { + ArgumentException.ThrowIfNullOrWhiteSpace(glyph); + var result = new EditorSettingObject(); + result.SetAsString("value", glyph); + return result; + } + + private static float GetIconSlotWidth() => EditorUI.current.widgets.GetIconCatalogWidth(); + private static string GetIconName(string glyph) + { + for (int i = 0; i < C_ICONS.Count; i++) + { + if (string.Equals(C_ICONS[i].Glyph, glyph, StringComparison.Ordinal)) + return C_ICONS[i].Name; + } + + return glyph; + } + + private static void DrawIconTextAt( + EditorDrawList drawList, + Vector2 origin, + string glyph, + string text, + float iconSlotWidth + ) { + EditorUiLayout style = Ui.GetLayout(); + float fontSize = Ui.GetFontSize(); + float lineHeight = Ui.GetTextLineHeight(); + uint color = Ui.GetStyleColor(ImGuiCol.Text); + EditorUI.current.widgets.AddGlyphCentered(fontSize, glyph, new Vector2(origin.X + iconSlotWidth * 0.5f, origin.Y + lineHeight * 0.5f), color); + Inno.Editor.ImGui.ImGui.DrawText(new Vector2(origin.X + iconSlotWidth + style.itemInnerSpacing.X, origin.Y), color, text); + } +} + +[EditorSettingPath("Editor/Appearance/Icons/Scene")] +internal sealed class SceneIconSetting() : IconSetting(ImGuiIcon.Cubes) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a scene document or loaded scene."; +} + +[EditorSettingPath("Editor/Appearance/Icons/GameObject")] +internal sealed class GameObjectIconSetting() : IconSetting(ImGuiIcon.Cube) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a scene GameObject."; +} + +[EditorSettingPath("Editor/Appearance/Icons/Prefab")] +internal sealed class PrefabIconSetting() : IconSetting(ImGuiIcon.DiceD6) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a reusable prefab object."; +} + +[EditorSettingPath("Editor/Appearance/Icons/Layers")] +internal sealed class LayersIconSetting() : IconSetting(ImGuiIcon.LayerGroup) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a project layer configuration."; +} + +[EditorSettingPath("Editor/Appearance/Icons/Folder")] +internal sealed class FolderIconSetting() : IconSetting(ImGuiIcon.Folder) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a source directory."; +} + +[EditorSettingPath("Editor/Appearance/Icons/File")] +internal sealed class FileIconSetting() : IconSetting(ImGuiIcon.File) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used wherever the editor presents a generic source file."; +} + +[EditorSettingPath("Editor/Appearance/Icons/Shader")] +internal sealed class ShaderIconSetting() : IconSetting(ImGuiIcon.Shader) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used consistently for Shader graph assets and their Inspector headers."; +} + +[EditorSettingPath("Editor/Appearance/Icons/ShaderSource")] +internal sealed class ShaderSourceIconSetting() : IconSetting(ImGuiIcon.RupeeSign) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used consistently for Shader source-function libraries. The default function symbol distinguishes reusable code from complete Shader graphs."; +} + +[EditorSettingPath("Editor/Appearance/Icons/Material")] +internal sealed class MaterialIconSetting() : IconSetting(ImGuiIcon.Palette) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used consistently for Material assets and their Inspector headers."; +} + +[EditorSettingPath("Editor/Appearance/Icons/RenderPipeline")] +internal sealed class RenderPipelineIconSetting() : IconSetting(ImGuiIcon.RupiahSign) +{ + /// + /// Gets the user-facing explanation of this feature or setting. + /// + public override string description => "Selects the icon used consistently for Render Pipeline assets and their Inspector headers."; +} diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ActualSizeSetting.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ActualSizeSetting.cs similarity index 77% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ActualSizeSetting.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ActualSizeSetting.cs index 823ed43fd..7b46f0185 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ActualSizeSetting.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ActualSizeSetting.cs @@ -1,36 +1,23 @@ +using Inno.Editor.ImGui; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System; - using Inno.Editor.ImGui.ImGuiWidget; using Inno.Editor.Settings; -using Inno.Native.ImGui; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; -using NativeImGui = Inno.Native.ImGui.ImGui; - -namespace Inno.Editor.Panel.Global; +using Ui = Inno.Editor.ImGui.ImGui; +namespace Inno.Editor.Workbench; [EditorSettingPath("Editor/Appearance/Accessibility/Actual Size")] internal sealed class ActualSizeSetting : EditorSetting { - private static readonly (float Value, string Label)[] C_CHOICES = - [ - (0.75f, "75%"), - (0.9f, "90%"), - (1f, "100%"), - (1.1f, "110%"), - (1.25f, "125%"), - (1.5f, "150%") - ]; - + private static readonly (float Value, string Label)[] C_CHOICES = [(0.75f, "75%"), (0.9f, "90%"), (1f, "100%"), (1.1f, "110%"), (1.25f, "125%"), (1.5f, "150%")]; /// /// Gets a new value initialized to this setting's canonical default state. /// public override EditorSettingObject defaultValue => CreateDefault(); - /// /// Gets the presentation section that groups this setting. /// public override string section => "Font"; - /// /// Gets the user-facing explanation of this feature or setting. /// @@ -46,21 +33,21 @@ protected override void OnDraw(EditorSettingObject setting) { float value = setting.GetAsSingle("value", 1f); string preview = GetLabel(value); - NativeImGui.SetNextItemWidth(-1f); - if (!EditorWidget.BeginBoundedCombo("##actual_size", preview)) + Ui.SetNextItemWidth(-1f); + if (!EditorUI.current.widgets.BeginBoundedCombo("##actual_size", preview)) return; try { for (int i = 0; i < C_CHOICES.Length; i++) { (float candidate, string label) = C_CHOICES[i]; - if (NativeImGui.Selectable(label, MathF.Abs(candidate - value) < 0.0001f)) + if (Ui.Selectable(label, MathF.Abs(candidate - value) < 0.0001f)) setting.SetAsSingle("value", candidate); } } finally { - EditorWidget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } } @@ -78,6 +65,7 @@ private static string GetLabel(float value) if (MathF.Abs(C_CHOICES[i].Value - value) < 0.0001f) return C_CHOICES[i].Label; } + return $"{value * 100f:0}%"; } } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/EditorZoomModule.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/EditorZoomModule.cs similarity index 63% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/EditorZoomModule.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/EditorZoomModule.cs index 8daf7affd..92f399fc5 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/EditorZoomModule.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/EditorZoomModule.cs @@ -1,41 +1,37 @@ using System; - using Inno.Editor.Core; using Inno.Editor.ImGui; using Inno.Editor.Settings; -using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; - -namespace Inno.Editor.Panel.Global; +namespace Inno.Editor.Workbench; /// /// Applies the configured actual UI size and preserves project-local zoom multiples around it. /// +/// +/// The host-owned presentation session receiving the selected scale. +/// /// /// The validated configuration that controls this operation. /// [EditorModule("editor-zoom", order: 10)] -internal sealed class EditorZoomModule(EditorSettings settings) : EditorModule +internal sealed class EditorZoomModule( + EditorSettings settings, + EditorUiSession ui +) : EditorModule { + private IDisposable? m_settingsSubscription; private const string C_ACTUAL_SIZE_PATH = "Editor/Appearance/Accessibility/Actual Size"; private const string C_DENSITY_PATH = "Editor/Appearance/Density"; - private float m_actualSize = 1f; private int m_zoomStep; - - internal float zoom => EditorWidget.style.zoom; - - internal bool canZoomIn => ResolveZoom(m_zoomStep + 1) > zoom + 0.0001f; - - internal bool canZoomOut => ResolveZoom(m_zoomStep - 1) < zoom - 0.0001f; - + internal float zoom => ui.widgets.style.zoom; + internal bool canZoomIn => ResolveZoom(m_zoomStep + 1) > ResolveZoom(m_zoomStep) + 0.0001f; + internal bool canZoomOut => ResolveZoom(m_zoomStep - 1) < ResolveZoom(m_zoomStep) - 0.0001f; internal bool isActualSize => m_zoomStep == 0; internal bool ZoomIn() => SetStep(m_zoomStep + 1); - internal bool ZoomOut() => SetStep(m_zoomStep - 1); - internal bool UseActualSize() => SetStep(0); - /// /// Initializes this feature when its owning runtime becomes active. /// @@ -45,7 +41,7 @@ internal sealed class EditorZoomModule(EditorSettings settings) : EditorModule protected override void OnStart(EditorContext context) { ApplyActualSize(settings); - settings.changed += ApplyActualSize; + m_settingsSubscription = settings.ListenChanges(ApplyActualSize); } /// @@ -56,7 +52,7 @@ protected override void OnStart(EditorContext context) /// protected override void OnStop(EditorContext context) { - settings.changed -= ApplyActualSize; + m_settingsSubscription?.Dispose(); } /// @@ -79,38 +75,30 @@ protected override void Capture(EditorState state) protected override void Restore(EditorState state) { m_zoomStep = Math.Clamp(state.Get("step", 0), -10, 10); - _ = EditorWidget.style.SetZoom(ResolveZoom(m_zoomStep)); + _ = ui.widgets.style.SetZoom(ResolveZoom(m_zoomStep)); } private void ApplyActualSize(EditorSettings changedSettings) { - m_actualSize = NormalizeActualSize( - changedSettings.Get(C_ACTUAL_SIZE_PATH).GetAsSingle("value", 1f)); - _ = EditorWidget.style.SetZoom(ResolveZoom(m_zoomStep)); - _ = EditorWidget.style.SetCompactMode( - changedSettings.Get(C_DENSITY_PATH).GetAsBoolean("compact", false)); + m_actualSize = NormalizeActualSize(changedSettings.Get(C_ACTUAL_SIZE_PATH).GetAsSingle("value", 1f)); + _ = ui.widgets.style.SetZoom(ResolveZoom(m_zoomStep)); + _ = ui.widgets.style.SetCompactMode(changedSettings.Get(C_DENSITY_PATH).GetAsBoolean("compact", false)); } private float ResolveZoom(int step) { float multiplier = 1f + step * EditorStyleMetrics.C_ZOOM_STEP; - return Math.Clamp( - m_actualSize * multiplier, - EditorStyleMetrics.C_MIN_ZOOM, - EditorStyleMetrics.C_MAX_ZOOM); + return Math.Clamp(m_actualSize * multiplier, EditorStyleMetrics.C_MIN_ZOOM, EditorStyleMetrics.C_MAX_ZOOM); } private bool SetStep(int step) { float resolved = ResolveZoom(step); - if (MathF.Abs(resolved - zoom) < 0.0001f) + if (MathF.Abs(resolved - ResolveZoom(m_zoomStep)) < 0.0001f) return false; m_zoomStep = step; - return EditorWidget.style.SetZoom(resolved); + return ui.widgets.style.SetZoom(resolved); } - private static float NormalizeActualSize(float value) - => float.IsFinite(value) - ? Math.Clamp(value, EditorStyleMetrics.C_MIN_ZOOM, EditorStyleMetrics.C_MAX_ZOOM) - : 1f; + private static float NormalizeActualSize(float value) => float.IsFinite(value) ? Math.Clamp(value, EditorStyleMetrics.C_MIN_ZOOM, EditorStyleMetrics.C_MAX_ZOOM) : 1f; } diff --git a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ZoomActions.cs b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ZoomActions.cs similarity index 89% rename from src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ZoomActions.cs rename to src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ZoomActions.cs index ac611353f..8b9e31b64 100644 --- a/src/composition/editor/panels/Inno.Editor.Panel.Global/Zoom/ZoomActions.cs +++ b/src/composition/editor/presentation/Inno.Editor.Workbench/Zoom/ZoomActions.cs @@ -1,8 +1,7 @@ using Inno.Core.Input; using Inno.Editor.Interactions; -namespace Inno.Editor.Panel.Global; - +namespace Inno.Editor.Workbench; /// /// Increases UI zoom by one multiple of the configured actual size. /// @@ -23,11 +22,7 @@ internal sealed class ZoomInEditorAction(EditorZoomModule zoom) : EditorAction /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => zoom.canZoomIn - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => zoom.canZoomIn ? EditorActionState.enabled : EditorActionState.disabled; /// /// Applies the editor action to the supplied interaction context. /// @@ -57,11 +52,7 @@ internal sealed class ZoomOutEditorAction(EditorZoomModule zoom) : EditorAction /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => zoom.canZoomOut - ? EditorActionState.enabled - : EditorActionState.disabled; - + protected override EditorActionState Query(EditorActionContext context) => zoom.canZoomOut ? EditorActionState.enabled : EditorActionState.disabled; /// /// Applies the editor action to the supplied interaction context. /// @@ -91,11 +82,7 @@ internal sealed class ActualSizeEditorAction(EditorZoomModule zoom) : EditorActi /// /// The validated editor action state that represents the completed operation. /// - protected override EditorActionState Query(EditorActionContext context) - => zoom.isActualSize - ? EditorActionState.disabled - : EditorActionState.enabled; - + protected override EditorActionState Query(EditorActionContext context) => zoom.isActualSize ? EditorActionState.disabled : EditorActionState.enabled; /// /// Applies the editor action to the supplied interaction context. /// diff --git a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs index 9c7e6d2f7..a1f51f645 100644 --- a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs +++ b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Dependencies.cs @@ -307,12 +307,16 @@ out bool metadataChanged string sourcePath = GetSourcePath(NormalizeRelativePath(dependency.key)); if (!AssetSourceFileStamp.TryCapture(sourcePath, out AssetSourceFileStamp sourceStamp)) return "MISSING"; - if (SourceStampMatches(dependency, sourceStamp)) + bool stampChanged = !SourceStampMatches(dependency, sourceStamp); + if (GetMount(dependency.key).isReadOnly && !stampChanged) return dependency.fingerprint; - byte[] sourceBytes = ReadStableSourceBytes(sourcePath, out sourceStamp); - ApplySourceStamp(ref dependency, sourceStamp); - metadataChanged = true; - return ComputeSha256Hex(sourceBytes); + string hash = ReadStableSourceHash(sourcePath, out sourceStamp); + if (stampChanged) + { + ApplySourceStamp(ref dependency, sourceStamp); + metadataChanged = true; + } + return hash; } case AssetImportDependencyKind.Artifact: return Guid.TryParse(dependency.key, out Guid id) && diff --git a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs index a710fc460..3e8bf4f1d 100644 --- a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs +++ b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.Loading.cs @@ -36,8 +36,13 @@ sealed partial class AssetLoader /// The required assignable asset type. /// /// - /// The canonical asset, or when unavailable or incompatible. + /// The canonical asset, including retained last-good content after a diagnosed import failure, + /// or when unavailable or incompatible. /// + /// + /// Writable source content is verified independently of timestamps. Temporarily blocked source + /// verification follows the existing failed-import protocol; it does not certify cached content as current. + /// public AssetObject? Load( AssetPath path, Type requestedAssetType @@ -256,7 +261,17 @@ Type requestedAssetType bool stale = false; bool catalogChanged = false; if (record is not null && sourceExists) - stale = IsStale(record, out catalogChanged); + { + try + { + stale = IsStale(record, out catalogChanged); + } + catch (Exception failure) when (failure is IOException or UnauthorizedAccessException) + { + // Import owns failure diagnostics and the retained last-good artifact. + stale = true; + } + } if (catalogChanged && !stale) CommitCatalogLocked(); if (record is null || stale) diff --git a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceIO.cs b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceIO.cs index 874376486..8d62ba4be 100644 --- a/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceIO.cs +++ b/src/content/assets/Inno.Assets.Pipeline/Importing/AssetLoader.SourceIO.cs @@ -66,18 +66,21 @@ out bool catalogChanged return true; if (!AssetSourceFileStamp.TryCapture(sourcePath, out AssetSourceFileStamp sourceStamp)) return true; - if (!SourceStampMatches(record.meta, sourceStamp)) + bool stampChanged = !SourceStampMatches(record.meta, sourceStamp); + if (!GetMount(record.relativePath).isReadOnly || stampChanged) { - byte[] sourceBytes = ReadStableSourceBytes(sourcePath, out sourceStamp); if (!string.Equals( record.meta.sourceHash, - ComputeSha256Hex(sourceBytes), + ReadStableSourceHash(sourcePath, out sourceStamp), StringComparison.Ordinal)) { return true; } - ApplySourceStamp(record.meta, sourceStamp); - catalogChanged = true; + if (stampChanged) + { + ApplySourceStamp(record.meta, sourceStamp); + catalogChanged = true; + } } for (int i = 0; i < record.meta.importDependencies.Length; i++) { @@ -309,6 +312,27 @@ private AssetSourceMount GetMount(AssetPath path) private string GetMetaPath(string relativePath) => GetSourcePath(relativePath) + C_META_POSTFIX; + private static string ReadStableSourceHash( + string sourcePath, + out AssetSourceFileStamp sourceStamp + ) { + const int C_MAX_ATTEMPTS = 3; + for (int attempt = 0; attempt < C_MAX_ATTEMPTS; attempt++) + { + if (!AssetSourceFileStamp.TryCapture(sourcePath, out AssetSourceFileStamp before)) + throw new FileNotFoundException("Asset source is unavailable.", sourcePath); + using FileStream source = IOFile.OpenRead(sourcePath); + string hash = Convert.ToHexString(SHA256.HashData(source)); + if (AssetSourceFileStamp.TryCapture(sourcePath, out AssetSourceFileStamp after) + && before == after && source.Length == after.length) + { + sourceStamp = after; + return hash; + } + } + throw new IOException($"Asset source '{sourcePath}' did not remain stable while its content was verified."); + } + private static byte[] ReadStableSourceBytes( string sourcePath, out AssetSourceFileStamp sourceStamp diff --git a/src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs b/src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs index f8bdc6c06..60a9ceca2 100644 --- a/src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs +++ b/src/content/assets/Inno.Assets.Pipeline/Sources/AssetFileSystem.cs @@ -31,6 +31,13 @@ public sealed class AssetFileSystem : IDisposable private bool m_identitiesActive; private bool m_disposed; + private readonly record struct SourceEntry( + AssetPath path, + Guid persistentId, + bool isReadOnly, + bool isDirectory + ); + /// /// Gets the absolute source asset root. /// @@ -183,18 +190,68 @@ public void Stop() } /// - /// Rebuilds the indexed source file snapshot. + /// Reconciles a complete source snapshot while preserving unchanged entry identities. /// + /// + /// The source filesystem has been disposed. + /// + /// + /// Source enumeration fails; the previous complete index and its identities remain active. + /// + /// + /// A source directory cannot be read; the previous complete index remains active. + /// + /// + /// Multiple source paths claim the same persistent entry identity. + /// + /// + /// A new entry identity cannot be registered; the previous index remains active. + /// public void Refresh() { ObjectDisposedException.ThrowIf(m_disposed, this); lock (m_sync) { - UnregisterEntries(); + List candidate = CollectEntries(); + AssetFileEntry[] previous = m_entries.All().ToArray(); + Dictionary existing = previous.ToDictionary(static entry => entry.identity.persistentId); + var prepared = new List<(SourceEntry source, AssetFileEntry entry)>(candidate.Count); + var registered = new List(); + try + { + foreach (SourceEntry source in candidate) + { + if (!existing.TryGetValue(source.persistentId, out AssetFileEntry? entry)) + { + entry = new AssetFileEntry(); + if (m_identitiesActive) + { + if (!m_identities.Register(entry, source.persistentId)) + throw new InvalidOperationException($"Asset source entry '{source.path}' could not acquire its identity."); + registered.Add(entry); + } + else + m_identities.InitializePersistentIdentity(entry, source.persistentId); + } + prepared.Add((source, entry)); + } + } + catch + { + foreach (AssetFileEntry entry in registered) + _ = m_identities.Unregister(entry); + throw; + } m_entries.RemoveAll(); - foreach (AssetSourceMount mount in m_mounts.Values.OrderBy(static value => value.id.value, StringComparer.Ordinal)) - IndexDirectoryRecursive(mount, mount.rootPath, string.Empty); + foreach ((SourceEntry source, AssetFileEntry entry) in prepared) + { + IndexEntry(entry, source); + existing.Remove(source.persistentId); + } + if (m_identitiesActive) + foreach (AssetFileEntry removed in existing.Values) + _ = m_identities.Unregister(removed); } } @@ -435,54 +492,56 @@ private void RefreshSafely() } } - private void IndexDirectoryRecursive( - AssetSourceMount mount, - string absoluteDirectoryPath, - string localDirectoryPath - ) { - string normalizedDirectory = NormalizeLocalPath(localDirectoryPath); - AddOrUpdateEntry(new AssetPath(mount.id, normalizedDirectory), mount.isReadOnly, isDirectory: true); - - foreach (string absoluteChildDirectory in Directory.EnumerateDirectories(absoluteDirectoryPath)) + private List CollectEntries() + { + var entries = new List(); + var identities = new HashSet(); + foreach (AssetSourceMount mount in m_mounts.Values.OrderBy(static value => value.id.value, StringComparer.Ordinal)) { - string name = Path.GetFileName(absoluteChildDirectory); - string childRelativePath = CombineLocalPath(normalizedDirectory, name); - if (m_sourcePolicy.IsIgnored(childRelativePath, isDirectory: true)) - continue; - IndexDirectoryRecursive(mount, absoluteChildDirectory, childRelativePath); + var pending = new Stack(); + pending.Push(string.Empty); + while (pending.TryPop(out string? directory)) + { + Add(new AssetPath(mount.id, directory), mount.isReadOnly, isDirectory: true); + string absoluteDirectory = mount.Resolve(directory); + foreach (string child in Directory.EnumerateDirectories(absoluteDirectory)) + { + string path = CombineLocalPath(directory, Path.GetFileName(child)); + if (!m_sourcePolicy.IsIgnored(path, isDirectory: true)) + pending.Push(path); + } + foreach (string child in Directory.EnumerateFiles(absoluteDirectory)) + { + string path = CombineLocalPath(directory, Path.GetFileName(child)); + if (!m_sourcePolicy.IsIgnored(path, isDirectory: false)) + Add(new AssetPath(mount.id, path), mount.isReadOnly, isDirectory: false); + } + } } - - foreach (string absoluteFile in Directory.EnumerateFiles(absoluteDirectoryPath)) - { - string name = Path.GetFileName(absoluteFile); - string fileRelativePath = CombineLocalPath(normalizedDirectory, name); - if (m_sourcePolicy.IsIgnored(fileRelativePath, isDirectory: false)) - continue; - AddOrUpdateEntry(new AssetPath(mount.id, fileRelativePath), mount.isReadOnly, isDirectory: false); + return entries; + + void Add( + AssetPath path, + bool isReadOnly, + bool isDirectory + ) { + Guid sourceId = m_persistentIdentityResolver?.Invoke(path) ?? CreateFallbackPersistentId(path.ToString()); + Guid entryId = CreateEntryPersistentId(sourceId); + if (!identities.Add(entryId)) + throw new InvalidDataException($"Asset source path '{path}' duplicates a persistent entry identity."); + entries.Add(new(path, entryId, isReadOnly, isDirectory)); } } - private void AddOrUpdateEntry( - AssetPath assetPath, - bool isReadOnly, - bool isDirectory + private void IndexEntry( + AssetFileEntry existing, + SourceEntry source ) { + AssetPath assetPath = source.path; + bool isDirectory = source.isDirectory; string path = assetPath.ToString(); - AssetFileEntry? existing = m_entries.First(m_pathKey, path); - if (existing is null) - { - existing = new AssetFileEntry(); - Guid sourcePersistentId = m_persistentIdentityResolver?.Invoke(assetPath) - ?? CreateFallbackPersistentId(path); - Guid entryPersistentId = CreateEntryPersistentId(sourcePersistentId); - if (m_identitiesActive) - _ = m_identities.Register(existing, entryPersistentId); - else - m_identities.InitializePersistentIdentity(existing, entryPersistentId); - } - existing.assetPath = assetPath; - existing.isReadOnly = isReadOnly; + existing.isReadOnly = source.isReadOnly; existing.isSample = isDirectory && AssetSample.IsRoot(assetPath); existing.isSampleContent = AssetSample.Contains(assetPath, isDirectory); existing.parentAssetPath = AssetPath.Parse(GetParentPath(path)); diff --git a/src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs b/src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs index c6f0e48e6..243eaabd5 100644 --- a/src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs +++ b/src/content/assets/Inno.Assets.Pipeline/Sources/Filtering/AssetSourcePolicy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Inno.Core.IO; namespace Inno.Assets.Pipeline; @@ -104,7 +105,7 @@ bool isDirectory return false; if (isDirectory && m_directoryNames.Contains(name)) return true; - if (!isDirectory && (m_fileNames.Contains(name) || IsGeneratedPath(name))) + if (!isDirectory && (m_fileNames.Contains(name) || IsGeneratedPath(name) || AtomicFile.IsCandidatePath(name))) return true; for (int i = 0; i < m_prefixes.Length; i++) { diff --git a/src/content/assets/Inno.Assets/Serialization/AssetDependencyCollection.cs b/src/content/assets/Inno.Assets/Serialization/AssetDependencyCollection.cs index 793587dbd..5931938c6 100644 --- a/src/content/assets/Inno.Assets/Serialization/AssetDependencyCollection.cs +++ b/src/content/assets/Inno.Assets/Serialization/AssetDependencyCollection.cs @@ -16,7 +16,7 @@ namespace Inno.Assets; /// public sealed class AssetDependencyCollection { - private readonly Dictionary m_dependencies = []; + private Dictionary? m_dependencies; /// /// Creates a dependency collector that preserves last-known source paths in serialized references. @@ -47,7 +47,9 @@ public AssetDependencyCollection(bool includeLastKnownPaths) /// Gets the collected dependencies in deterministic persistent-identity order. /// public IReadOnlyList dependencies - => m_dependencies.Values.OrderBy(static dependency => dependency.persistentId).ToArray(); + => m_dependencies is null || m_dependencies.Count == 0 + ? Array.Empty() + : m_dependencies.Values.OrderBy(static dependency => dependency.persistentId).ToArray(); internal void Add( AssetObject asset, @@ -64,7 +66,7 @@ TypeCatalog types $"Asset type '{asset.GetType().FullName}' requires a StableTypeId before it can be referenced persistently."); } - m_dependencies[persistentId] = new AssetDependency( + (m_dependencies ??= [])[persistentId] = new AssetDependency( persistentId, typeRef, includeLastKnownPaths ? asset.assetPath.ToString() : string.Empty); @@ -80,7 +82,7 @@ public void Add(AssetDependency dependency) { if (dependency.persistentId == Guid.Empty) throw new ArgumentException("A dependency requires a persistent asset identity.", nameof(dependency)); - m_dependencies[dependency.persistentId] = includeLastKnownPaths ? dependency + (m_dependencies ??= [])[dependency.persistentId] = includeLastKnownPaths ? dependency : new AssetDependency(dependency.persistentId, dependency.type, string.Empty); } } diff --git a/src/content/assets/Inno.Assets/Serialization/AssetObjectConverter.cs b/src/content/assets/Inno.Assets/Serialization/AssetObjectConverter.cs index f7b1615a8..d77cc4b77 100644 --- a/src/content/assets/Inno.Assets/Serialization/AssetObjectConverter.cs +++ b/src/content/assets/Inno.Assets/Serialization/AssetObjectConverter.cs @@ -31,7 +31,7 @@ AssetObject value Guid persistentId = value.identity.persistentId; if (persistentId == Guid.Empty) throw new InvalidOperationException($"Asset '{value.GetType().FullName}' has no persistent identity at '{writer.path}'."); - TypeCatalog types = writer.context.GetRequired(); + TypeCacheSnapshot types = writer.typeSnapshot; if (!types.TryGetTypeRef(value.GetType(), out TypeRef typeRef)) { throw new InvalidOperationException( @@ -40,13 +40,14 @@ AssetObject value bool hasDependencies = writer.context.TryGet(out AssetDependencyCollection? dependencies) && dependencies is not null; + string lastKnownPath = !hasDependencies || dependencies!.includeLastKnownPaths + ? value.assetPath.ToString() + : string.Empty; writer.Write(C_PERSISTENT_ID, persistentId); writer.Write(C_STABLE_TYPE_ID, typeRef.stableId); - writer.Write( - C_LAST_KNOWN_PATH, - !hasDependencies || dependencies!.includeLastKnownPaths ? value.assetPath.ToString() : string.Empty); + writer.Write(C_LAST_KNOWN_PATH, lastKnownPath); if (hasDependencies) - dependencies!.Add(value, types); + dependencies!.Add(new AssetDependency(persistentId, typeRef, lastKnownPath)); } /// diff --git a/src/content/scene/Inno.Scene/Concrete/Transform.cs b/src/content/scene/Inno.Scene/Concrete/Transform.cs index 035a15893..c5d6cad09 100644 --- a/src/content/scene/Inno.Scene/Concrete/Transform.cs +++ b/src/content/scene/Inno.Scene/Concrete/Transform.cs @@ -12,8 +12,10 @@ namespace Inno.Scene.Components; /// Stores local transform data and exposes the scene hierarchy relationship. /// [StableTypeId("39a944b8-f021-4ac9-9f66-506f15a96848")] +[SerializationRevision("m_serializationRevision")] public sealed class Transform : GameComponent { + private ulong? m_serializationRevision = 0; private readonly List m_children = []; private readonly ReadOnlyCollection m_readOnlyChildren; private Transform? m_parent; @@ -42,6 +44,7 @@ public Vector3 localPosition get => m_localPosition; set { + AdvanceCaptureRevision(); m_localPosition = value; RecomputeWorldFromLocal(); } @@ -56,6 +59,7 @@ public Quaternion localRotation get => m_localRotation; set { + AdvanceCaptureRevision(); m_localRotation = value.normalized; RecomputeWorldFromLocal(); } @@ -70,6 +74,7 @@ public Vector3 localScale get => m_localScale; set { + AdvanceCaptureRevision(); m_localScale = value; RecomputeWorldFromLocal(); } @@ -205,6 +210,7 @@ Vector3 scale /// protected override void Reset() { + AdvanceCaptureRevision(); m_localPosition = Vector3.ZERO; m_localRotation = Quaternion.identity; m_localScale = Vector3.ONE; @@ -247,6 +253,7 @@ internal void ApplyWorld( Quaternion rotation, Vector3 scale ) { + AdvanceCaptureRevision(); if (m_parent is null) { m_localPosition = position; @@ -297,6 +304,12 @@ internal void RecomputeWorldFromLocal(bool notifyChildren = true) NotifyChildrenWorldChanged(); } + private void AdvanceCaptureRevision() + { + if (m_serializationRevision is { } revision) + m_serializationRevision = revision == ulong.MaxValue ? null : revision + 1; + } + private void RecomputeMatrixFromLocal() { Matrix localMatrix = Matrix.CreateTranslation(m_localPosition) diff --git a/src/content/scene/Inno.Scene/Core/GameObject.cs b/src/content/scene/Inno.Scene/Core/GameObject.cs index 651a7076d..e3b9aed50 100644 --- a/src/content/scene/Inno.Scene/Core/GameObject.cs +++ b/src/content/scene/Inno.Scene/Core/GameObject.cs @@ -19,7 +19,9 @@ public sealed class GameObject : EngineObject, ISerializable /// public const string defaultTag = "Untagged"; + private ulong? m_serializationRevision = 0; private GameScene? m_scene; + private SceneObjectRecord? m_attachment; private Transform? m_transform; private PrefabInstanceInfo? m_prefabInstance; private PrefabConnectionRecord? m_prefabConnection; @@ -66,6 +68,7 @@ public string name string requestedName = value ?? string.Empty; if (string.Equals(m_name, requestedName, StringComparison.Ordinal)) return; + AdvanceCaptureRevision(); m_name = requestedName; owner.NotifyObjectMetadataChanged(this); } @@ -94,6 +97,7 @@ public string tag string requestedTag = NormalizeTag(value); if (string.Equals(m_tag, requestedTag, StringComparison.Ordinal)) return; + AdvanceCaptureRevision(); m_tag = requestedTag; owner.NotifyObjectMetadataChanged(this); } @@ -118,6 +122,7 @@ public GameLayer layer GameScene owner = EnsureAlive(); if (m_layer == value) return; + AdvanceCaptureRevision(); m_layer = value; owner.NotifyObjectMetadataChanged(this); } @@ -363,6 +368,7 @@ public bool RemoveComponent(GameComponent component) return EnsureAlive().RemoveComponent(this, component); } + internal ulong? captureRevision => m_serializationRevision; internal string storedName => m_name; internal string storedTag => m_tag; internal GameLayer storedLayer => m_layer; @@ -380,6 +386,7 @@ internal void SetNameDirect(string value) string requestedName = value ?? string.Empty; if (string.Equals(m_name, requestedName, StringComparison.Ordinal)) return; + AdvanceCaptureRevision(); m_name = requestedName; m_scene?.NotifyObjectMetadataChanged(this); } @@ -389,6 +396,7 @@ internal void SetTagDirect(string value) string requestedTag = NormalizeTag(value); if (string.Equals(m_tag, requestedTag, StringComparison.Ordinal)) return; + AdvanceCaptureRevision(); m_tag = requestedTag; m_scene?.NotifyObjectMetadataChanged(this); } @@ -397,11 +405,38 @@ internal void SetLayerDirect(GameLayer value) { if (m_layer == value) return; + AdvanceCaptureRevision(); m_layer = value; m_scene?.NotifyObjectMetadataChanged(this); } - internal void SetActiveSelfDirect(bool value) => m_activeSelf = value; + + internal void SetActiveSelfDirect(bool value) + { + AdvanceCaptureRevision(); + m_activeSelf = value; + } internal void SetActiveInHierarchyDirect(bool value) => m_activeInHierarchy = value; + + /// + /// Borrows the existing indexed attachment for this exact store, without creating another object authority. + /// + internal SceneObjectRecord? GetAttachment(SceneStore store) + => m_attachment is { } record && ReferenceEquals(record.store, store) ? record : null; + + internal void AttachRecord(SceneObjectRecord record) + { + if (m_attachment is not null) + throw new InvalidOperationException("The object already owns a scene-store attachment."); + m_attachment = record; + } + + internal void ReleaseRecord(SceneObjectRecord record) + { + if (!ReferenceEquals(m_attachment, record)) + throw new InvalidOperationException("The object no longer owns the retiring scene-store attachment."); + m_attachment = null; + } + internal void SetSceneDirect(GameScene scene) => m_scene = scene ?? throw new ArgumentNullException(nameof(scene)); internal void SetPrefabInstanceDirect(PrefabInstanceInfo? value) => m_prefabInstance = value; internal void SetPrefabConnectionDirect(PrefabConnectionRecord? value) => m_prefabConnection = value; @@ -439,4 +474,11 @@ internal static string NormalizeTag(string value) ArgumentException.ThrowIfNullOrWhiteSpace(value); return value.Trim(); } + private void AdvanceCaptureRevision() + { + if (m_serializationRevision is { } revision) + m_serializationRevision = revision == ulong.MaxValue ? null : revision + 1; + } + + } diff --git a/src/content/scene/Inno.Scene/Core/GameScene.cs b/src/content/scene/Inno.Scene/Core/GameScene.cs index d22cd05a0..3a3fc8cf1 100644 --- a/src/content/scene/Inno.Scene/Core/GameScene.cs +++ b/src/content/scene/Inno.Scene/Core/GameScene.cs @@ -69,6 +69,15 @@ public string name /// public bool isLoaded => m_isLoaded; + /// + /// Gets the owner-local change counter for object membership, parentage and sibling ordering. + /// + /// + /// Use this counter only to invalidate derived views of this live scene. It is not a persistent + /// identity, data format or guarantee that object properties remain unchanged. + /// + public ulong hierarchyRevision => m_hierarchy.revision; + internal SceneTypeCatalog typeCatalog => m_types; internal long structureRevision => m_store.structureRevision; diff --git a/src/content/scene/Inno.Scene/Serialization/Converters/GameComponentReferenceConverter.cs b/src/content/scene/Inno.Scene/Serialization/Converters/GameComponentReferenceConverter.cs index dfc881243..dfd05f4ae 100644 --- a/src/content/scene/Inno.Scene/Serialization/Converters/GameComponentReferenceConverter.cs +++ b/src/content/scene/Inno.Scene/Serialization/Converters/GameComponentReferenceConverter.cs @@ -24,7 +24,7 @@ GameComponent value ) { ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(value); - EngineReferenceToken token = SceneGraphReferenceMap.current.Capture(value, writer.path); + EngineReferenceToken token = SceneReferenceScope.current.Capture(value, writer.path); writer.Write("kind", (int)token.kind); writer.Write("sourceId", token.sourceId); } @@ -46,6 +46,6 @@ public override GameComponent Read(SerializationReader reader) reader.Read("sourceId")); if (token.kind != EngineReferenceKind.GameComponent) throw new InvalidDataException($"Reference token at '{reader.path}' is not a GameComponent reference."); - return (GameComponent)SceneGraphReferenceMap.current.Resolve(token, reader.valueType, reader.path); + return (GameComponent)SceneReferenceScope.current.Resolve(token, reader.valueType, reader.path); } } diff --git a/src/content/scene/Inno.Scene/Serialization/Converters/GameObjectConverter.cs b/src/content/scene/Inno.Scene/Serialization/Converters/GameObjectConverter.cs index a7d3cd8a0..3cf6d2ffd 100644 --- a/src/content/scene/Inno.Scene/Serialization/Converters/GameObjectConverter.cs +++ b/src/content/scene/Inno.Scene/Serialization/Converters/GameObjectConverter.cs @@ -30,7 +30,7 @@ GameObject value ) { ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(value); - if (SceneGraphReferenceMap.TryGetCurrent(out SceneGraphReferenceMap? activeReferences)) + if (SceneReferenceScope.TryGetCurrent(out SceneReferenceScope? activeReferences)) { WriteReference(writer, activeReferences!, value); return; @@ -68,7 +68,7 @@ GameObject value : Guid.NewGuid()); } - var references = new SceneGraphReferenceMap(value.scene, engineObjects, sourceIds, value); + var references = new SceneGraphReferenceMap(value.scene, sourceIds, value); writer.Write(C_SOURCE_ROOT_ID_KEY, sourceIds[value]); using (references.Enter()) { @@ -76,6 +76,7 @@ GameObject value writer, entries, sourceIds, + included.Contains, preserveRootSiblingOrder: false); } } @@ -92,7 +93,7 @@ GameObject value public override GameObject Read(SerializationReader reader) { ArgumentNullException.ThrowIfNull(reader); - if (SceneGraphReferenceMap.TryGetCurrent(out SceneGraphReferenceMap? activeReferences)) + if (SceneReferenceScope.TryGetCurrent(out SceneReferenceScope? activeReferences)) return (GameObject)ReadReference(reader, activeReferences!); GameScene scene = reader.context.GetRequired(); @@ -198,7 +199,7 @@ public override GameObject Read(SerializationReader reader) private static void WriteReference( SerializationWriter writer, - SceneGraphReferenceMap references, + SceneReferenceScope references, GameObject gameObject ) { EngineReferenceToken token = references.Capture(gameObject, writer.path); @@ -208,7 +209,7 @@ GameObject gameObject private static EngineObject ReadReference( SerializationReader reader, - SceneGraphReferenceMap references + SceneReferenceScope references ) { var token = new EngineReferenceToken( (EngineReferenceKind)reader.Read("kind"), diff --git a/src/content/scene/Inno.Scene/Serialization/Converters/GameSceneConverter.cs b/src/content/scene/Inno.Scene/Serialization/Converters/GameSceneConverter.cs index 6b0981faa..89c5378a2 100644 --- a/src/content/scene/Inno.Scene/Serialization/Converters/GameSceneConverter.cs +++ b/src/content/scene/Inno.Scene/Serialization/Converters/GameSceneConverter.cs @@ -31,14 +31,8 @@ GameScene value SceneStructureSnapshot snapshot = value.CaptureStructure(); IReadOnlyList systems = value.GetSystems(); - EngineObject[] engineObjects = snapshot.objects - .SelectMany(static entry => entry.components.Cast().Prepend(entry.gameObject)) - .Concat(systems) - .ToArray(); - var sourceIds = new Dictionary(ReferenceEqualityComparer.Instance); - for (int i = 0; i < engineObjects.Length; i++) - sourceIds.Add(engineObjects[i], engineObjects[i].identity.persistentId); - var references = new SceneGraphReferenceMap(value, engineObjects, sourceIds); + var sourceIds = new SceneGraphSourceIds(snapshot.CaptureObjectSourceIds(), systems); + var references = new SceneGraphReferenceMap(value, sourceIds); writer.Write(SceneGraphSerialization.C_SCENE_ID_KEY, value.identity.persistentId); writer.Write(SceneGraphSerialization.C_NAME_KEY, value.name); @@ -48,6 +42,7 @@ GameScene value writer, snapshot.objects, sourceIds, + gameObject => sourceIds.ContainsKey(gameObject), preserveRootSiblingOrder: true); SceneGraphSerialization.WriteSystems(writer, systems, sourceIds); } @@ -88,6 +83,8 @@ public override GameScene Read(SerializationReader reader) } } + + /// /// Restores the supplied snapshot while preserving current invariants. /// @@ -162,4 +159,5 @@ bool preservePersistentIds serialization.RestorePropertiesData(system, state, context: reader.context); } } + } diff --git a/src/content/scene/Inno.Scene/Serialization/Converters/GameSystemReferenceConverter.cs b/src/content/scene/Inno.Scene/Serialization/Converters/GameSystemReferenceConverter.cs index 990e812dc..6b99c30ac 100644 --- a/src/content/scene/Inno.Scene/Serialization/Converters/GameSystemReferenceConverter.cs +++ b/src/content/scene/Inno.Scene/Serialization/Converters/GameSystemReferenceConverter.cs @@ -24,7 +24,7 @@ GameSystem value ) { ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(value); - EngineReferenceToken token = SceneGraphReferenceMap.current.Capture(value, writer.path); + EngineReferenceToken token = SceneReferenceScope.current.Capture(value, writer.path); writer.Write("kind", (int)token.kind); writer.Write("sourceId", token.sourceId); } @@ -46,6 +46,6 @@ public override GameSystem Read(SerializationReader reader) reader.Read("sourceId")); if (token.kind != EngineReferenceKind.GameSystem) throw new InvalidDataException($"Reference token at '{reader.path}' is not a GameSystem reference."); - return (GameSystem)SceneGraphReferenceMap.current.Resolve(token, reader.valueType, reader.path); + return (GameSystem)SceneReferenceScope.current.Resolve(token, reader.valueType, reader.path); } } diff --git a/src/content/scene/Inno.Scene/Serialization/PrefabOverrideProcessor.cs b/src/content/scene/Inno.Scene/Serialization/PrefabOverrideProcessor.cs index d65ac10a8..e1f528530 100644 --- a/src/content/scene/Inno.Scene/Serialization/PrefabOverrideProcessor.cs +++ b/src/content/scene/Inno.Scene/Serialization/PrefabOverrideProcessor.cs @@ -16,7 +16,7 @@ internal static PrefabOverrideSet Capture( PrefabConnectionRecord connection, GameScene scene, SerializationContext context, - SceneGraphReferenceMap storageReferences + SceneReferenceScope storageReferences ) { ArgumentNullException.ThrowIfNull(connection); ArgumentNullException.ThrowIfNull(scene); @@ -150,7 +150,7 @@ private static void CaptureComponentOverrides( SerializationContext context, SceneGraphReferenceMap currentReferences, SceneGraphReferenceMap sourceReferences, - SceneGraphReferenceMap storageReferences, + SceneReferenceScope storageReferences, PrefabOverrideSet overrides ) { foreach ((Guid sourceId, Guid sourceRuntimeId) in sourceConnection.componentIdentities) @@ -559,7 +559,7 @@ PrefabConnectionRecord connection if (component is not null) ids[component] = sourceId; } - return new SceneGraphReferenceMap(scene, allObjects, ids); + return new SceneGraphReferenceMap(scene, ids); } private static SceneGraphReferenceMap CreateTargetReferences( @@ -611,7 +611,7 @@ SerializationContext context .With(sourceAsset); try { - using (SceneGraphReferenceMap.Suspend()) + using (SceneReferenceScope.Suspend()) { GameObject sourceRoot = context.GetRequired().Deserialize( sourceAsset.runtimePayload.Span, diff --git a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphReferenceMap.cs b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphReferenceMap.cs index a4584d884..292656f14 100644 --- a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphReferenceMap.cs +++ b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphReferenceMap.cs @@ -1,116 +1,25 @@ using System; using System.Collections.Generic; -using Inno.Core.Execution; using Inno.Scene; namespace Inno.Scene; -internal sealed class SceneGraphReferenceMap +/// +/// Freezes local identities for graph construction without indexing live property access. +/// +internal sealed class SceneGraphReferenceMap : SceneReferenceScope { - private static readonly ExecutionSlot C_CURRENT = new("scene graph references"); - - private readonly Dictionary m_sourceIdByObject = new(ReferenceEqualityComparer.Instance); + private readonly IReadOnlyDictionary? m_sourceIdByObject; private readonly Dictionary m_objectBySourceId = new(); - private readonly HashSet m_allowedObjects = new(ReferenceEqualityComparer.Instance); - private readonly GameScene m_scene; - private readonly GameObject? m_captureRoot; internal SceneGraphReferenceMap( GameScene scene, - IEnumerable? allowedObjects = null, IReadOnlyDictionary? sourceIds = null, GameObject? captureRoot = null - ) { - m_scene = scene ?? throw new ArgumentNullException(nameof(scene)); - m_captureRoot = captureRoot; - if (allowedObjects is null) - return; - - foreach (EngineObject engineObject in allowedObjects) - { - m_allowedObjects.Add(engineObject); - m_sourceIdByObject.Add( - engineObject, - sourceIds is not null && sourceIds.TryGetValue(engineObject, out Guid sourceId) - ? sourceId - : engineObject.identity.persistentId); - } - } - - internal static SceneGraphReferenceMap current => C_CURRENT.current; - - internal static bool TryGetCurrent(out SceneGraphReferenceMap? references) - { - return C_CURRENT.TryGet(out references); - } - - internal IDisposable Enter() - { - return C_CURRENT.Enter(this, threadAffine: true); - } - - internal static IDisposable Suspend() - { - return C_CURRENT.Suspend(); - } - - internal EngineReferenceToken Capture( - EngineObject engineObject, - string path - ) { - ArgumentNullException.ThrowIfNull(engineObject); - if (engineObject.isDestroyed || - !m_allowedObjects.Contains(engineObject) || - !m_sourceIdByObject.TryGetValue(engineObject, out Guid sourceId)) - { - throw new InvalidOperationException( - $"Reference to '{engineObject.GetType().FullName}' with identity " + - $"'{engineObject.identity.persistentId}' at '{path}' is outside the serialized graph boundary" + - (m_captureRoot is null - ? "." - : $" rooted at GameObject '{m_captureRoot.name}' ({m_captureRoot.identity.persistentId}).")); - } - - GameScene ownerScene = engineObject switch - { - GameObject gameObject => gameObject.scene, - GameComponent component => component.gameObject.scene, - GameSystem system => system.ownerScene - ?? throw new InvalidOperationException($"Detached GameSystem reference at '{path}' cannot be serialized."), - _ => throw new InvalidOperationException( - $"Engine object type '{engineObject.GetType().FullName}' cannot be serialized as a scene reference at '{path}'.") - }; - if (!ReferenceEquals(ownerScene, m_scene)) - throw new InvalidOperationException($"Cross-scene object reference at '{path}' cannot be serialized."); - - EngineReferenceKind kind = engineObject switch - { - GameObject => EngineReferenceKind.GameObject, - GameComponent => EngineReferenceKind.GameComponent, - GameSystem => EngineReferenceKind.GameSystem, - _ => throw new InvalidOperationException($"Unsupported scene reference at '{path}'.") - }; - return new EngineReferenceToken(kind, sourceId); - } - - internal EngineObject Resolve( - EngineReferenceToken token, - Type expectedType, - string path - ) { - if (!m_objectBySourceId.TryGetValue(token.sourceId, out EngineObject? engineObject)) - { - throw new InvalidOperationException( - $"Scene graph reference '{token.sourceId}' at '{path}' could not be resolved."); - } - if (!expectedType.IsInstanceOfType(engineObject)) - { - throw new InvalidOperationException( - $"Scene graph reference '{token.sourceId}' at '{path}' resolves to " + - $"'{engineObject.GetType().FullName}', expected '{expectedType.FullName}'."); - } - return engineObject; + ) : base(scene, captureRoot) { + // Capture callers own the complete map and keep it fixed for this synchronous scope. + m_sourceIdByObject = sourceIds; } internal void Register( @@ -141,4 +50,19 @@ internal EngineObject GetRegistered(Guid sourceId) ? engineObject : throw new InvalidOperationException( $"Scene graph reference alias target '{sourceId}' could not be resolved."); + + /// + protected override bool TryGetSourceId( + EngineObject engineObject, + out Guid sourceId + ) { + sourceId = default; + return m_sourceIdByObject is not null && m_sourceIdByObject.TryGetValue(engineObject, out sourceId); + } + + /// + protected override bool TryResolve( + Guid sourceId, + out EngineObject? engineObject + ) => m_objectBySourceId.TryGetValue(sourceId, out engineObject); } diff --git a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSerialization.cs b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSerialization.cs index 541a022c2..1f515d513 100644 --- a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSerialization.cs +++ b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSerialization.cs @@ -61,15 +61,17 @@ internal static void WriteObjects( SerializationWriter writer, IReadOnlyCollection entries, IReadOnlyDictionary sourceIds, + Predicate containsObject, bool preserveRootSiblingOrder ) { ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(entries); ArgumentNullException.ThrowIfNull(sourceIds); - - var included = new HashSet( - entries.Select(static entry => entry.gameObject), - ReferenceEqualityComparer.Instance); + ArgumentNullException.ThrowIfNull(containsObject); + Action writeComponent = ( + componentWriter, + component + ) => WriteComponent(componentWriter, component, sourceIds); WriteMissingReferenceAliases(writer, sourceIds); writer.WriteObjectArray(C_OBJECTS_KEY, entries, ( objectWriter, @@ -78,7 +80,7 @@ bool preserveRootSiblingOrder { GameObject gameObject = entry.gameObject; Transform? parent = gameObject.transform.parent; - bool parentIsIncluded = parent is not null && included.Contains(parent.gameObject); + bool parentIsIncluded = parent is not null && containsObject(parent.gameObject); objectWriter.Write(C_OBJECT_ID_KEY, GetSourceId(sourceIds, gameObject)); objectWriter.Write(C_NAME_KEY, gameObject.name); objectWriter.Write(C_TAG_KEY, gameObject.tag); @@ -91,7 +93,7 @@ bool preserveRootSiblingOrder C_SIBLING_INDEX_KEY, parentIsIncluded || preserveRootSiblingOrder ? gameObject.transform.siblingIndex : 0); PrefabInstanceInfo? prefab = gameObject.prefabInstance; - bool hasPrefabConnection = prefab is not null && included.Contains(prefab.instanceRoot); + bool hasPrefabConnection = prefab is not null && containsObject(prefab.instanceRoot); objectWriter.Write(C_HAS_PREFAB_CONNECTION_KEY, hasPrefabConnection); if (hasPrefabConnection) { @@ -112,7 +114,7 @@ bool preserveRootSiblingOrder connection, gameObject.scene, objectWriter.context, - SceneGraphReferenceMap.current); + SceneReferenceScope.current); WritePrefabConnection( objectWriter, connection, @@ -127,36 +129,27 @@ bool preserveRootSiblingOrder C_PREFAB_ORPHANED_OVERRIDE_COUNT_KEY, capturedOverrides?.orphanedCount ?? prefab.orphanedOverrideCount); } - objectWriter.WriteObjectArray(C_COMPONENTS_KEY, entry.components, ( - componentWriter, - component - ) => + objectWriter.WriteObjectArray(C_COMPONENTS_KEY, entry.components, writeComponent); + }, captureKey: ( + entry, + key + ) => { + GameObject gameObject = entry.gameObject; + if (gameObject.prefabInstance is not null || !key.TryAddOwner(gameObject, gameObject.captureRevision)) + return false; + Transform? parent = gameObject.transform.parent; + bool parentIsIncluded = parent is not null && containsObject(parent.gameObject); + key.AddValue(GetSourceId(sourceIds, gameObject)); + key.AddValue(parentIsIncluded ? GetSourceId(sourceIds, parent!.gameObject) : Guid.Empty); + key.AddValue((ulong)(parentIsIncluded || preserveRootSiblingOrder ? gameObject.transform.siblingIndex : 0)); + for (int componentIndex = 0; componentIndex < entry.components.Count; componentIndex++) { - componentWriter.Write(C_COMPONENT_ID_KEY, GetSourceId(sourceIds, component)); - if (component is MissingGameComponent missing) - { - componentWriter.Write(C_STABLE_TYPE_ID_KEY, missing.missingType.stableId); - componentWriter.Write(C_TYPE_NAME_KEY, missing.missingTypeName); - componentWriter.Write(C_STATE_KEY, missing.CaptureSerializedState()); - WriteStateDependencies(componentWriter, missing.dependencies); - return; - } - componentWriter.Write(C_STABLE_TYPE_ID_KEY, GetStableComponentTypeId(component.GetType(), componentWriter.context)); - componentWriter.Write(C_TYPE_NAME_KEY, component.GetType().FullName ?? component.GetType().Name); - try - { - CapturedSceneState state = CaptureState(component, componentWriter.context); - componentWriter.Write(C_STATE_KEY, state.data); - WriteStateDependencies(componentWriter, state.dependencies); - } - catch (Exception exception) - { - throw new InvalidOperationException( - $"Failed to serialize GameComponent '{component.GetType().FullName}' on " + - $"GameObject '{gameObject.name}' at '{componentWriter.path}'. {exception.Message}", - exception); - } - }); + GameComponent component = entry.components[componentIndex]; + if (component is MissingGameComponent || !key.TryAddProperties(component)) + return false; + key.AddValue(GetSourceId(sourceIds, component)); + } + return true; }); } @@ -413,11 +406,10 @@ IReadOnlyDictionary sourceIds WriteStateDependencies(systemWriter, missing.dependencies); return; } - systemWriter.Write(C_STABLE_TYPE_ID_KEY, GetStableSystemTypeId(system.GetType(), systemWriter.context)); + systemWriter.Write(C_STABLE_TYPE_ID_KEY, systemWriter.typeSnapshot.GetTypeRef(system.GetType()).stableId); systemWriter.Write(C_TYPE_NAME_KEY, system.GetType().FullName ?? system.GetType().Name); - CapturedSceneState state = CaptureState(system, systemWriter.context); - systemWriter.Write(C_STATE_KEY, state.data); - WriteStateDependencies(systemWriter, state.dependencies); + AssetDependency[] dependencies = WriteState(systemWriter, system); + WriteStateDependencies(systemWriter, dependencies); }); } @@ -806,13 +798,6 @@ private static bool IsTransform(SerializationReader reader) out Type? type) && type == typeof(Transform); - private static Guid GetStableComponentTypeId( - Type componentType, - SerializationContext context - ) { - return context.GetRequired().GetTypeRef(componentType).stableId; - } - private static bool TryResolveComponentType( Guid stableTypeId, SerializationContext context, @@ -829,13 +814,6 @@ out Type? componentType return true; } - private static Guid GetStableSystemTypeId( - Type systemType, - SerializationContext context - ) { - return context.GetRequired().GetTypeRef(systemType).stableId; - } - private static bool TryResolveSystemType( Guid stableTypeId, SerializationContext context, @@ -857,18 +835,48 @@ out Type? type return context.GetRequired().TryResolve(typeRef, out type); } - private static CapturedSceneState CaptureState( - ISerializable value, - SerializationContext outerContext + private static void WriteComponent( + SerializationWriter writer, + GameComponent component, + IReadOnlyDictionary sourceIds + ) { + writer.Write(C_COMPONENT_ID_KEY, GetSourceId(sourceIds, component)); + if (component is MissingGameComponent missing) + { + writer.Write(C_STABLE_TYPE_ID_KEY, missing.missingType.stableId); + writer.Write(C_TYPE_NAME_KEY, missing.missingTypeName); + writer.Write(C_STATE_KEY, missing.CaptureSerializedState()); + WriteStateDependencies(writer, missing.dependencies); + return; + } + writer.Write(C_STABLE_TYPE_ID_KEY, writer.typeSnapshot.GetTypeRef(component.GetType()).stableId); + writer.Write(C_TYPE_NAME_KEY, component.GetType().FullName ?? component.GetType().Name); + GameObject gameObject = component.gameObject; + try + { + AssetDependency[] dependencies = WriteState(writer, component); + WriteStateDependencies(writer, dependencies); + } + catch (Exception exception) + { + throw new InvalidOperationException( + $"Failed to serialize GameComponent '{component.GetType().FullName}' on " + + $"GameObject '{gameObject.name}' at '{writer.path}'. {exception.Message}", + exception); + } + } + + private static AssetDependency[] WriteState( + SerializationWriter writer, + ISerializable value ) { + SerializationContext outerContext = writer.context; bool includeLastKnownPaths = !outerContext.TryGet(out AssetDependencyCollection? outerDependencies) || outerDependencies is null || outerDependencies.includeLastKnownPaths; var dependencies = new AssetDependencyCollection(includeLastKnownPaths); - byte[] data = outerContext.GetRequired().CapturePropertiesData( - value, - outerContext.With(dependencies)); - return new CapturedSceneState(data, dependencies.dependencies.ToArray()); + writer.WritePropertiesData(C_STATE_KEY, value, outerContext.With(dependencies)); + return dependencies.dependencies.ToArray(); } private static void WriteStateDependencies( @@ -877,14 +885,22 @@ IReadOnlyList dependencies ) { writer.Write(C_STATE_DEPENDENCIES_KEY, dependencies.ToArray()); IAssetReferenceResolver resolver = writer.context.GetRequired(); - AssetObject[] assets = dependencies - .Select(dependency => resolver.Resolve( + if (dependencies.Count == 0) + { + writer.Write(C_STATE_DEPENDENCY_ASSETS_KEY, Array.Empty()); + return; + } + var assets = new AssetObject[dependencies.Count]; + for (int index = 0; index < dependencies.Count; index++) + { + AssetDependency dependency = dependencies[index]; + assets[index] = resolver.Resolve( dependency.persistentId, dependency.type.stableId, dependency.lastKnownPath, typeof(AssetObject), - "scene.state.dependencies")) - .ToArray(); + "scene.state.dependencies"); + } writer.Write(C_STATE_DEPENDENCY_ASSETS_KEY, assets); } @@ -924,8 +940,4 @@ string path throw new InvalidDataException($"Scene element type name at '{path}' cannot be empty."); } - private readonly record struct CapturedSceneState( - byte[] data, - AssetDependency[] dependencies - ); } diff --git a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSourceIds.cs b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSourceIds.cs new file mode 100644 index 000000000..df1b197e8 --- /dev/null +++ b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneGraphSourceIds.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Inno.Scene; + +/// +/// Combines frozen structural identities with systems borrowed only by the current graph write. +/// +internal sealed class SceneGraphSourceIds : IReadOnlyDictionary +{ + private readonly IReadOnlyDictionary m_objects; + private readonly Dictionary? m_systems; + + internal SceneGraphSourceIds( + IReadOnlyDictionary objects, + IReadOnlyList systems + ) { + m_objects = objects; + // Systems have a separate lifecycle owner; the structural cache must not retain them. + if (systems.Count == 0) + return; + m_systems = new Dictionary(systems.Count, ReferenceEqualityComparer.Instance); + for (int index = 0; index < systems.Count; index++) + { + GameSystem system = systems[index]; + m_systems.Add(system, system.identity.persistentId); + } + } + + /// + public int Count => checked(m_objects.Count + (m_systems?.Count ?? 0)); + + /// + public IEnumerable Keys => EnumerateKeys(); + + /// + public IEnumerable Values => EnumerateValues(); + + /// + public Guid this[EngineObject key] => TryGetValue(key, out Guid value) ? value : throw new KeyNotFoundException(); + + /// + public bool ContainsKey(EngineObject key) => TryGetValue(key, out _); + + /// + public bool TryGetValue( + EngineObject key, + out Guid value + ) { + if (m_objects.TryGetValue(key, out value)) + return true; + if (m_systems is not null) + return m_systems.TryGetValue(key, out value); + value = default; + return false; + } + + /// + public IEnumerator> GetEnumerator() + { + foreach (KeyValuePair entry in m_objects) + yield return entry; + if (m_systems is not null) + foreach (KeyValuePair entry in m_systems) + yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable EnumerateKeys() + { + foreach (KeyValuePair entry in this) + yield return entry.Key; + } + + private IEnumerable EnumerateValues() + { + foreach (KeyValuePair entry in this) + yield return entry.Value; + } +} diff --git a/src/content/scene/Inno.Scene/Serialization/SceneGraph/ScenePropertyReferenceScope.cs b/src/content/scene/Inno.Scene/Serialization/SceneGraph/ScenePropertyReferenceScope.cs new file mode 100644 index 000000000..1b98620ee --- /dev/null +++ b/src/content/scene/Inno.Scene/Serialization/SceneGraph/ScenePropertyReferenceScope.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using Inno.Core.Identity; + +namespace Inno.Scene; + +/// +/// Resolves property references through the current owner domain without materializing a second live-object index. +/// +internal sealed class ScenePropertyReferenceScope : SceneReferenceScope +{ + private readonly IdentityAllocator m_identities; + private readonly RuntimeIdentity m_sceneIdentity; + private readonly Dictionary? m_aliases; + + internal ScenePropertyReferenceScope( + GameScene scene, + IReadOnlyDictionary? aliases = null + ) : base(scene) { + m_identities = IdentityAllocator.current; + if (scene.identity.runtimeIdentity is not { } owner || owner.domainId != m_identities.domainId || + scene.isDestroyed || !ReferenceEquals(m_identities.Get(owner), scene)) + throw new InvalidOperationException("Scene property serialization requires its current owning identity domain."); + m_sceneIdentity = owner; + if (aliases is not { Count: > 0 }) + return; + m_aliases = new Dictionary(aliases.Count); + foreach ((Guid alias, Guid targetId) in aliases) + { + EngineObject? target = m_identities.Get(targetId); + if (target is null || !BelongsToScene(target)) + throw new InvalidOperationException($"Scene reference alias '{alias}' targets missing object '{targetId}'."); + EngineObject? previous = m_identities.Get(alias); + if (previous is not null && BelongsToScene(previous) && !ReferenceEquals(previous, target)) + throw new InvalidOperationException($"Duplicate scene graph local identity '{alias}'."); + m_aliases.Add(alias, targetId); + } + } + + /// + /// + /// The current allocator must still own the Scene and the exact registered object in that Scene. + /// + protected override bool TryGetSourceId( + EngineObject engineObject, + out Guid sourceId + ) { + sourceId = default; + if (!HasCurrentOwner() || engineObject.identity.runtimeIdentity is not { } identity || + identity.domainId != m_identities.domainId || !ReferenceEquals(m_identities.Get(identity), engineObject) || + !BelongsToScene(engineObject)) + return false; + sourceId = engineObject.identity.persistentId; + return true; + } + + /// + /// + /// Neutral aliases resolve to current Core identities; destroyed or foreign Scene objects are rejected. + /// + protected override bool TryResolve( + Guid sourceId, + out EngineObject? engineObject + ) { + engineObject = null; + if (!HasCurrentOwner()) + return false; + Guid target = m_aliases is not null && m_aliases.TryGetValue(sourceId, out Guid alias) ? alias : sourceId; + EngineObject? candidate = m_identities.Get(target); + if (candidate is null || !BelongsToScene(candidate)) + return false; + engineObject = candidate; + return true; + } + + private bool HasCurrentOwner() + => IdentityAllocator.hasCurrent && ReferenceEquals(IdentityAllocator.current, m_identities) && + !scene.isDestroyed && ReferenceEquals(m_identities.Get(m_sceneIdentity), scene); + + private bool BelongsToScene(EngineObject value) + => !value.isDestroyed && value switch + { + GameObject gameObject => gameObject.isRuntimeValid && ReferenceEquals(gameObject.scene, scene), + GameComponent component => component.ownerOrNull is { isRuntimeValid: true } owner && ReferenceEquals(owner.scene, scene), + GameSystem system => ReferenceEquals(system.ownerScene, scene), + _ => false + }; +} diff --git a/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneReferenceScope.cs b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneReferenceScope.cs new file mode 100644 index 000000000..91ca42435 --- /dev/null +++ b/src/content/scene/Inno.Scene/Serialization/SceneGraph/SceneReferenceScope.cs @@ -0,0 +1,131 @@ +using System; +using Inno.Core.Execution; + +namespace Inno.Scene; + +/// +/// Owns the synchronous scene-reference protocol while a specific serialization boundary is active. +/// +internal abstract class SceneReferenceScope +{ + private static readonly ExecutionSlot C_CURRENT = new("scene graph references"); + private readonly GameObject? m_captureRoot; + + /// + /// Associates a synchronous reference boundary with its owning Scene. + /// + /// + /// The Scene whose object references this boundary may describe. + /// + /// + /// The optional frozen subtree root used in diagnostics, or null for a Scene-wide scope. + /// + /// + /// The owning Scene is null. + /// + protected SceneReferenceScope( + GameScene scene, + GameObject? captureRoot = null + ) { + this.scene = scene ?? throw new ArgumentNullException(nameof(scene)); + m_captureRoot = captureRoot; + } + + internal static SceneReferenceScope current => C_CURRENT.current; + /// + /// Gets the Scene that owns the references admitted by this boundary. + /// + protected GameScene scene { get; } + + internal static bool TryGetCurrent(out SceneReferenceScope? references) + => C_CURRENT.TryGet(out references); + + internal static IDisposable Suspend() => C_CURRENT.Suspend(); + + internal IDisposable Enter() => C_CURRENT.Enter(this, threadAffine: true); + + internal EngineReferenceToken Capture( + EngineObject engineObject, + string path + ) { + ArgumentNullException.ThrowIfNull(engineObject); + if (engineObject.isDestroyed || !TryGetSourceId(engineObject, out Guid sourceId)) + { + throw new InvalidOperationException( + $"Reference to '{engineObject.GetType().FullName}' with identity " + + $"'{engineObject.identity.persistentId}' at '{path}' is outside the serialized graph boundary" + + (m_captureRoot is null + ? "." + : $" rooted at GameObject '{m_captureRoot.name}' ({m_captureRoot.identity.persistentId}).")); + } + GameScene ownerScene = engineObject switch + { + GameObject gameObject => gameObject.scene, + GameComponent component => component.gameObject.scene, + GameSystem system => system.ownerScene + ?? throw new InvalidOperationException($"Detached GameSystem reference at '{path}' cannot be serialized."), + _ => throw new InvalidOperationException( + $"Engine object type '{engineObject.GetType().FullName}' cannot be serialized as a scene reference at '{path}'.") + }; + if (!ReferenceEquals(ownerScene, scene)) + throw new InvalidOperationException($"Cross-scene object reference at '{path}' cannot be serialized."); + EngineReferenceKind kind = engineObject switch + { + GameObject => EngineReferenceKind.GameObject, + GameComponent => EngineReferenceKind.GameComponent, + GameSystem => EngineReferenceKind.GameSystem, + _ => throw new InvalidOperationException($"Unsupported scene reference at '{path}'.") + }; + return new EngineReferenceToken(kind, sourceId); + } + + internal EngineObject Resolve( + EngineReferenceToken token, + Type expectedType, + string path + ) { + if (!TryResolve(token.sourceId, out EngineObject? engineObject) || engineObject is null) + throw new InvalidOperationException($"Scene graph reference '{token.sourceId}' at '{path}' could not be resolved."); + if (!expectedType.IsInstanceOfType(engineObject)) + { + throw new InvalidOperationException( + $"Scene graph reference '{token.sourceId}' at '{path}' resolves to " + + $"'{engineObject.GetType().FullName}', expected '{expectedType.FullName}'."); + } + return engineObject; + } + + /// + /// Looks up the identifier assigned to an object by this reference boundary. + /// + /// + /// The non-null object being captured within the synchronous owner operation. + /// + /// + /// The admitted identifier, or the empty GUID when the boundary rejects the object. + /// + /// + /// True when the boundary maps the object; false when it lies outside the admitted scope. + /// + protected abstract bool TryGetSourceId( + EngineObject engineObject, + out Guid sourceId + ); + + /// + /// Resolves one identifier within the frozen or current owner boundary. + /// + /// + /// The reference identifier from the current serialized payload. + /// + /// + /// The mapped object, or null when the boundary cannot resolve the identifier. + /// + /// + /// True when the boundary contains the identifier; false without publishing a replacement object. + /// + protected abstract bool TryResolve( + Guid sourceId, + out EngineObject? engineObject + ); +} diff --git a/src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs b/src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs index 7459fbf92..8870b8345 100644 --- a/src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs +++ b/src/content/scene/Inno.Scene/Serialization/ScenePropertySerialization.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Inno.Assets; using Inno.Core.Serialization; @@ -11,6 +10,11 @@ namespace Inno.Scene; /// /// Captures and restores individual scene-object properties while preserving scene reference identities. /// +/// +/// Each operation borrows its owning Core identity domain and resolves current references within that scene. +/// Property access does not enumerate unrelated scene objects. Complete graph and prefab operations keep +/// their explicit frozen local-identity boundaries. Asset references retain the supplied resolver context. +/// public static class ScenePropertySerialization { /// @@ -52,7 +56,7 @@ IAssetReferenceResolver assets ArgumentNullException.ThrowIfNull(assets); if (target is not ISerializable serializable) throw new ArgumentException($"Scene object '{target.GetType().FullName}' is not serializable.", nameof(target)); - SceneGraphReferenceMap references = CreateReferences(ResolveScene(target)); + ScenePropertyReferenceScope references = CreateReferences(ResolveScene(target)); SerializationContext context = AssetSerializationContext.Create(assets); using (references.Enter()) return serialization.CapturePropertyData(serializable, propertyName, context); @@ -89,7 +93,7 @@ IAssetReferenceResolver assets ArgumentNullException.ThrowIfNull(assets); if (target is not ISerializable serializable) throw new ArgumentException($"Scene object '{target.GetType().FullName}' is not serializable.", nameof(target)); - SceneGraphReferenceMap references = CreateReferences(ResolveScene(target)); + ScenePropertyReferenceScope references = CreateReferences(ResolveScene(target)); SerializationContext context = AssetSerializationContext.Create(assets); using (references.Enter()) return serialization.CapturePropertiesData(serializable, context); @@ -120,7 +124,7 @@ IAssetReferenceResolver assets ArgumentNullException.ThrowIfNull(assets); if (target is not ISerializable serializable) throw new ArgumentException($"Scene object '{target.GetType().FullName}' is not serializable.", nameof(target)); - SceneGraphReferenceMap references = CreateReferences(ResolveScene(target)); + ScenePropertyReferenceScope references = CreateReferences(ResolveScene(target)); SerializationContext context = AssetSerializationContext.Create(assets); using (references.Enter()) return serialization.CaptureProperties(serializable, context); @@ -136,7 +140,7 @@ IAssetReferenceResolver assets /// Bytes produced by or . /// /// - /// The compatibility policy applied to matching current properties. + /// The policy applied to unavailable or mismatched current properties. /// /// /// The serialization registry that owns the active converter generation. @@ -165,45 +169,16 @@ public static SerializationPropertyRestoreResult RestoreProperties( ArgumentNullException.ThrowIfNull(assets); if (target is not ISerializable serializable) throw new ArgumentException($"Scene object '{target.GetType().FullName}' is not serializable.", nameof(target)); - SceneGraphReferenceMap references = CreateReferences(ResolveScene(target)); + ScenePropertyReferenceScope references = CreateReferences(ResolveScene(target)); SerializationContext context = AssetSerializationContext.Create(assets); using (references.Enter()) return serialization.RestorePropertiesData(serializable, data, mode, context); } - internal static SceneGraphReferenceMap CreateReferences( + internal static ScenePropertyReferenceScope CreateReferences( GameScene scene, IReadOnlyDictionary? aliases = null - ) { - var objects = new List(); - IReadOnlyList gameObjects = scene.GetObjects(); - for (int i = 0; i < gameObjects.Count; i++) - { - GameObject gameObject = gameObjects[i]; - objects.Add(gameObject); - IReadOnlyList components = gameObject.GetComponents(); - for (int componentIndex = 0; componentIndex < components.Count; componentIndex++) - objects.Add(components[componentIndex]); - } - IReadOnlyList systems = scene.GetSystems(); - for (int i = 0; i < systems.Count; i++) - objects.Add(systems[i]); - - var references = new SceneGraphReferenceMap(scene, objects); - for (int i = 0; i < objects.Count; i++) - references.Register(objects[i].identity.persistentId, objects[i]); - if (aliases is not null) - { - var byId = objects.ToDictionary(static value => value.identity.persistentId); - foreach ((Guid alias, Guid targetId) in aliases) - { - if (!byId.TryGetValue(targetId, out EngineObject? target)) - throw new InvalidOperationException($"Scene reference alias '{alias}' targets missing object '{targetId}'."); - references.Register(alias, target); - } - } - return references; - } + ) => new(scene, aliases); internal static GameScene ResolveScene(EngineObject target) { diff --git a/src/content/scene/Inno.Scene/Serialization/SceneReloadStateTransfer.cs b/src/content/scene/Inno.Scene/Serialization/SceneReloadStateTransfer.cs index faed5aac5..098123d90 100644 --- a/src/content/scene/Inno.Scene/Serialization/SceneReloadStateTransfer.cs +++ b/src/content/scene/Inno.Scene/Serialization/SceneReloadStateTransfer.cs @@ -64,7 +64,7 @@ IAssetReferenceResolver assets var sourceIds = new Dictionary(ReferenceEqualityComparer.Instance); foreach (EngineObject target in engineObjects) sourceIds.Add(target, target.identity.persistentId); - var references = new SceneGraphReferenceMap(scene, engineObjects, sourceIds); + var references = new SceneGraphReferenceMap(scene, sourceIds); var states = new List(); using (references.Enter()) { diff --git a/src/content/scene/Inno.Scene/Serialization/SceneSubtreeStateConverter.cs b/src/content/scene/Inno.Scene/Serialization/SceneSubtreeStateConverter.cs index 54e9df78c..9faa25916 100644 --- a/src/content/scene/Inno.Scene/Serialization/SceneSubtreeStateConverter.cs +++ b/src/content/scene/Inno.Scene/Serialization/SceneSubtreeStateConverter.cs @@ -41,7 +41,7 @@ SceneSubtreeState value var sourceIds = new Dictionary(ReferenceEqualityComparer.Instance); for (int i = 0; i < allObjects.Length; i++) sourceIds.Add(allObjects[i], allObjects[i].identity.persistentId); - var references = new SceneGraphReferenceMap(scene, allObjects, sourceIds, root); + var references = new SceneGraphReferenceMap(scene, sourceIds, root); writer.Write(C_ROOT_ID_KEY, root.identity.persistentId); using (references.Enter()) @@ -50,6 +50,7 @@ SceneSubtreeState value writer, entries, sourceIds, + included.Contains, preserveRootSiblingOrder: true); } } diff --git a/src/content/scene/Inno.Scene/Serialization/SerializedPropertyValueCodec.cs b/src/content/scene/Inno.Scene/Serialization/SerializedPropertyValueCodec.cs index 2730aef80..ddb43ad1d 100644 --- a/src/content/scene/Inno.Scene/Serialization/SerializedPropertyValueCodec.cs +++ b/src/content/scene/Inno.Scene/Serialization/SerializedPropertyValueCodec.cs @@ -22,7 +22,7 @@ internal static byte[] Encode( SerializationMemberMetadata member, GameComponent component, SerializationContext context, - SceneGraphReferenceMap references + SceneReferenceScope references ) { ArgumentNullException.ThrowIfNull(member); ArgumentNullException.ThrowIfNull(component); @@ -39,7 +39,7 @@ internal static void Decode( GameComponent component, ReadOnlySpan bytes, SerializationContext context, - SceneGraphReferenceMap references + SceneReferenceScope references ) { ArgumentNullException.ThrowIfNull(member); ArgumentNullException.ThrowIfNull(component); diff --git a/src/content/scene/Inno.Scene/Service/HierarchyService.cs b/src/content/scene/Inno.Scene/Service/HierarchyService.cs index 60f929fad..0308770d5 100644 --- a/src/content/scene/Inno.Scene/Service/HierarchyService.cs +++ b/src/content/scene/Inno.Scene/Service/HierarchyService.cs @@ -13,26 +13,33 @@ internal sealed class HierarchyService { private readonly GameScene m_scene; private readonly List m_roots = []; + private readonly Dictionary m_rootIndices = new(ReferenceEqualityComparer.Instance); internal HierarchyService(GameScene scene) { m_scene = scene; } + internal ulong revision { get; private set; } + internal void Register(Transform transform) { - if (!m_roots.Contains(transform)) + if (m_rootIndices.TryAdd(transform, m_roots.Count)) + { m_roots.Add(transform); + revision++; + } } internal void Unregister(Transform transform) { Transform? parent = transform.parent; if (parent is null) - m_roots.Remove(transform); + RemoveRoot(transform); else parent.RemoveChildDirect(transform); transform.SetParentDirect(null); + revision++; } internal void SetParent( @@ -66,13 +73,13 @@ bool worldPositionStays Transform? previousParent = transform.parent; if (previousParent is null) - m_roots.Remove(transform); + RemoveRoot(transform); else previousParent.RemoveChildDirect(transform); transform.SetParentDirect(parent); if (parent is null) - m_roots.Add(transform); + Register(transform); else parent.AddChildDirect(transform); @@ -89,13 +96,14 @@ bool worldPositionStays } m_scene.RecomputeActiveSubtree(transform.gameObject); + revision++; } internal int GetSiblingIndex(Transform transform) { if (transform.parent is Transform parent) return parent.IndexOfChild(transform); - return m_roots.IndexOf(transform); + return m_rootIndices.GetValueOrDefault(transform, -1); } internal void SetSiblingIndex( @@ -109,11 +117,14 @@ int siblingIndex throw new InvalidOperationException("Transform is missing from its parent child collection."); int target = Math.Clamp(siblingIndex, 0, parent.children.Count - 1); if (current != target) + { parent.MoveChild(current, target); + revision++; + } return; } - int rootIndex = m_roots.IndexOf(transform); + int rootIndex = m_rootIndices.GetValueOrDefault(transform, -1); if (rootIndex < 0) throw new InvalidOperationException("Transform is missing from the scene hierarchy index."); int rootTarget = Math.Clamp(siblingIndex, 0, m_roots.Count - 1); @@ -121,9 +132,30 @@ int siblingIndex return; m_roots.RemoveAt(rootIndex); m_roots.Insert(rootTarget, transform); + RefreshRootIndices(Math.Min(rootIndex, rootTarget), Math.Max(rootIndex, rootTarget) + 1); + revision++; + } + + internal void Clear() + { + m_roots.Clear(); + m_rootIndices.Clear(); + revision++; } - internal IReadOnlyList GetRoots() => m_roots.ToArray(); + private void RemoveRoot(Transform transform) + { + if (!m_rootIndices.Remove(transform, out int index)) + return; + m_roots.RemoveAt(index); + RefreshRootIndices(index, m_roots.Count); + } - internal void Clear() => m_roots.Clear(); + private void RefreshRootIndices( + int start, + int end + ) { + for (int index = start; index < end; index++) + m_rootIndices[m_roots[index]] = index; + } } diff --git a/src/content/scene/Inno.Scene/Storage/SceneObjectRecord.cs b/src/content/scene/Inno.Scene/Storage/SceneObjectRecord.cs index 0f5da9692..62ae955c6 100644 --- a/src/content/scene/Inno.Scene/Storage/SceneObjectRecord.cs +++ b/src/content/scene/Inno.Scene/Storage/SceneObjectRecord.cs @@ -7,11 +7,15 @@ namespace Inno.Scene; /// internal sealed class SceneObjectRecord { - internal SceneObjectRecord(GameObject gameObject) - { + internal SceneObjectRecord( + SceneStore store, + GameObject gameObject + ) { + this.store = store; this.gameObject = gameObject; } + internal SceneStore store { get; } internal GameObject gameObject { get; } internal List components { get; } = []; internal bool isAlive { get; set; } = true; diff --git a/src/content/scene/Inno.Scene/Storage/SceneStore.cs b/src/content/scene/Inno.Scene/Storage/SceneStore.cs index 03a6c8887..cf794091c 100644 --- a/src/content/scene/Inno.Scene/Storage/SceneStore.cs +++ b/src/content/scene/Inno.Scene/Storage/SceneStore.cs @@ -18,15 +18,15 @@ internal enum SceneStoreRemovalKind /// /// The none key. /// -None, + None, /// /// The canceled pending addition key. /// -CanceledPendingAddition, + CanceledPendingAddition, /// /// The removed committed key. /// -RemovedCommitted, + RemovedCommitted, } internal readonly record struct SceneStoreRemovedComponent( @@ -64,6 +64,7 @@ internal sealed class SceneStore private readonly Dictionary m_typedComponentQueryCache = []; private readonly Dictionary> m_objectQueryCache = []; private GameObject[]? m_objectSnapshotCache; + private SceneStructureSnapshot? m_structureSnapshotCache; private long m_nextObjectOrder; private long m_structureRevision; private int m_executionDepth; @@ -116,7 +117,7 @@ internal void AddObject(GameObject gameObject) $"GameObject '{gameObject.identity.persistentId}' is already owned by this scene store."); } - var record = new SceneObjectRecord(gameObject); + var record = new SceneObjectRecord(this, gameObject); long order = m_nextObjectOrder; m_nextObjectOrder = checked(m_nextObjectOrder + 1); try @@ -129,6 +130,7 @@ internal void AddObject(GameObject gameObject) .Set(m_objectLayerKey, gameObject.storedLayer) .Set(m_objectCommittedKey, false) .Set(m_objectOrderKey, order); + gameObject.AttachRecord(record); } catch { @@ -260,6 +262,7 @@ internal IReadOnlyList RemoveObject(GameObject gameO return Array.Empty(); record.isAlive = false; + gameObject.ReleaseRecord(record); GameComponent[] attached = [.. record.components]; var removed = new SceneStoreRemovedComponent[attached.Length]; for (int i = 0; i < attached.Length; i++) @@ -284,7 +287,7 @@ internal IReadOnlyList RemoveObject(GameObject gameO return removed; } - internal bool Contains(GameObject gameObject) => TryGetRecord(gameObject, out SceneObjectRecord? record) && record!.isAlive; + internal bool Contains(GameObject gameObject) => gameObject.GetAttachment(this)?.isAlive == true; internal IReadOnlyList GetObjects() { @@ -449,6 +452,7 @@ int componentIndex return; record.components.RemoveAt(currentIndex); record.components.Insert(targetIndex, component); + m_structureSnapshotCache = null; } internal IReadOnlyList GetComponents( @@ -572,10 +576,12 @@ internal SceneStructureSnapshot CaptureStructure() "A scene cannot be captured during an execution phase with uncommitted structural changes."); } + if (m_structureSnapshotCache is not null) + return m_structureSnapshotCache; SceneObjectStructureSnapshot[] objects = GetObjects() .Select(gameObject => new SceneObjectStructureSnapshot(gameObject, GetComponents(gameObject))) .ToArray(); - return new SceneStructureSnapshot(objects); + return m_structureSnapshotCache = new SceneStructureSnapshot(objects); } internal void InvalidateTypeCaches() @@ -767,6 +773,7 @@ private void InvalidateStructureCaches() { m_structureRevision = unchecked(m_structureRevision + 1); m_objectSnapshotCache = null; + m_structureSnapshotCache = null; InvalidateTypeCaches(); } @@ -791,6 +798,14 @@ private void ClearImmediately() m_pendingComponentAdditions.Clear(); m_pendingComponentRemovals.Clear(); m_components.RemoveAll(); + foreach (SceneObjectRecord record in m_objects.AllFast()) + { + if (record.isAlive) + { + record.isAlive = false; + record.gameObject.ReleaseRecord(record); + } + } m_objects.RemoveAll(); m_nextObjectOrder = 0; m_clearRequested = false; diff --git a/src/content/scene/Inno.Scene/Storage/SceneStructureSnapshot.cs b/src/content/scene/Inno.Scene/Storage/SceneStructureSnapshot.cs index 2d17a5f67..5ec9d6a3e 100644 --- a/src/content/scene/Inno.Scene/Storage/SceneStructureSnapshot.cs +++ b/src/content/scene/Inno.Scene/Storage/SceneStructureSnapshot.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; namespace Inno.Scene; @@ -16,4 +18,49 @@ internal sealed record SceneObjectStructureSnapshot( IReadOnlyList components ); -internal sealed record SceneStructureSnapshot(IReadOnlyList objects); +/// +/// Owns one attachment snapshot and its frozen object/component serialization identities. +/// +internal sealed class SceneStructureSnapshot +{ + private Dictionary? m_sourceIds; + private IReadOnlyDictionary? m_frozenSourceIds; + + internal SceneStructureSnapshot(IReadOnlyList objects) => this.objects = objects; + + internal IReadOnlyList objects { get; } + + internal IReadOnlyDictionary CaptureObjectSourceIds() + { + if (m_sourceIds is not null && HasCurrentIdentities(m_sourceIds)) + return m_frozenSourceIds!; + + int count = objects.Count; + foreach (SceneObjectStructureSnapshot entry in objects) + count = checked(count + entry.components.Count); + var sourceIds = new Dictionary(count, ReferenceEqualityComparer.Instance); + foreach (SceneObjectStructureSnapshot entry in objects) + { + sourceIds.Add(entry.gameObject, entry.gameObject.identity.persistentId); + for (int index = 0; index < entry.components.Count; index++) + { + GameComponent component = entry.components[index]; + sourceIds.Add(component, component.identity.persistentId); + } + } + // Replace the projection so an already borrowed graph write keeps its original identities. + var frozen = new ReadOnlyDictionary(sourceIds); + m_sourceIds = sourceIds; + m_frozenSourceIds = frozen; + return frozen; + } + + private static bool HasCurrentIdentities(Dictionary sourceIds) + { + // Core registry rebinding can replace an identity without changing the structural snapshot. + foreach ((EngineObject value, Guid persistentId) in sourceIds) + if (value.identity.persistentId != persistentId) + return false; + return true; + } +} diff --git a/src/foundation/core/Inno.Core.IO/AtomicDirectory.cs b/src/foundation/core/Inno.Core.IO/AtomicDirectory.cs index a9da7d841..52808ff35 100644 --- a/src/foundation/core/Inno.Core.IO/AtomicDirectory.cs +++ b/src/foundation/core/Inno.Core.IO/AtomicDirectory.cs @@ -71,8 +71,13 @@ public static void Publish( /// /// The destination directory. /// + /// + /// Cancels preparation or bounded rename waits before the candidate commits. + /// /// /// The caller must serialize readers and writers while the two directory moves run. + /// Cancellation before commit restores the previous destination without canceling rollback. + /// A committed candidate is not canceled afterward. /// Backup cleanup runs after commit; its failure preserves the installed candidate and remaining backup. /// /// @@ -93,10 +98,15 @@ public static void Publish( /// /// Installation and restoration both fail; neither tree is deleted to force a rollback. /// + /// + /// Cancellation is requested before the candidate commits; any moved previous tree is restored. + /// public static void Install( string source, - string destination + string destination, + CancellationToken cancellationToken = default ) { + cancellationToken.ThrowIfCancellationRequested(); ArgumentException.ThrowIfNullOrWhiteSpace(source); ArgumentException.ThrowIfNullOrWhiteSpace(destination); string candidate = Path.GetFullPath(source); @@ -114,10 +124,10 @@ string destination string backup = target + ".backup-" + Guid.NewGuid().ToString("N"); if (Directory.Exists(target)) - FileSystemRename.MoveDirectory(target, backup); + FileSystemRename.MoveDirectory(target, backup, cancellationToken); try { - FileSystemRename.MoveDirectory(candidate, target); + FileSystemRename.MoveDirectory(candidate, target, cancellationToken); } catch (Exception installationFailure) { diff --git a/src/foundation/core/Inno.Core.IO/AtomicFile.cs b/src/foundation/core/Inno.Core.IO/AtomicFile.cs index 0f842acc9..9d6d72289 100644 --- a/src/foundation/core/Inno.Core.IO/AtomicFile.cs +++ b/src/foundation/core/Inno.Core.IO/AtomicFile.cs @@ -8,6 +8,32 @@ namespace Inno.Core.IO; /// public static class AtomicFile { + private const string C_CANDIDATE_MARKER = ".staging-"; + + /// + /// Recognizes the reserved temporary file names created by atomic byte publication. + /// + /// + /// The file name or path to inspect without opening the file. + /// + /// + /// True for a nonempty destination name followed by the reserved marker and a complete hexadecimal operation identity. + /// + /// + /// Recognition excludes transient payloads from source discovery. It does not establish ownership or authorize deletion. + /// + /// + /// The path is null. + /// + public static bool IsCandidatePath(string path) + { + ArgumentNullException.ThrowIfNull(path); + ReadOnlySpan name = Path.GetFileName(path.AsSpan()); + int marker = name.Length - C_CANDIDATE_MARKER.Length - 32; + return marker > 0 && name.Slice(marker, C_CANDIDATE_MARKER.Length).SequenceEqual(C_CANDIDATE_MARKER) + && Guid.TryParseExact(name[(marker + C_CANDIDATE_MARKER.Length)..], "N", out _); + } + /// /// Writes a complete byte payload and atomically installs it at the destination. /// @@ -20,13 +46,20 @@ public static class AtomicFile /// /// Whether an existing destination may be replaced; false atomically rejects name collisions. /// + /// + /// Publication or cleanup fails, the destination already exists in create-only mode, or the + /// filesystem cannot atomically publish a complete file. No copy fallback exposes partial content. + /// + /// + /// The host does not provide an atomic create-only filesystem primitive. + /// public static void WriteAllBytes( string path, ReadOnlySpan data, bool overwrite = true ) { string destination = NormalizeDestination(path); - string candidate = destination + ".staging-" + Guid.NewGuid().ToString("N"); + string candidate = destination + C_CANDIDATE_MARKER + Guid.NewGuid().ToString("N"); try { using (var stream = new FileStream( @@ -61,6 +94,19 @@ public static void WriteAllBytes( /// /// Whether an existing destination may be replaced; false never overwrites an external creator. /// + /// + /// A failed create-only publication preserves the candidate. After successful publication the + /// candidate is removed; a subsequent cleanup failure reports that the complete target exists. + /// Unix create-only publication requires same-directory hard-link support. The caller must own + /// the candidate and parent directory until the operation completes. + /// + /// + /// Publication fails, create-only mode finds an existing destination, or candidate retirement + /// fails after the complete destination has been published. + /// + /// + /// The host does not provide an atomic create-only filesystem primitive. + /// public static void Install( string source, string destination, diff --git a/src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs b/src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs index b36620eb7..9c8695b7d 100644 --- a/src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs +++ b/src/foundation/core/Inno.Core.IO/FileByteDocumentStore.cs @@ -37,11 +37,27 @@ public FileByteDocumentStore(string path) public bool exists => File.Exists(m_path); /// + /// + /// One rename-compatible handle pins the selected complete publication until its owned bytes are read. + /// Writers must atomically replace the document rather than modify the selected file in place. + /// + /// + /// The document cannot be read completely or exceeds the maximum supported byte-array size. + /// + /// + /// The selected location denies reading or names a directory instead of a document. + /// public byte[]? Read() { try { - return File.ReadAllBytes(m_path); + using var stream = new FileStream(m_path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); + long length = stream.Length; + if (length > Array.MaxLength) + throw new IOException("The file document exceeds the maximum supported byte-array size."); + byte[] data = GC.AllocateUninitializedArray((int)length); + stream.ReadExactly(data); + return data; } catch (FileNotFoundException) { diff --git a/src/foundation/core/Inno.Core.IO/FileSystemRename.cs b/src/foundation/core/Inno.Core.IO/FileSystemRename.cs index a61c8adcf..0f9f6d20f 100644 --- a/src/foundation/core/Inno.Core.IO/FileSystemRename.cs +++ b/src/foundation/core/Inno.Core.IO/FileSystemRename.cs @@ -20,7 +20,16 @@ internal static void MoveFile( string source, string destination, bool overwrite - ) => Execute(() => File.Move(source, destination, overwrite), default); + ) { + if (overwrite || OperatingSystem.IsWindows()) + { + Execute(() => File.Move(source, destination, overwrite), default); + return; + } + // The SDK's Unix Move(false) can check absence and then replace a competing creator. + // POSIX link creates the complete destination in one no-clobber filesystem operation. + PosixFilePublication.Install(source, destination); + } private static void Execute( Action rename, diff --git a/src/foundation/core/Inno.Core.IO/Inno.Core.IO.csproj b/src/foundation/core/Inno.Core.IO/Inno.Core.IO.csproj index cb3f5bcc1..c6e2f6885 100644 --- a/src/foundation/core/Inno.Core.IO/Inno.Core.IO.csproj +++ b/src/foundation/core/Inno.Core.IO/Inno.Core.IO.csproj @@ -4,6 +4,7 @@ 14.0 disable enable + true true $(WarningsAsErrors);CS1572;CS1573;CS1591 diff --git a/src/foundation/core/Inno.Core.IO/PosixFilePublication.cs b/src/foundation/core/Inno.Core.IO/PosixFilePublication.cs new file mode 100644 index 000000000..517daad74 --- /dev/null +++ b/src/foundation/core/Inno.Core.IO/PosixFilePublication.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; + +namespace Inno.Core.IO; + +internal static partial class PosixFilePublication +{ + internal static void Install( + string source, + string destination + ) { + int result; + try + { + result = Link(source, destination); + } + catch (Exception exception) when (exception is DllNotFoundException or EntryPointNotFoundException or PlatformNotSupportedException) + { + throw new PlatformNotSupportedException("The host does not provide atomic create-only file publication through POSIX link.", exception); + } + if (result != 0) + { + int error = Marshal.GetLastPInvokeError(); + throw new IOException( + $"Atomic create-only publication of '{source}' at '{destination}' failed (errno {error}). " + + "The destination was not replaced; the filesystem must support same-directory hard links.", + new Win32Exception(error)); + } + + try + { + File.Delete(source); + } + catch (IOException exception) + { + throw CleanupFailure(source, destination, exception); + } + catch (UnauthorizedAccessException exception) + { + throw CleanupFailure(source, destination, exception); + } + } + + private static IOException CleanupFailure( + string source, + string destination, + Exception exception + ) => new($"The complete file was published at '{destination}', but its candidate '{source}' could not be retired.", exception); + + [LibraryImport("libc", EntryPoint = "link", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + private static partial int Link( + string source, + string destination + ); +} diff --git a/src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs b/src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs new file mode 100644 index 000000000..6b5262e40 --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Capture/SerializationCaptureKeyBuilder.cs @@ -0,0 +1,361 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using Inno.Core.Identity; +using Inno.Core.Serialization.Converters; + +namespace Inno.Core.Serialization; + +/// +/// Builds an exact neutral key during one owned synchronous converter capture callback. +/// +/// +/// Only current Core identities, revisions, metadata stamps and neutral values enter the key. Key storage is not +/// serialized or restored. Every failed TryAdd invalidates the whole key. The borrowed builder retires after the callback. +/// +public sealed class SerializationCaptureKeyBuilder +{ + private const byte C_OWNER = 1; + private const byte C_VALUE = 2; + private const byte C_TEXT = 3; + private const byte C_GUARD = 4; + internal const byte C_GUID = 5; + + private readonly int m_threadId = Environment.CurrentManagedThreadId; + private SerializationCaptureIdentity[] m_identities = []; + private int m_identityCount; + private SerializationOperation? m_operation; + private SerializationCaptureKey? m_previous; + private List? m_parts; + private int m_count; + private int m_capacity; + private bool m_unavailable; + + internal SerializationCaptureKeyBuilder( + SerializationOperation operation, + Type rootType, + Inno.Core.Identity.Identity source, + SerializationCaptureKey? previous + ) : this(operation, previous) { + if (source.runtimeIdentity is not { } identity) + { + m_unavailable = true; + return; + } + Add(new(C_GUARD, identity, 0, operation.GetMetadata(rootType).captureIdentity, null)); + AddIdentity(new(source, identity)); + } + + internal SerializationCaptureKeyBuilder( + SerializationOperation operation, + SerializationCaptureKey? previous + ) { + m_operation = operation; + m_previous = previous; + Add(new(C_GUARD, default, 0, operation.converters.captureIdentity, null)); + } + + /// + /// Gets the current immutable owner context while this callback is active. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public SerializationContext context => RequireOperation().context; + + /// + /// Reserves capacity for actual frozen input entries without changing a matching key. + /// + /// + /// The expected total entry count, including Core guards. + /// + /// + /// Capacity is negative. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public void EnsureCapacity(int capacity) + { + RequireOperation(); + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + m_capacity = Math.Max(m_capacity, capacity); + m_parts?.EnsureCapacity(capacity); + EnsureIdentityCapacity(capacity); + } + + /// + /// Adds an owner revision through its current Core identity; a null revision or detached source invalidates the key. + /// + /// + /// The current owner, never retained in the completed key. + /// + /// + /// A complete revision for the producer-owned state, or null when reuse cannot be certified. + /// + /// + /// True when an identity and revision were available; false invalidates the complete key. + /// + /// + /// Source is null. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public bool TryAddOwner( + IdentityObject source, + ulong? revision + ) { + RequireOperation(); + ArgumentNullException.ThrowIfNull(source); + Inno.Core.Identity.Identity snapshot = source.identity; + if (!revision.HasValue || snapshot.runtimeIdentity is not { } identity) + return Decline(); + Add(new(C_OWNER, identity, revision.Value, default, null)); + AddIdentity(new(snapshot, identity)); + return true; + } + + /// + /// Adds a complete property revision only when current metadata proves context-free default capture without converters. + /// + /// + /// The current serializable identity object. + /// + /// + /// True for certified property state; false invalidates the complete key and requires normal capture. + /// + /// + /// Source is null. + /// + /// + /// Metadata/revision access fails, or the callback has retired or is accessed from another thread. + /// + public bool TryAddProperties(ISerializable source) + { + SerializationOperation operation = RequireOperation(); + ArgumentNullException.ThrowIfNull(source); + if (source is not IdentityObject identified) + return Decline(); + Inno.Core.Identity.Identity snapshot = identified.identity; + if (snapshot.runtimeIdentity is not { } identity) + return Decline(); + ISerializationMetadataSource provider = operation.context.GetRequired(); + SerializationTypeMetadata metadata = provider.GetMetadata(source.GetType()); + if (metadata.captureRevision is null) + { + foreach (SerializationMemberMetadata member in metadata.members) + if ((member.visibility & PropertyVisibility.Serialize) != 0) + return Decline(); + } + if (!operation.converters.CanReuseRevision(metadata, provider)) + return Decline(); + ulong? revision = metadata.captureRevision is { } readRevision ? readRevision(source) : 0; + if (!revision.HasValue) + return Decline(); + Add(new(C_OWNER, identity, revision.Value, metadata.captureIdentity, null)); + AddIdentity(new(snapshot, identity)); + return true; + } + + /// + /// Adds an exact neutral numeric input to the declared key. + /// + /// + /// The current complete input value. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public void AddValue(ulong value) => Add(new(C_VALUE, default, value, default, null)); + + /// + /// Adds an exact neutral identifier without hashing or allocating text. + /// + /// + /// The current identifier input, including an empty identifier when meaningful. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public void AddValue(Guid value) => Add(new(C_GUID, default, 0, value, null)); + + /// + /// Adds exact ordinal text, preserving the distinction between null and empty. + /// + /// + /// The immutable current text input. + /// + /// + /// The callback has retired or is accessed from another thread. + /// + public void AddText(string? value) => Add(new(C_TEXT, default, 0, default, value)); + + internal SerializationCaptureAttempt Complete() + { + RequireOperation(); + if (m_unavailable || m_identityCount == 0) + return SerializationCaptureAttempt.empty; + SerializationCaptureKey key = m_parts is null && m_previous is not null && m_previous.parts.Length == m_count + ? m_previous + : new(m_parts is not null ? m_parts.ToArray() : m_previous!.parts.AsSpan(0, m_count).ToArray()); + ReadOnlySpan identities = m_identities.AsSpan(0, m_identityCount); + foreach (SerializationCaptureIdentity identity in identities) + identity.Validate(); + return new(key, ReferenceEquals(key, m_previous) ? [] : identities.ToArray()); + } + + internal void Retire() + { + m_operation = null; + m_previous = null; + m_parts?.Clear(); + m_parts = null; + SerializationCaptureIdentity[] identities = m_identities; + m_identities = []; + m_identityCount = 0; + if (identities.Length > 0) + ArrayPool.Shared.Return(identities, clearArray: true); + } + + private SerializationOperation RequireOperation() + { + SerializationOperation operation = m_operation ?? throw new InvalidOperationException("The serialization key callback has retired."); + if (Environment.CurrentManagedThreadId != m_threadId) + throw new InvalidOperationException("The serialization key callback belongs to another thread."); + operation.EnsureActive(); + return operation; + } + + private bool Decline() + { + m_unavailable = true; + return false; + } + + private void AddIdentity(SerializationCaptureIdentity identity) + { + EnsureIdentityCapacity(checked(m_identityCount + 1)); + m_identities[m_identityCount++] = identity; + } + + private void EnsureIdentityCapacity(int capacity) + { + if (capacity <= m_identities.Length) + return; + SerializationCaptureIdentity[] identities = ArrayPool.Shared.Rent(capacity); + m_identities.AsSpan(0, m_identityCount).CopyTo(identities); + SerializationCaptureIdentity[] previous = m_identities; + m_identities = identities; + if (previous.Length > 0) + ArrayPool.Shared.Return(previous, clearArray: true); + } + + private void Add(SerializationCaptureKeyPart part) + { + RequireOperation(); + if (m_parts is null && m_previous is not null && m_count < m_previous.parts.Length && m_previous.parts[m_count] == part) + { + m_count++; + return; + } + if (m_parts is null) + { + m_parts = new(Math.Max(m_capacity, m_count + 1)); + if (m_previous is not null) + for (int index = 0; index < m_count; index++) + m_parts.Add(m_previous.parts[index]); + } + m_parts.Add(part); + m_count++; + } +} + +internal sealed record SerializationCaptureKey(SerializationCaptureKeyPart[] parts) +{ + internal bool HasSameInputs(SerializationCaptureKey other) + { + if (parts.Length != other.parts.Length) + return false; + for (int index = 0; index < parts.Length; index++) + { + SerializationCaptureKeyPart before = parts[index]; + SerializationCaptureKeyPart after = other.parts[index]; + if (before.kind != after.kind || before.identity != after.identity || + before.revision != after.revision || !string.Equals(before.text, after.text, StringComparison.Ordinal) || + before.kind == SerializationCaptureKeyBuilder.C_GUID && before.metadata != after.metadata) + return false; + } + return true; + } +} + +internal readonly record struct SerializationCaptureAttempt( + SerializationCaptureKey? key, + SerializationCaptureIdentity[] identities +) { + internal static readonly SerializationCaptureAttempt empty = new(null, []); + + internal static SerializationCaptureAttempt Capture( + SerializationOperation operation, + Type valueType, + T value, + SerializationCaptureKey? previous, + SerializationConverter? converter = null, + Func? captureKey = null + ) { + if (!operation.isSnapshotCapture) + return empty; + SerializationCaptureKeyBuilder builder; + if (captureKey is not null) + builder = new(operation, previous); + else if (value is IdentityObject identified && identified.identity.runtimeIdentity is not null) + builder = new(operation, valueType, identified.identity, previous); + else + return empty; + try + { + bool complete = captureKey is not null ? captureKey(value, builder) : converter!.CaptureKey(value, builder); + return complete ? builder.Complete() : empty; + } + finally + { + builder.Retire(); + } + } + + internal SerializationCaptureKey? Verify(SerializationCaptureAttempt after) + { + if (key is null || after.key is null || !key.HasSameInputs(after.key)) + throw new InvalidOperationException("The declared serialization capture inputs changed during the write."); + // Fresh equivalent metadata preserves ordinary capture without publishing a reusable hint. + return ReferenceEquals(after.key, key) ? key : null; + } + + internal void ValidateIdentities() + { + if (identities is null) + return; + foreach (SerializationCaptureIdentity identity in identities) + identity.Validate(); + } +} + +internal readonly record struct SerializationCaptureIdentity( + Inno.Core.Identity.Identity identity, + RuntimeIdentity expected +) { + internal void Validate() + { + if (identity.runtimeIdentity != expected) + throw new InvalidOperationException($"Core identity '{expected}' changed during serialization capture."); + } +} + +internal readonly record struct SerializationCaptureKeyPart( + byte kind, + RuntimeIdentity identity, + ulong revision, + Guid metadata, + string? text +); diff --git a/src/foundation/core/Inno.Core.Serialization/Context/SerializationContext.cs b/src/foundation/core/Inno.Core.Serialization/Context/SerializationContext.cs index 8705abe2e..4e9b73008 100644 --- a/src/foundation/core/Inno.Core.Serialization/Context/SerializationContext.cs +++ b/src/foundation/core/Inno.Core.Serialization/Context/SerializationContext.cs @@ -8,9 +8,9 @@ namespace Inno.Core.Serialization; /// public sealed class SerializationContext { - private readonly IReadOnlyDictionary m_values; + private readonly KeyValuePair[] m_values; - private SerializationContext(IReadOnlyDictionary values) + private SerializationContext(KeyValuePair[] values) { m_values = values; } @@ -18,7 +18,7 @@ private SerializationContext(IReadOnlyDictionary values) /// /// Gets an empty serialization context. /// - public static SerializationContext empty { get; } = new(new Dictionary()); + public static SerializationContext empty { get; } = new([]); /// /// Returns a new context containing the supplied value under its declared context type. @@ -38,11 +38,21 @@ private SerializationContext(IReadOnlyDictionary values) public SerializationContext With(TContext value) where TContext : class { ArgumentNullException.ThrowIfNull(value); - var values = new Dictionary(m_values) + Type key = typeof(TContext); + for (int index = 0; index < m_values.Length; index++) { - [typeof(TContext)] = value - }; - return new SerializationContext(values); + if (m_values[index].Key != key) + continue; + if (ReferenceEquals(m_values[index].Value, value)) + return new SerializationContext(m_values); + var updated = (KeyValuePair[])m_values.Clone(); + updated[index] = new(key, value); + return new SerializationContext(updated); + } + var expanded = new KeyValuePair[m_values.Length + 1]; + m_values.CopyTo(expanded, 0); + expanded[^1] = new(key, value); + return new SerializationContext(expanded); } /// @@ -59,10 +69,13 @@ public SerializationContext With(TContext value) where TContext : clas /// public bool TryGet(out TContext? value) where TContext : class { - if (m_values.TryGetValue(typeof(TContext), out object? candidate) && candidate is TContext typed) + foreach (KeyValuePair pair in m_values) { - value = typed; - return true; + if (pair.Key == typeof(TContext) && pair.Value is TContext typed) + { + value = typed; + return true; + } } value = null; diff --git a/src/foundation/core/Inno.Core.Serialization/Converters/SerializationConverter.cs b/src/foundation/core/Inno.Core.Serialization/Converters/SerializationConverter.cs index 6f8ac743f..44468ae38 100644 --- a/src/foundation/core/Inno.Core.Serialization/Converters/SerializationConverter.cs +++ b/src/foundation/core/Inno.Core.Serialization/Converters/SerializationConverter.cs @@ -62,5 +62,31 @@ T target => throw new NotSupportedException( $"Serialization converter '{GetType().FullName}' does not support restoring existing '{typeof(T).FullName}' values."); + /// + /// Optionally declares every input to a snapshot capture without writing data. + /// + /// + /// The current registered identity source. + /// + /// + /// The borrowed Core key builder, valid only during this synchronous callback. + /// + /// + /// True only when the complete output is covered; false retains ordinary authoritative capture. + /// + /// + /// This callback must be pure and complete. It runs only for CaptureSnapshot; ordinary Serialize never uses it. + /// After a changed key, Core verifies the key again before publishing. Default converters do not opt in. + /// + protected virtual bool TryCaptureKey( + T value, + SerializationCaptureKeyBuilder key + ) => false; + + internal bool CaptureKey( + T value, + SerializationCaptureKeyBuilder key + ) => TryCaptureKey(value, key); + internal sealed override ConverterInvoker CreateInvoker() => new ConverterInvoker(this, GetType()); } diff --git a/src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs b/src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs index c0f00bbd9..d2c427c8e 100644 --- a/src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs +++ b/src/foundation/core/Inno.Core.Serialization/IO/SerializationWriter.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Inno.Extensibility.Types; +using Inno.Scripting.Api; namespace Inno.Core.Serialization; @@ -9,16 +11,25 @@ namespace Inno.Core.Serialization; public sealed class SerializationWriter { private readonly SerializationOperation m_operation; - private readonly ObjectSerializationNode m_node; + private readonly ObjectSerializationNode? m_previous; + private readonly int m_operationGeneration; + private ObjectSerializationNode? m_node; + private HashSet? m_writtenNames; + private ulong m_writtenMask; + private int m_writtenCount; + private bool m_completed; internal SerializationWriter( SerializationOperation operation, - ObjectSerializationNode node, + ObjectSerializationNode? previous, string path, Type valueType ) { m_operation = operation; - m_node = node; + m_operationGeneration = operation.writerGeneration; + m_previous = previous; + if (previous is null) + m_node = new ObjectSerializationNode(); this.path = path; this.valueType = valueType; } @@ -28,6 +39,26 @@ Type valueType /// public SerializationContext context => m_operation.context; + /// + /// Borrows the immutable type snapshot pinned by this operation's converter generation. + /// + /// + /// Use this snapshot for consistent type identities during a complete capture. It must not be retained beyond + /// the writer's operation; keeping it would retain its collectible types after the converter lease is released. + /// + /// + /// The writer no longer belongs to an active capture. + /// + [ScriptingApiIgnore] + public TypeCacheSnapshot typeSnapshot + { + get + { + EnsureActive(); + return m_operation.types; + } + } + /// /// Gets the current diagnostic path. /// @@ -59,7 +90,22 @@ Type valueType public void Write( string name, TValue value - ) => Write(name, value, typeof(TValue)); + ) { + ValidateName(name); + EnsureActive(); + if (m_previous is not null && m_previous.values.Count <= 64 && m_writtenCount < m_previous.values.Count) + { + KeyValuePair entry = m_previous.values.GetAt(m_writtenCount); + if (string.Equals(entry.Key, name, StringComparison.Ordinal) && entry.Value is ScalarSerializationNode scalar && + scalar.value.GetType() == typeof(TValue) && m_operation.ResolveConverter(typeof(TValue)) is null && + SerializationNodeReuse.SameScalar(value, scalar.value)) + { + MarkWritten(name, m_writtenCount); + return; + } + } + Write(name, value, typeof(TValue)); + } /// /// Writes a value using its declared metadata type without constructing a generic method at runtime. @@ -89,7 +135,10 @@ Type declaredType ) { ArgumentNullException.ThrowIfNull(declaredType); ValidateName(name); - AddNode(name, ValuePipeline.Write(value, declaredType, m_operation, AppendPath(name), allowDefaultObject: false)); + EnsureActive(); + SerializationNode? previous = Previous(name); + AddNode(name, ValuePipeline.Write(value, declaredType, m_operation, AppendPath(name), + allowDefaultObject: false, previous)); } /// @@ -110,9 +159,12 @@ Action write ) { ArgumentNullException.ThrowIfNull(write); ValidateName(name); - var child = new ObjectSerializationNode(); - AddNode(name, child); - write(new SerializationWriter(m_operation, child, AppendPath(name), typeof(object))); + EnsureActive(); + SerializationNode? previous = Previous(name); + var child = new SerializationWriter(m_operation, previous as ObjectSerializationNode, AppendPath(name), typeof(object)); + AddNode(name, previous ?? child.m_node!); + write(child); + ReplaceNode(name, child.Complete()); } /// @@ -130,28 +182,77 @@ Action write /// /// The callback that fills each element object. /// + /// + /// An optional pure callback covering every element output and contextual input. It must declare + /// a current Core identity owner and return true only for a complete proof. Snapshot capture alone + /// can reuse a matching element; ordinary serialization always executes writeElement. + /// + /// + /// The key builder belongs only to its synchronous callback. Uncertified rows and rows that invoke + /// nested converters execute the ordinary element writer on every capture. Callback failures and + /// changed declared inputs abort publication without modifying a previous snapshot. + /// /// - /// Thrown when an argument is null. + /// Values or writeElement is null. + /// + /// + /// The member name is null, empty or whitespace. + /// + /// + /// The writer has retired, a member is duplicated, or declared capture inputs change during writing. /// public void WriteObjectArray( string name, IEnumerable values, - Action writeElement + Action writeElement, + Func? captureKey = null ) { ArgumentNullException.ThrowIfNull(values); ArgumentNullException.ThrowIfNull(writeElement); ValidateName(name); - var array = new ArraySerializationNode(); - AddNode(name, array); + EnsureActive(); + SerializationNode? previous = Previous(name); + var array = new SerializationArrayCapture(previous as ArraySerializationNode); + AddNode(name, previous ?? array.initialNode!); int index = 0; - foreach (TValue value in values) + if (values is TValue[] items) { - var child = new ObjectSerializationNode(); - array.values.Add(child); - writeElement( - new SerializationWriter(m_operation, child, $"{AppendPath(name)}[{index}]", typeof(TValue)), - value); + for (int itemIndex = 0; itemIndex < items.Length; itemIndex++) + WriteElement(items[itemIndex]); + } + else + foreach (TValue value in values) + WriteElement(value); + ReplaceNode(name, array.Complete()); + + void WriteElement(TValue value) + { + ObjectSerializationNode? prior = array.Next as ObjectSerializationNode; + SerializationCaptureAttempt attempt = captureKey is null ? SerializationCaptureAttempt.empty + : SerializationCaptureAttempt.Capture(m_operation, typeof(TValue), value, prior?.captureKey, captureKey: captureKey); + SerializationCaptureKey? key = attempt.key; + if (key is not null && ReferenceEquals(key, prior?.captureKey)) + { + array.Add(prior!); + index++; + return; + } + var child = new SerializationWriter(m_operation, prior, + array.Next?.capturePath ?? $"{AppendPath(name)}[{index}]", typeof(TValue)); + long converterWrites = m_operation.converterWrites; + writeElement(child, value); + ObjectSerializationNode result = child.Complete(); + if (key is not null) + { + attempt.ValidateIdentities(); + key = attempt.Verify(SerializationCaptureAttempt.Capture(m_operation, typeof(TValue), value, key, captureKey: captureKey)); + if (m_operation.converterWrites != converterWrites) + key = null; + } + if (ReferenceEquals(result, prior) && prior!.captureKey is null) + key = null; + array.Add(SerializationNodeReuse.WithCaptureKey(result, prior, key)); index++; } } @@ -168,11 +269,11 @@ Action writeElement public void WriteProperties(ISerializable value) { ArgumentNullException.ThrowIfNull(value); - m_operation.EnsureActive(); + EnsureActive(); m_operation.EnterCapture(value, path); try { - ValuePipeline.WriteProperties(value, m_node, m_operation, path); + ValuePipeline.WriteProperties(value, this, m_operation); } finally { @@ -180,16 +281,142 @@ public void WriteProperties(ISerializable value) } } + /// + /// Writes independently restorable property bytes through the current converter generation. + /// + /// + /// The unique member receiving the binary property snapshot. + /// + /// + /// The object whose persistent properties are captured once. + /// + /// + /// The complete owner context, or null to use this writer's context. + /// + /// + /// The source object is null. + /// + /// + /// The writer has ended, a key is duplicated, or capture fails. + /// + /// + /// The member name is empty. + /// + public void WritePropertiesData( + string name, + ISerializable value, + SerializationContext? context = null + ) { + ArgumentNullException.ThrowIfNull(value); + ValidateName(name); + EnsureActive(); + AddNode(name, m_operation.CapturePropertyData(value, context ?? this.context, Previous(name) as BinarySerializationNode)); + } + + internal ObjectSerializationNode Complete() + { + EnsureActive(); + m_completed = true; + if (m_previous is not null && (m_node is not null || m_writtenCount != m_previous.values.Count)) + { + EnsureMutable(); + for (int index = 0; index < m_previous.values.Count; index++) + { + string name = m_previous.values.GetAt(index).Key; + if (m_previous.values.Count <= 64 ? (m_writtenMask & (1UL << index)) == 0 : m_writtenNames?.Contains(name) != true) + m_node!.values.Remove(name); + } + } + ObjectSerializationNode result = m_node ?? m_previous ?? new ObjectSerializationNode(); + result.RememberPath(path); + return result; + } + + private SerializationNode? Previous(string name) + { + if (m_previous is not null && m_writtenCount < m_previous.values.Count && + string.Equals(m_previous.values.GetAt(m_writtenCount).Key, name, StringComparison.Ordinal)) + return m_previous.values.GetAt(m_writtenCount).Value; + return m_previous is not null && m_previous.values.TryGetValue(name, out SerializationNode? value) ? value : null; + } + private void AddNode( string name, SerializationNode node ) { - m_operation.EnsureActive(); - if (!m_node.values.TryAdd(name, node)) + EnsureActive(); + if (m_previous is null) + { + m_node ??= new ObjectSerializationNode(); + if (!m_node.values.TryAdd(name, node)) + throw new InvalidOperationException($"Serialization object '{path}' already contains key '{name}'."); + return; + } + int index = -1; + if (m_previous.values.Count <= 64) + { + if (m_writtenCount < m_previous.values.Count && + string.Equals(m_previous.values.GetAt(m_writtenCount).Key, name, StringComparison.Ordinal)) + index = m_writtenCount; + else + index = m_previous.values.IndexOf(name); + } + SerializationNode? previous = index >= 0 ? m_previous.values.GetAt(index).Value : Previous(name); + MarkWritten(name, index); + if (!ReferenceEquals(previous, node)) + { + EnsureMutable(); + m_node!.values[name] = node; + } + } + + private void MarkWritten( + string name, + int previousIndex + ) { + bool duplicate; + if (previousIndex >= 0) + { + ulong bit = 1UL << previousIndex; + duplicate = (m_writtenMask & bit) != 0; + m_writtenMask |= bit; + } + else + duplicate = !(m_writtenNames ??= new(StringComparer.Ordinal)).Add(name); + if (duplicate) throw new InvalidOperationException($"Serialization object '{path}' already contains key '{name}'."); + m_writtenCount++; + } + + private void EnsureMutable() + { + if (m_node is not null) + return; + m_node = m_previous is null ? new ObjectSerializationNode() : SerializationNodeReuse.CopyObject(m_previous); + } + + private void ReplaceNode( + string name, + SerializationNode value + ) { + EnsureActive(); + if (!ReferenceEquals(Previous(name), value)) + { + EnsureMutable(); + m_node!.values[name] = value; + } + } + + private void EnsureActive() + { + m_operation.EnsureActive(); + if (m_operationGeneration != m_operation.writerGeneration) + throw new InvalidOperationException("The serialization writer belongs to a completed property capture."); + if (m_completed && m_operation.isSnapshotCapture) + throw new InvalidOperationException("The serialization writer callback has already completed."); } - private string AppendPath(string name) => path == "$" ? $"$.{name}" : $"{path}.{name}"; + private string AppendPath(string name) => Previous(name)?.capturePath ?? (path == "$" ? $"$.{name}" : $"{path}.{name}"); private static void ValidateName(string name) { diff --git a/src/foundation/core/Inno.Core.Serialization/Inno.Core.Serialization.csproj b/src/foundation/core/Inno.Core.Serialization/Inno.Core.Serialization.csproj index c2904bb41..4274294bc 100644 --- a/src/foundation/core/Inno.Core.Serialization/Inno.Core.Serialization.csproj +++ b/src/foundation/core/Inno.Core.Serialization/Inno.Core.Serialization.csproj @@ -5,7 +5,7 @@ disable enable true - $(WarningsAsErrors);CS1591 + $(WarningsAsErrors);CS1572;CS1573;CS1591 @@ -16,6 +16,7 @@ + diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Capture.cs b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Capture.cs new file mode 100644 index 000000000..5268a03c6 --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Capture.cs @@ -0,0 +1,35 @@ +using System; + +namespace Inno.Core.Serialization; + +internal static partial class BinarySerializationFormat +{ + private const int C_MAX_CAPTURE_BLOCK_BYTES = 64 * 1024; + + private static byte[]? TryGetEncodedCapture(ObjectSerializationNode node) + { + if (node.encodedCapture is { } encoded) + return encoded; + if (node.captureKey is null || node.encodingTooLarge) + return null; + int length; + try + { + length = GetEncodedSize(node); + } + catch (OverflowException) + { + // An optional bounded memo must not impose ToArray's contiguous-size limit on streaming. + node.RejectEncodedCapture(); + return null; + } + if (length > C_MAX_CAPTURE_BLOCK_BYTES) + { + node.RejectEncodedCapture(); + return null; + } + // This is the same canonical encoder. Bypass only this root to avoid recursive memo creation; + // independently frozen child captures can still share their already encoded blocks. + return node.PublishEncodedCapture(Encode(node, includeMagic: false, useObjectCache: false)); + } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Sizing.cs b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Sizing.cs new file mode 100644 index 000000000..668d72703 --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.Sizing.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Text; + +namespace Inno.Core.Serialization; + +internal static partial class BinarySerializationFormat +{ + private static int GetEncodedSize(SerializationNode node) + { + if (node is ObjectSerializationNode { encodedCapture: { } encoded }) + return encoded.Length; + int payload = node switch + { + NullSerializationNode => 0, + ScalarSerializationNode scalar => GetScalarSize(scalar.value), + BinarySerializationNode binary => checked(sizeof(int) + binary.value.Length), + ObjectSerializationNode value => GetObjectSize(value), + ArraySerializationNode value => GetArraySize(value), + MapSerializationNode value => GetMapSize(value), + _ => throw new InvalidDataException($"Unsupported serialization node '{node.GetType().FullName}'.") + }; + return checked(sizeof(byte) + payload); + } + + private static int GetScalarSize(object value) => value switch + { + bool or byte or sbyte => 1, + short or ushort => 2, + int or uint or float => 4, + long or ulong or double => 8, + decimal or Guid => 16, + string text => GetEncodedStringSize(text), + _ => throw new InvalidDataException($"Unsupported scalar runtime type '{value.GetType().FullName}'.") + }; + + private static int GetEncodedStringSize(string value) + { + int bytes = Encoding.UTF8.GetByteCount(value); + int prefix = 1 + BitOperations.Log2((uint)bytes) / 7; + return checked(prefix + bytes); + } + + private static int GetObjectSize(ObjectSerializationNode node) + { + int size = sizeof(int); + foreach (KeyValuePair member in node.values) + size = checked(size + GetEncodedStringSize(member.Key) + GetEncodedSize(member.Value)); + return size; + } + + private static int GetArraySize(ArraySerializationNode node) + { + int size = sizeof(int); + foreach (SerializationNode value in node.values) + size = checked(size + GetEncodedSize(value)); + return size; + } + + private static int GetMapSize(MapSerializationNode node) + { + int size = sizeof(int); + foreach (KeyValuePair entry in node.values) + size = checked(size + GetEncodedSize(entry.Key) + GetEncodedSize(entry.Value)); + return size; + } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.cs b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.cs index 75365ad94..be6a56795 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/BinarySerializationFormat.cs @@ -1,24 +1,26 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; namespace Inno.Core.Serialization; -internal static class BinarySerializationFormat +internal static partial class BinarySerializationFormat { private const string C_MAGIC = "INNO-BINARY-CURRENT"; private const int C_MAX_COLLECTION_COUNT = 16_777_216; internal static byte[] Encode(SerializationNode root) - { - using var stream = new MemoryStream(16 * 1024); - using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + => Encode(root, includeMagic: true); + + internal static void WriteTo( + Stream destination, + SerializationNode root + ) { + using var writer = new BinaryWriter(destination, Encoding.UTF8, leaveOpen: true); writer.Write(C_MAGIC); WriteNode(writer, root); writer.Flush(); - return stream.ToArray(); } internal static SerializationNode Decode(ReadOnlySpan bytes) @@ -48,7 +50,8 @@ internal static SerializationNode Decode(ReadOnlySpan bytes) private static void WriteNode( BinaryWriter writer, - SerializationNode node + SerializationNode node, + bool useObjectCache = true ) { switch (node) { @@ -64,8 +67,14 @@ SerializationNode node writer.Write(binary.value); return; case ObjectSerializationNode objectNode: + if (useObjectCache && TryGetEncodedCapture(objectNode) is { } encoded) + { + writer.Write(encoded.AsSpan()); + return; + } writer.Write((byte)NodeKind.Object); - string[] names = [.. objectNode.values.Keys.OrderBy(static name => name, StringComparer.Ordinal)]; + string[] names = [.. objectNode.values.Keys]; + Array.Sort(names, StringComparer.Ordinal); writer.Write(names.Length); for (int i = 0; i < names.Length; i++) { @@ -147,7 +156,12 @@ object value case double typed: writer.Write((byte)NodeKind.Double); writer.Write(typed); return; case decimal typed: writer.Write((byte)NodeKind.Decimal); writer.Write(typed); return; case string typed: writer.Write((byte)NodeKind.String); writer.Write(typed); return; - case Guid typed: writer.Write((byte)NodeKind.Guid); writer.Write(typed.ToByteArray()); return; + case Guid typed: + Span bytes = stackalloc byte[16]; + _ = typed.TryWriteBytes(bytes); + writer.Write((byte)NodeKind.Guid); + writer.Write(bytes); + return; default: throw new InvalidDataException($"Unsupported scalar runtime type '{value.GetType().FullName}'."); } @@ -235,12 +249,24 @@ string name ) => path == "$" ? $"$.{name}" : $"{path}.{name}"; private static byte[] EncodeNode(SerializationNode node) - { - using var stream = new MemoryStream(); + => Encode(node, includeMagic: false); + + private static byte[] Encode( + SerializationNode node, + bool includeMagic, + bool useObjectCache = true + ) { + int length = checked(GetEncodedSize(node) + (includeMagic ? GetEncodedStringSize(C_MAGIC) : 0)); + var data = new byte[length]; + using var stream = new MemoryStream(data, writable: true); using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); - WriteNode(writer, node); + if (includeMagic) + writer.Write(C_MAGIC); + WriteNode(writer, node, useObjectCache); writer.Flush(); - return stream.ToArray(); + if (stream.Position != length) + throw new InvalidDataException("The serialization encoder did not fill its calculated output size."); + return data; } private static int CompareBytes( diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/ConverterRegistry.cs b/src/foundation/core/Inno.Core.Serialization/Internal/ConverterRegistry.cs index f05e9fafe..1a8346ca3 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/ConverterRegistry.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/ConverterRegistry.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -242,19 +243,6 @@ internal ConverterRegistryLease Capture() } } - internal ConverterInvoker? Resolve(Type valueType) - { - ConverterRegistrySnapshot snapshot = current; - lock (snapshot.sync) - { - if (snapshot.cache.TryGetValue(valueType, out ConverterInvoker? cached)) - return cached; - ConverterInvoker? resolved = ResolveSnapshot(snapshot, valueType); - snapshot.cache.Add(valueType, resolved); - return resolved; - } - } - /// /// Builds a validated result from the current immutable input snapshot. /// @@ -303,12 +291,15 @@ TypeCacheSnapshot types ) { private TypeCacheSnapshot? m_types = types; - internal readonly object sync = new(); - internal readonly Dictionary cache = []; + internal readonly Guid captureIdentity = Guid.NewGuid(); + internal readonly Lock sync = new(); + internal readonly ConcurrentDictionary cache = new(); internal readonly Dictionary converterInstances = []; internal IReadOnlyList registrations = registrations; private int m_referenceCount = 1; + internal TypeCacheSnapshot types => m_types ?? throw new ObjectDisposedException(nameof(ConverterRegistrySnapshot)); + internal object CreateInstance(Type type) => (m_types ?? throw new ObjectDisposedException(nameof(ConverterRegistrySnapshot))).CreateInstance(type); @@ -372,6 +363,7 @@ internal void Release() internal sealed class ConverterRegistryLease : IDisposable { + private ConcurrentDictionary? m_revisionPlans; private ConverterRegistry.ConverterRegistrySnapshot? m_snapshot; internal ConverterRegistryLease(ConverterRegistry.ConverterRegistrySnapshot snapshot) @@ -379,18 +371,69 @@ internal ConverterRegistryLease(ConverterRegistry.ConverterRegistrySnapshot snap m_snapshot = snapshot; } + internal TypeCacheSnapshot types => (m_snapshot ?? throw new ObjectDisposedException(nameof(ConverterRegistryLease))).types; + internal Guid captureIdentity => (m_snapshot ?? throw new ObjectDisposedException(nameof(ConverterRegistryLease))).captureIdentity; + + internal bool CanReuseRevision( + SerializationTypeMetadata metadata, + ISerializationMetadataSource source + ) { + var plans = Volatile.Read(ref m_revisionPlans); + if (plans is null) + { + var created = new ConcurrentDictionary(); + plans = Interlocked.CompareExchange(ref m_revisionPlans, created, null) ?? created; + } + if (plans.TryGetValue(metadata, out var known) && ReferenceEquals(known.source, source)) + return known.eligible; + bool eligible = !metadata.requiresConverter; + foreach (SerializationMemberMetadata member in metadata.members) + { + if (!eligible) + break; + if ((member.visibility & PropertyVisibility.Serialize) == 0) + continue; + if (!IsKnownDefault(member.type)) + { + eligible = false; + break; + } + if (ValuePipeline.IsPrimitive(member.type) || member.type.IsEnum) + continue; + SerializationTypeMetadata nested = source.GetMetadata(member.type); + if (!nested.hasPureFieldAccess) + { + eligible = false; + break; + } + foreach (SerializationMemberMetadata field in nested.members) + if ((field.visibility & PropertyVisibility.Serialize) != 0 && !IsKnownDefault(field.type)) + { + eligible = false; + break; + } + } + // An unresolved fact can become known through an ordinary write in this same operation. + // Cache only positive proofs; a key query still never invokes the resolver itself. + if (eligible) + plans[metadata] = (source, true); + return eligible; + } internal ConverterInvoker? Resolve(Type valueType) { ArgumentNullException.ThrowIfNull(valueType); ConverterRegistry.ConverterRegistrySnapshot snapshot = m_snapshot ?? throw new ObjectDisposedException(nameof(ConverterRegistryLease)); Type normalizedType = Nullable.GetUnderlyingType(valueType) ?? valueType; + if (snapshot.cache.TryGetValue(normalizedType, out ConverterInvoker? cached)) + return cached; lock (snapshot.sync) { - if (snapshot.cache.TryGetValue(normalizedType, out ConverterInvoker? cached)) + if (snapshot.cache.TryGetValue(normalizedType, out cached)) return cached; ConverterInvoker? resolved = ConverterRegistry.ResolveSnapshot(snapshot, normalizedType); - snapshot.cache.Add(normalizedType, resolved); + if (!snapshot.cache.TryAdd(normalizedType, resolved)) + throw new InvalidOperationException("The serialization converter resolution was published twice."); return resolved; } } @@ -401,9 +444,19 @@ internal ConverterRegistryLease(ConverterRegistry.ConverterRegistrySnapshot snap public void Dispose() { ConverterRegistry.ConverterRegistrySnapshot? snapshot = Interlocked.Exchange(ref m_snapshot, null); + m_revisionPlans?.Clear(); snapshot?.Release(); } + private bool IsKnownDefault(Type type) + { + ConverterRegistry.ConverterRegistrySnapshot snapshot = m_snapshot + ?? throw new ObjectDisposedException(nameof(ConverterRegistryLease)); + Type normalized = Nullable.GetUnderlyingType(type) ?? type; + // Reuse only facts established by the authoritative resolver. A key query must not + // construct converters, change their execution order, or introduce another resolver. + return snapshot.cache.TryGetValue(normalized, out ConverterInvoker? converter) && converter is null; + } } internal abstract class ConverterInvoker @@ -433,7 +486,8 @@ internal abstract SerializationNode Write( SerializationOperation operation, string path, Type valueType, - object value + object value, + SerializationNode? previous ); internal abstract object Read( @@ -469,7 +523,8 @@ internal override SerializationNode Write( SerializationOperation operation, string path, Type valueType, - object value + object value, + SerializationNode? previous ) { if (value is not T typed) { @@ -477,9 +532,24 @@ object value $"Converter '{converterType.FullName}' cannot write runtime value '{value.GetType().FullName}' at '{path}'."); } - var node = new ObjectSerializationNode(); - m_converter.Write(new SerializationWriter(operation, node, path, valueType), typed); - return node; + ObjectSerializationNode? prior = previous as ObjectSerializationNode; + operation.RecordConverterWrite(); + long converterWrites = operation.converterWrites; + SerializationCaptureAttempt attempt = SerializationCaptureAttempt.Capture(operation, valueType, typed, prior?.captureKey, m_converter); + SerializationCaptureKey? key = attempt.key; + if (key is not null && ReferenceEquals(key, prior?.captureKey)) + return prior!; + var writer = new SerializationWriter(operation, prior, path, valueType); + m_converter.Write(writer, typed); + ObjectSerializationNode result = writer.Complete(); + if (key is not null) + { + attempt.ValidateIdentities(); + key = attempt.Verify(SerializationCaptureAttempt.Capture(operation, valueType, typed, key, m_converter)); + if (operation.converterWrites != converterWrites) + key = null; + } + return SerializationNodeReuse.WithCaptureKey(result, prior, key); } internal override object Read( diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/PropertyDataCapture.cs b/src/foundation/core/Inno.Core.Serialization/Internal/PropertyDataCapture.cs new file mode 100644 index 000000000..86673dbbe --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Internal/PropertyDataCapture.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Inno.Core.Serialization; + +internal sealed class PropertyDataCapture(KeyValuePair[] members) +{ + internal KeyValuePair[] members { get; } = members; + internal bool hasConverters { get; init; } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotBinaryFormat.cs b/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotBinaryFormat.cs index 6ac0b36b4..e35ad6fa5 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotBinaryFormat.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotBinaryFormat.cs @@ -9,6 +9,23 @@ internal static class PropertySnapshotBinaryFormat { private const string C_MAGIC = "INNO-PROPERTY-SNAPSHOT-CURRENT"; + internal static byte[] Encode(IReadOnlyList> members) + { + using var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write(C_MAGIC); + writer.Write(members.Count); + foreach (var member in members) + { + byte[] data = BinarySerializationFormat.Encode(member.Value); + writer.Write(member.Key); + writer.Write(data.Length); + writer.Write(data); + } + writer.Flush(); + return stream.ToArray(); + } + internal static byte[] Encode(IReadOnlyList snapshots) { using var stream = new MemoryStream(); diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotPipeline.cs b/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotPipeline.cs index 727716322..93572b9cb 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotPipeline.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/PropertySnapshotPipeline.cs @@ -1,11 +1,63 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Inno.Core.Serialization; internal static class PropertySnapshotPipeline { + internal static BinarySerializationNode CaptureData( + ISerializable value, + SerializationContext context, + ConverterRegistryLease converters, + BinarySerializationNode? previous + ) { + IReadOnlyList members = context.GetRequired().GetMetadata(value.GetType()).members; + KeyValuePair[]? before = previous?.properties?.members; + List>? captured = before is null ? [] : null; + int count = 0; + var operation = new SerializationOperation(context, converters, snapshotCapture: previous is not null); + try + { + for (int index = 0; index < members.Count; index++) + { + SerializationMemberMetadata member = members[index]; + if ((member.visibility & PropertyVisibility.Serialize) == 0) + continue; + SerializationNode? prior = before is not null && count < before.Length && + string.Equals(before[count].Key, member.name, StringComparison.Ordinal) + ? before[count].Value + : null; + SerializationNode node = ValuePipeline.Write(member.GetValue(value), member.type, operation, + prior?.capturePath ?? AppendPath(member.name), allowDefaultObject: false, prior); + operation.RetireCaptureMember(); + if (captured is null && !ReferenceEquals(node, prior)) + { + captured = new(before!); + captured.RemoveRange(count, captured.Count - count); + } + captured?.Add(new(member.name, node)); + count++; + } + } + finally + { + operation.Fail(); + } + if (captured is null && before is not null && count == before.Length) + return previous!.properties!.hasConverters == (operation.converterWrites != 0) ? previous + : new BinarySerializationNode(previous.value) + { + properties = new(before) { hasConverters = operation.converterWrites != 0 } + }; + if (captured is null) + { + captured = new(before!); + captured.RemoveRange(count, captured.Count - count); + } + var state = new PropertyDataCapture([.. captured]) { hasConverters = operation.converterWrites != 0 }; + return new BinarySerializationNode(PropertySnapshotBinaryFormat.Encode(state.members)) { properties = state }; + } + internal static SerializationPropertySnapshot CaptureProperty( ISerializable value, string propertyName, @@ -13,11 +65,17 @@ internal static SerializationPropertySnapshot CaptureProperty( ConverterRegistryLease converters ) { ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - return Capture(value, context, converters).FirstOrDefault(snapshot => - string.Equals(snapshot.name, propertyName, StringComparison.Ordinal)) - ?? throw new ArgumentException( - $"Serializable property '{propertyName}' was not found on '{value.GetType().FullName}'.", - nameof(propertyName)); + IReadOnlyList members = context.GetRequired().GetMetadata(value.GetType()).members; + for (int index = 0; index < members.Count; index++) + { + SerializationMemberMetadata member = members[index]; + if ((member.visibility & PropertyVisibility.Serialize) != 0 && + string.Equals(member.name, propertyName, StringComparison.Ordinal)) + return CaptureMember(value, member, context, converters); + } + throw new ArgumentException( + $"Serializable property '{propertyName}' was not found on '{value.GetType().FullName}'.", + nameof(propertyName)); } internal static IReadOnlyList Capture( @@ -33,24 +91,7 @@ ConverterRegistryLease converters if ((member.visibility & PropertyVisibility.Serialize) == 0) continue; - var operation = new SerializationOperation(context, converters); - try - { - SerializationNode node = ValuePipeline.Write( - member.GetValue(value), - member.type, - operation, - AppendPath(member.name), - allowDefaultObject: false); - snapshots.Add(new SerializationPropertySnapshot( - member.name, - member.type, - BinarySerializationFormat.Encode(node))); - } - finally - { - operation.Fail(); - } + snapshots.Add(CaptureMember(value, member, context, converters)); } return snapshots; } @@ -140,6 +181,32 @@ private static void ValidateSnapshots(IReadOnlyList exception is not OutOfMemoryException and not StackOverflowException and diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationArrayCapture.cs b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationArrayCapture.cs new file mode 100644 index 000000000..327c1118a --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationArrayCapture.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace Inno.Core.Serialization; + +internal struct SerializationArrayCapture(ArraySerializationNode? previous) +{ + private readonly ArraySerializationNode? m_initialNode = previous is null ? new ArraySerializationNode() : null; + private List? m_values; + private int m_count; + + internal SerializationNode? Next => previous is not null && m_count < previous.values.Count + ? previous.values[m_count] + : null; + + internal ArraySerializationNode? initialNode => m_initialNode; + + internal void Add(SerializationNode value) + { + if (m_initialNode is not null) + m_values = m_initialNode.values; + if (m_values is null && !ReferenceEquals(Next, value)) + m_values = previous is null ? [] : new(previous.values); + if (m_values is not null) + { + if (m_count < m_values.Count) + m_values[m_count] = value; + else + m_values.Add(value); + } + m_count++; + } + + internal ArraySerializationNode Complete() + { + if (m_initialNode is not null) + return m_initialNode; + if (previous is not null && m_count == previous.values.Count && m_values is null) + return previous; + m_values ??= previous is null ? [] : new(previous.values); + if (m_values.Count > m_count) + m_values.RemoveRange(m_count, m_values.Count - m_count); + return new ArraySerializationNode(m_values); + } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNode.cs b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNode.cs index 6a16f838f..236ccaa0f 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNode.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNode.cs @@ -1,9 +1,21 @@ using System; using System.Collections.Generic; +using System.Threading; namespace Inno.Core.Serialization; -internal abstract record SerializationNode; +internal abstract record SerializationNode +{ + private string? m_capturePath; + + internal string? capturePath => Volatile.Read(ref m_capturePath); + + internal void RememberPath(string path) + { + if (this is not NullSerializationNode) + Interlocked.CompareExchange(ref m_capturePath, path, null); + } +} internal sealed record NullSerializationNode : SerializationNode { @@ -16,16 +28,32 @@ private NullSerializationNode() internal sealed record ScalarSerializationNode(object value) : SerializationNode; -internal sealed record BinarySerializationNode(byte[] value) : SerializationNode; +internal sealed record BinarySerializationNode(byte[] value) : SerializationNode +{ + internal PropertyDataCapture? properties { get; init; } +} internal sealed record ObjectSerializationNode : SerializationNode { - internal Dictionary values { get; } = new(StringComparer.Ordinal); + private byte[]? m_encodedCapture; + private int m_encodingTooLarge; + + internal SerializationCaptureKey? captureKey { get; set; } + internal OrderedDictionary values { get; } = new(StringComparer.Ordinal); + internal byte[]? encodedCapture => Volatile.Read(ref m_encodedCapture); + internal bool encodingTooLarge => Volatile.Read(ref m_encodingTooLarge) != 0; + + internal byte[] PublishEncodedCapture(byte[] data) => Interlocked.CompareExchange(ref m_encodedCapture, data, null) ?? data; + internal void RejectEncodedCapture() => Volatile.Write(ref m_encodingTooLarge, 1); } internal sealed record ArraySerializationNode : SerializationNode { - internal List values { get; } = []; + internal ArraySerializationNode() : this([]) { } + + internal ArraySerializationNode(List values) => this.values = values; + + internal List values { get; } } internal sealed record MapSerializationNode : SerializationNode diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNodeReuse.cs b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNodeReuse.cs new file mode 100644 index 000000000..098fdd46d --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationNodeReuse.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Inno.Core.Serialization; + +internal static class SerializationNodeReuse +{ + internal static ObjectSerializationNode WithCaptureKey( + ObjectSerializationNode result, + ObjectSerializationNode? previous, + SerializationCaptureKey? key + ) { + if (!ReferenceEquals(result.captureKey, key)) + { + if (ReferenceEquals(result, previous)) + result = CopyObject(result); + result.captureKey = key; + } + return result; + } + + internal static bool SameScalar( + T value, + object previous + ) { + if (typeof(T) == typeof(float)) + return BitConverter.SingleToInt32Bits(Unsafe.As(ref value)) == BitConverter.SingleToInt32Bits((float)previous); + if (typeof(T) == typeof(double)) + return BitConverter.DoubleToInt64Bits(Unsafe.As(ref value)) == BitConverter.DoubleToInt64Bits((double)previous); + if (typeof(T) == typeof(decimal)) + return SameScalar((object)Unsafe.As(ref value), previous); + return EqualityComparer.Default.Equals(value, (T)previous); + } + + internal static ObjectSerializationNode CopyObject(ObjectSerializationNode previous) + { + var result = new ObjectSerializationNode(); + result.values.EnsureCapacity(previous.values.Count); + foreach (var pair in previous.values) + result.values.Add(pair.Key, pair.Value); + return result; + } + + internal static SerializationNode Scalar( + object value, + SerializationNode? previous + ) => previous is ScalarSerializationNode scalar && SameScalar(value, scalar.value) + ? scalar + : new ScalarSerializationNode(value); + + internal static SerializationNode Binary( + byte[] value, + SerializationNode? previous + ) => previous is BinarySerializationNode binary && value.AsSpan().SequenceEqual(binary.value) + ? binary + : new BinarySerializationNode((byte[])value.Clone()); + + internal static bool SameScalar( + object value, + object previous + ) { + if (value.GetType() != previous.GetType()) + return false; + if (value is float number) + return BitConverter.SingleToInt32Bits(number) == BitConverter.SingleToInt32Bits((float)previous); + if (value is double real) + return BitConverter.DoubleToInt64Bits(real) == BitConverter.DoubleToInt64Bits((double)previous); + if (value is decimal precision) + { + Span left = stackalloc int[4]; + Span right = stackalloc int[4]; + decimal.GetBits(precision, left); + decimal.GetBits((decimal)previous, right); + return left.SequenceEqual(right); + } + return value.Equals(previous); + } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationOperation.cs b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationOperation.cs index 75c9a1b97..83fbdfc1e 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/SerializationOperation.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/SerializationOperation.cs @@ -1,28 +1,47 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; +using Inno.Extensibility.Types; namespace Inno.Core.Serialization; internal sealed class SerializationOperation { private readonly ConverterRegistryLease m_converters; - private readonly List m_completionCallbacks = []; - private readonly HashSet m_scheduledObjects = new(ReferenceComparer.instance); - private readonly List m_scheduledObjectOrder = []; - private readonly Dictionary m_capturePaths = new(ReferenceComparer.instance); + private List? m_completionCallbacks; + private HashSet? m_scheduledObjects; + private List? m_scheduledObjectOrder; + private Dictionary? m_capturePaths; private bool m_isActive = true; + private int m_writerGeneration; + private long m_converterWrites; internal SerializationOperation( SerializationContext context, - ConverterRegistryLease converters + ConverterRegistryLease converters, + bool snapshotCapture = false ) { ArgumentNullException.ThrowIfNull(converters); this.context = context; m_converters = converters; + isSnapshotCapture = snapshotCapture; } + internal ConverterRegistryLease converters => m_converters; internal SerializationContext context { get; } + internal bool isSnapshotCapture { get; } + internal int writerGeneration => m_writerGeneration; + internal long converterWrites => m_converterWrites; + internal TypeCacheSnapshot types => m_converters.types; + + internal void RetireCaptureMember() + { + EnsureActive(); + m_writerGeneration = checked(m_writerGeneration + 1); + m_completionCallbacks?.Clear(); + m_scheduledObjects?.Clear(); + m_scheduledObjectOrder?.Clear(); + m_capturePaths?.Clear(); + } internal SerializationTypeMetadata GetMetadata(Type type) => context.GetRequired().GetMetadata(type); @@ -39,23 +58,37 @@ internal void EnsureActive() throw new InvalidOperationException("The serialization reader or writer is no longer active."); } + internal void RecordConverterWrite() => m_converterWrites = checked(m_converterWrites + 1); + + internal BinarySerializationNode CapturePropertyData( + ISerializable value, + SerializationContext context, + BinarySerializationNode? previous + ) { + EnsureActive(); + BinarySerializationNode captured = PropertySnapshotPipeline.CaptureData(value, context, m_converters, previous); + if (captured.properties?.hasConverters == true) + RecordConverterWrite(); + return captured; + } + internal void AddCompletionCallback(Action callback) { ArgumentNullException.ThrowIfNull(callback); EnsureActive(); - m_completionCallbacks.Add(callback); + (m_completionCallbacks ??= []).Add(callback); } internal void ScheduleRestoredObject(ISerializable value) { EnsureActive(); - if (!m_scheduledObjects.Add(value)) + if (!(m_scheduledObjects ??= new(ReferenceEqualityComparer.Instance)).Add(value)) return; - m_scheduledObjectOrder.Add(value); + (m_scheduledObjectOrder ??= []).Add(value); Action? callback = GetMetadata(value.GetType()).restored; if (callback is not null) - m_completionCallbacks.Add(() => callback(value, context)); + (m_completionCallbacks ??= []).Add(() => callback(value, context)); } internal void EnterCapture( @@ -63,49 +96,49 @@ internal void EnterCapture( string path ) { EnsureActive(); - if (m_capturePaths.TryGetValue(value, out string? existingPath)) + Dictionary captures = m_capturePaths ??= new(ReferenceEqualityComparer.Instance); + if (captures.TryGetValue(value, out string? existingPath)) { throw new InvalidOperationException( $"Serialization cycle detected at '{path}'. The same object is already being written at '{existingPath}'."); } - m_capturePaths.Add(value, path); + captures.Add(value, path); } internal Checkpoint CreateCheckpoint() { EnsureActive(); - return new Checkpoint(m_completionCallbacks.Count, m_scheduledObjectOrder.Count); + return new Checkpoint(m_completionCallbacks?.Count ?? 0, m_scheduledObjectOrder?.Count ?? 0); } internal void Rollback(Checkpoint checkpoint) { EnsureActive(); if (checkpoint.callbackCount < 0 || - checkpoint.callbackCount > m_completionCallbacks.Count || + checkpoint.callbackCount > (m_completionCallbacks?.Count ?? 0) || checkpoint.scheduledObjectCount < 0 || - checkpoint.scheduledObjectCount > m_scheduledObjectOrder.Count) + checkpoint.scheduledObjectCount > (m_scheduledObjectOrder?.Count ?? 0)) { throw new InvalidOperationException("The serialization operation checkpoint is invalid."); } - if (m_completionCallbacks.Count > checkpoint.callbackCount) + if (m_completionCallbacks is { } callbacks && callbacks.Count > checkpoint.callbackCount) + callbacks.RemoveRange(checkpoint.callbackCount, callbacks.Count - checkpoint.callbackCount); + if (m_scheduledObjectOrder is { } order) { - m_completionCallbacks.RemoveRange( - checkpoint.callbackCount, - m_completionCallbacks.Count - checkpoint.callbackCount); - } - for (int i = m_scheduledObjectOrder.Count - 1; i >= checkpoint.scheduledObjectCount; i--) - { - m_scheduledObjects.Remove(m_scheduledObjectOrder[i]); - m_scheduledObjectOrder.RemoveAt(i); + for (int i = order.Count - 1; i >= checkpoint.scheduledObjectCount; i--) + { + m_scheduledObjects!.Remove(order[i]); + order.RemoveAt(i); + } } } internal void ExitCapture(object value) { if (m_isActive) - m_capturePaths.Remove(value); + m_capturePaths?.Remove(value); } internal void Complete() @@ -113,16 +146,17 @@ internal void Complete() EnsureActive(); try { - for (int i = 0; i < m_completionCallbacks.Count; i++) - m_completionCallbacks[i](); + if (m_completionCallbacks is { } callbacks) + for (int i = 0; i < callbacks.Count; i++) + callbacks[i](); } finally { m_isActive = false; - m_completionCallbacks.Clear(); - m_scheduledObjects.Clear(); - m_scheduledObjectOrder.Clear(); - m_capturePaths.Clear(); + m_completionCallbacks?.Clear(); + m_scheduledObjects?.Clear(); + m_scheduledObjectOrder?.Clear(); + m_capturePaths?.Clear(); } } @@ -132,10 +166,10 @@ internal void Fail() return; m_isActive = false; - m_completionCallbacks.Clear(); - m_scheduledObjects.Clear(); - m_scheduledObjectOrder.Clear(); - m_capturePaths.Clear(); + m_completionCallbacks?.Clear(); + m_scheduledObjects?.Clear(); + m_scheduledObjectOrder?.Clear(); + m_capturePaths?.Clear(); } internal readonly record struct Checkpoint( @@ -143,36 +177,4 @@ internal readonly record struct Checkpoint( int scheduledObjectCount ); - private sealed class ReferenceComparer : IEqualityComparer - { - internal static ReferenceComparer instance { get; } = new(); - - /// - /// Determines whether this value and the supplied value represent the same logical state. - /// - /// - /// The horizontal or first component. - /// - /// - /// The vertical or second component. - /// - /// - /// when the requested condition is satisfied; otherwise, . - /// - public new bool Equals( - object? x, - object? y - ) => ReferenceEquals(x, y); - - /// - /// Computes a hash code consistent with the implemented equality contract. - /// - /// - /// The object compared with this value. - /// - /// - /// The scalar result calculated from the supplied inputs. - /// - public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); - } } diff --git a/src/foundation/core/Inno.Core.Serialization/Internal/ValuePipeline.cs b/src/foundation/core/Inno.Core.Serialization/Internal/ValuePipeline.cs index 98e1d0459..321ff7368 100644 --- a/src/foundation/core/Inno.Core.Serialization/Internal/ValuePipeline.cs +++ b/src/foundation/core/Inno.Core.Serialization/Internal/ValuePipeline.cs @@ -10,14 +10,15 @@ internal static class ValuePipeline internal static SerializationNode WriteRoot( ISerializable value, Type declaredType, - SerializationOperation operation + SerializationOperation operation, + SerializationNode? previous = null ) { operation.EnterCapture(value, "$"); try { ConverterInvoker? converter = operation.ResolveConverter(declaredType); if (converter is not null) - return converter.Write(operation, "$", declaredType, value); + return converter.Write(operation, "$", declaredType, value, previous); if (operation.GetMetadata(declaredType).requiresConverter) throw new InvalidOperationException( $"Serializable type '{declaredType.FullName}' requires an explicit serialization converter."); @@ -28,9 +29,9 @@ SerializationOperation operation $"Default root serialization requires exact type '{declaredType.FullName}', but the runtime value is '{value.GetType().FullName}'. Mark the root type with RequiresSerializationConverter and register a converter for polymorphism."); } EnsureDefaultSerializableType(declaredType, operation); - var node = new ObjectSerializationNode(); - WriteProperties(value, node, operation, "$"); - return node; + var writer = new SerializationWriter(operation, previous as ObjectSerializationNode, "$", declaredType); + WriteProperties(value, writer, operation); + return writer.Complete(); } finally { @@ -81,7 +82,26 @@ internal static SerializationNode Write( Type declaredType, SerializationOperation operation, string path, - bool allowDefaultObject + bool allowDefaultObject, + SerializationNode? previous = null + ) { + operation.EnsureActive(); + if (previous is ScalarSerializationNode scalar && value is not null && + scalar.value.GetType() == declaredType && value.GetType() == declaredType && + operation.ResolveConverter(declaredType) is null && SerializationNodeReuse.SameScalar(value, scalar.value)) + return scalar; + SerializationNode node = WriteValue(value, declaredType, operation, path, allowDefaultObject, previous); + node.RememberPath(path); + return node; + } + + private static SerializationNode WriteValue( + object? value, + Type declaredType, + SerializationOperation operation, + string path, + bool allowDefaultObject, + SerializationNode? previous = null ) { operation.EnsureActive(); Type? nullableType = Nullable.GetUnderlyingType(declaredType); @@ -100,22 +120,22 @@ bool allowDefaultObject { ConverterInvoker? converter = operation.ResolveConverter(valueType); if (converter is not null) - return converter.Write(operation, path, valueType, value); + return converter.Write(operation, path, valueType, value, previous); if (IsPrimitive(valueType)) - return new ScalarSerializationNode(NormalizePrimitive(value, valueType, path)); + return SerializationNodeReuse.Scalar(NormalizePrimitive(value, valueType, path), previous); if (valueType.IsEnum) - return new ScalarSerializationNode(Convert.ToInt64(value, CultureInfo.InvariantCulture)); + return SerializationNodeReuse.Scalar(Convert.ToInt64(value, CultureInfo.InvariantCulture), previous); if (valueType == typeof(byte[])) - return new BinarySerializationNode((byte[])((byte[])value).Clone()); + return SerializationNodeReuse.Binary((byte[])value, previous); if (valueType.IsArray) - return WriteArray((Array)value, valueType, operation, path); + return WriteArray((Array)value, valueType, operation, path, previous as ArraySerializationNode); if (operation.GetMetadata(valueType).collection is { keyType: not null } map) - return WriteMap(value, valueType, map.keyType, map.elementType, operation, path); + return WriteMap(value, valueType, map.keyType, map.elementType, operation, path, previous); if (operation.GetMetadata(valueType).collection is { keyType: null } sequence) - return WriteSequence(value, sequence.elementType, operation, path); + return WriteSequence(value, sequence.elementType, operation, path, previous as ArraySerializationNode); if (valueType.IsValueType) - return WriteStruct(value, valueType, operation, path); + return WriteStruct(value, valueType, operation, path, previous as ObjectSerializationNode); if (allowDefaultObject && value is ISerializable serializable) { @@ -125,9 +145,9 @@ bool allowDefaultObject $"Default serialization at '{path}' requires exact type '{valueType.FullName}', but the runtime value is '{value.GetType().FullName}'. Register a converter for polymorphic values."); } EnsureDefaultSerializableType(valueType, operation); - var node = new ObjectSerializationNode(); - WriteProperties(serializable, node, operation, path); - return node; + var writer = new SerializationWriter(operation, previous as ObjectSerializationNode, path, valueType); + WriteProperties(serializable, writer, operation); + return writer.Complete(); } throw new InvalidOperationException( @@ -192,9 +212,8 @@ bool allowDefaultObject internal static void WriteProperties( ISerializable value, - ObjectSerializationNode node, - SerializationOperation operation, - string path + SerializationWriter writer, + SerializationOperation operation ) { IReadOnlyList members = operation.GetMetadata(value.GetType()).members; for (int i = 0; i < members.Count; i++) @@ -202,15 +221,7 @@ string path SerializationMemberMetadata member = members[i]; if ((member.visibility & PropertyVisibility.Serialize) == 0) continue; - string memberPath = AppendPath(path, member.name); - SerializationNode memberNode = Write( - member.GetValue(value), - member.type, - operation, - memberPath, - allowDefaultObject: false); - if (!node.values.TryAdd(member.name, memberNode)) - throw new InvalidOperationException($"Serialization object '{path}' already contains key '{member.name}'."); + writer.Write(member.name, member.GetValue(value), member.type); } } @@ -242,15 +253,16 @@ private static SerializationNode WriteArray( Array value, Type arrayType, SerializationOperation operation, - string path + string path, + ArraySerializationNode? previous ) { if (arrayType.GetArrayRank() != 1) throw new InvalidOperationException($"Only one-dimensional arrays are supported at '{path}'."); Type elementType = arrayType.GetElementType()!; - var node = new ArraySerializationNode(); + var capture = new SerializationArrayCapture(previous); for (int i = 0; i < value.Length; i++) - node.values.Add(Write(value.GetValue(i), elementType, operation, $"{path}[{i}]", false)); - return node; + capture.Add(Write(value.GetValue(i), elementType, operation, capture.Next?.capturePath ?? $"{path}[{i}]", false, capture.Next)); + return capture.Complete(); } private static object ReadArray( @@ -274,18 +286,19 @@ private static SerializationNode WriteSequence( object value, Type elementType, SerializationOperation operation, - string path + string path, + ArraySerializationNode? previous ) { if (value is not IEnumerable enumerable) throw new InvalidOperationException($"Sequence value '{path}' is not enumerable."); - var node = new ArraySerializationNode(); + var capture = new SerializationArrayCapture(previous); int index = 0; foreach (object? element in enumerable) { - node.values.Add(Write(element, elementType, operation, $"{path}[{index}]", false)); + capture.Add(Write(element, elementType, operation, capture.Next?.capturePath ?? $"{path}[{index}]", false, capture.Next)); index++; } - return node; + return capture.Complete(); } private static object ReadSequence( @@ -309,7 +322,8 @@ private static SerializationNode WriteMap( Type keyType, Type mapValueType, SerializationOperation operation, - string path + string path, + SerializationNode? previous ) { IReadOnlyList> entries = operation.GetMetadata(mapType).collection!.enumerateMap!(value); var node = new MapSerializationNode(); @@ -322,7 +336,10 @@ string path Write(entry.Key, keyType, operation, $"{path}[{i}].key", false), Write(entry.Value, mapValueType, operation, $"{path}[{i}].value", false))); } - return node; + return previous is MapSerializationNode && + BinarySerializationFormat.Encode(node).AsSpan().SequenceEqual(BinarySerializationFormat.Encode(previous)) + ? previous + : node; } private static object ReadMap( @@ -349,20 +366,46 @@ private static SerializationNode WriteStruct( object value, Type structType, SerializationOperation operation, - string path + string path, + ObjectSerializationNode? previous ) { IReadOnlyList members = operation.GetMetadata(structType).members; - var node = new ObjectSerializationNode(); + ObjectSerializationNode? node = previous is null ? new ObjectSerializationNode() : null; + int count = 0; for (int i = 0; i < members.Count; i++) { SerializationMemberMetadata member = members[i]; if ((member.visibility & PropertyVisibility.Serialize) == 0) continue; - node.values.Add( - member.name, - Write(member.GetValue(value), member.type, operation, AppendPath(path, member.name), false)); + SerializationNode? prior = previous is not null && previous.values.TryGetValue(member.name, out SerializationNode? found) + ? found + : null; + SerializationNode captured = Write(member.GetValue(value), member.type, operation, + prior?.capturePath ?? AppendPath(path, member.name), false, prior); + if (!ReferenceEquals(prior, captured)) + { + node ??= SerializationNodeReuse.CopyObject(previous!); + node.values[member.name] = captured; + } + count++; } - return node; + if (previous is not null && (node is not null || count != previous.values.Count)) + { + node ??= SerializationNodeReuse.CopyObject(previous); + foreach (string name in previous.values.Keys) + { + bool included = false; + for (int index = 0; index < members.Count; index++) + if (members[index].name == name && (members[index].visibility & PropertyVisibility.Serialize) != 0) + { + included = true; + break; + } + if (!included) + node.values.Remove(name); + } + } + return node ?? previous!; } private static object ReadStruct( @@ -462,7 +505,7 @@ string path } } - private static bool IsPrimitive(Type type) + internal static bool IsPrimitive(Type type) => type == typeof(bool) || type == typeof(byte) || type == typeof(sbyte) || diff --git a/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs b/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs new file mode 100644 index 000000000..5bcb6c0fb --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationRevisionAttribute.cs @@ -0,0 +1,34 @@ +using System; + +namespace Inno.Core.Serialization; + +/// +/// Declares a sealed identity source's transient revision member, which covers all serialized state and pure getters. +/// +/// +/// The member must return ulong or nullable ulong. Every relevant write must advance its value before mutation; +/// null declines reuse. The counter is not serialized. Metadata providers explicitly opt into this declaration. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class SerializationRevisionAttribute : Attribute +{ + /// + /// Selects the source-owned nonserialized revision field or property. + /// + /// + /// The exact nonstatic field or readable nonindexed property name. + /// + /// + /// The name is blank. + /// + public SerializationRevisionAttribute(string memberName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(memberName); + this.memberName = memberName; + } + + /// + /// Gets the exact source-owned revision member name. + /// + public string memberName { get; } +} diff --git a/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs b/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs index 8bf93e882..58ffff6fe 100644 --- a/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs +++ b/src/foundation/core/Inno.Core.Serialization/Metadata/SerializationTypeMetadata.cs @@ -10,6 +10,7 @@ namespace Inno.Core.Serialization; public sealed class SerializationTypeMetadata { private readonly Func? m_factory; + private readonly Guid m_captureIdentity = Guid.NewGuid(); /// /// Copies complete declaration metadata before it participates in a serialization generation. @@ -95,6 +96,57 @@ public SerializationTypeMetadata( /// public bool requiresConverter { get; } + /// + /// Declares that every serialized member of this value type reads a direct primitive field. + /// + /// + /// An immutable declaration with pure field access; the original declaration remains unchanged. + /// + /// + /// The declaration is not a default value type with primitive serialized members. + /// + public SerializationTypeMetadata WithPureFieldAccess() + { + if (!type.IsValueType || requiresConverter || collection is not null || + members.Any(static member => (member.visibility & PropertyVisibility.Serialize) != 0 && !ValuePipeline.IsPrimitive(member.type))) + throw new InvalidOperationException("Pure field capture requires a default value type containing only primitive serialized fields."); + return new SerializationTypeMetadata(type, members, m_factory, restored, collection, requiresConverter) + { + hasPureFieldAccess = true + }; + } + + /// + /// Declares complete pure serialized-property revision access for a sealed serializable identity source. + /// + /// + /// The owner-controlled revision reader; null results decline reuse and counters must not repeat for different state. + /// + /// + /// An immutable declaration; snapshots never retain this reader or metadata. + /// + /// + /// The revision reader is null. + /// + /// + /// The declaration is not a sealed identity reference type. + /// + public SerializationTypeMetadata WithCaptureRevision(Func readRevision) + { + ArgumentNullException.ThrowIfNull(readRevision); + if (!type.IsSealed || type.IsValueType || !typeof(ISerializable).IsAssignableFrom(type) || + !typeof(Inno.Core.Identity.IdentityObject).IsAssignableFrom(type)) + { + throw new InvalidOperationException( + "Capture revision metadata requires a sealed serializable Core identity source."); + } + return new SerializationTypeMetadata(type, members, m_factory, restored, collection, requiresConverter) + { + hasPureFieldAccess = hasPureFieldAccess, + captureRevision = readRevision + }; + } + /// /// Creates a new instance through the declaration's selected constructor. /// @@ -117,4 +169,8 @@ public object CreateInstance() throw new InvalidOperationException($"The parameterless constructor for serialization type '{type}' failed.", exception); } } + + internal Guid captureIdentity => m_captureIdentity; + internal bool hasPureFieldAccess { get; private init; } + internal Func? captureRevision { get; private init; } } diff --git a/src/foundation/core/Inno.Core.Serialization/Properties/ScriptingApi.cs b/src/foundation/core/Inno.Core.Serialization/Properties/ScriptingApi.cs index 2d3e09583..0e7e0a458 100644 --- a/src/foundation/core/Inno.Core.Serialization/Properties/ScriptingApi.cs +++ b/src/foundation/core/Inno.Core.Serialization/Properties/ScriptingApi.cs @@ -23,3 +23,6 @@ [assembly: ScriptingApiExport(typeof(SerializationConverter<>), ScriptingApiScope.Runtime)] [assembly: ScriptingApiExport(typeof(SerializationReader), ScriptingApiScope.Runtime)] [assembly: ScriptingApiExport(typeof(SerializationWriter), ScriptingApiScope.Runtime)] + +[assembly: ScriptingApiExport(typeof(SerializationCaptureKeyBuilder), ScriptingApiScope.Runtime)] +[assembly: ScriptingApiExport(typeof(SerializationRevisionAttribute), ScriptingApiScope.Runtime)] diff --git a/src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs b/src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs index 31dea123c..90ac448cc 100644 --- a/src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs +++ b/src/foundation/core/Inno.Core.Serialization/SerializationRegistry.cs @@ -154,7 +154,7 @@ public IReadOnlyList CaptureProperties( } /// - /// Captures one persistent property as independently restorable neutral bytes. + /// Captures one persistent property as independently restorable neutral bytes without reading unrelated members. /// /// /// The serializable object containing the requested property. @@ -218,8 +218,7 @@ public byte[] CapturePropertiesData( EnsureInitialized(); ArgumentNullException.ThrowIfNull(value); using ConverterRegistryLease converters = m_converters.Capture(); - return PropertySnapshotBinaryFormat.Encode( - PropertySnapshotPipeline.Capture(value, CreateContext(context), converters)); + return PropertySnapshotPipeline.CaptureData(value, CreateContext(context), converters, null).value; } /// @@ -373,6 +372,62 @@ public byte[] Serialize( return Serialize(value, converters, context); } + /// + /// Captures complete serialized state while sharing unchanged neutral values with a previous capture. + /// + /// + /// The exact concrete root type, interpreted by the current converter generation. + /// + /// + /// The non-null root captured through its current converter and declared complete inputs. + /// + /// + /// The complete owner context, including required reference resolvers. + /// + /// + /// An optional immutable capture to reuse; it is never changed or retained by this registry. + /// + /// + /// The previous instance when all captured values can be reused, otherwise a new immutable snapshot. + /// Reference equality proves unchanged bytes; different instances may still encode identically. + /// Ordinary serialization and snapshot encoding produce identical bytes. Capture failure publishes no snapshot. + /// + /// + /// Writers borrowed by a snapshot converter belong to its synchronous callback and must not be used after it returns. + /// Default capture evaluates getters and converters in full. An explicitly opted-in converter may + /// reuse its prior node only after every current capture-key input has been verified. Ordinary + /// serialization never uses these keys; incomplete or unavailable declarations retain complete capture. + /// + /// + /// The source value is null. + /// + /// + /// The registry has been disposed. + /// + /// + /// A converter, unsupported value or reference context prevents capture. + /// + [ScriptingApiIgnore] + public SerializationSnapshot CaptureSnapshot( + T value, + SerializationContext? context = null, + SerializationSnapshot? previous = null + ) where T : class, ISerializable { + EnsureInitialized(); + ArgumentNullException.ThrowIfNull(value); + using ConverterRegistryLease converters = m_converters.Capture(); + var operation = new SerializationOperation(CreateContext(context), converters, snapshotCapture: true); + try + { + SerializationNode root = ValuePipeline.WriteRoot(value, typeof(T), operation, previous?.root); + return ReferenceEquals(root, previous?.root) ? previous! : new SerializationSnapshot(root); + } + finally + { + operation.Fail(); + } + } + internal byte[] Serialize( T value, ConverterRegistryLease converters, @@ -529,9 +584,9 @@ internal byte[] Encode( var operation = new SerializationOperation(CreateContext(context), converters); try { - var root = new ObjectSerializationNode(); - write(new SerializationWriter(operation, root, "$", typeof(object))); - return BinarySerializationFormat.Encode(root); + var writer = new SerializationWriter(operation, null, "$", typeof(object)); + write(writer); + return BinarySerializationFormat.Encode(writer.Complete()); } finally { @@ -609,5 +664,12 @@ private void EnsureInitialized() } private SerializationContext CreateContext(SerializationContext? context) - => (context ?? SerializationContext.empty).With(m_types).With(this).With(m_metadata); + { + context ??= SerializationContext.empty; + if (context.TryGet(out TypeCatalog? types) && ReferenceEquals(types, m_types) + && context.TryGet(out SerializationRegistry? registry) && ReferenceEquals(registry, this) + && context.TryGet(out ISerializationMetadataSource? metadata) && ReferenceEquals(metadata, m_metadata)) + return context; + return context.With(m_types).With(this).With(m_metadata); + } } diff --git a/src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs b/src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs new file mode 100644 index 000000000..4c03dc4d3 --- /dev/null +++ b/src/foundation/core/Inno.Core.Serialization/SerializationSnapshot.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; + +namespace Inno.Core.Serialization; + +/// +/// Holds an immutable, neutral capture of serialized values without retaining the source object or its generation. +/// +/// +/// Capture again through to reuse unchanged values. +/// Readers may share a snapshot across threads. It owns only scalar values, strings and copied binary data, +/// never source instances, metadata, converters or delegates. It is transient and is not a persistence format. +/// +public sealed class SerializationSnapshot +{ + internal SerializationSnapshot(SerializationNode root) => this.root = root; + + /// + /// Encodes this capture using the same deterministic binary format as ordinary serialization. + /// + /// + /// A new caller-owned byte array; changing it cannot modify this snapshot. + /// + public byte[] ToArray() => BinarySerializationFormat.Encode(root); + + /// + /// Streams this capture in the ordinary deterministic binary format without requiring a contiguous complete-output buffer. + /// + /// + /// The writable caller-owned stream, which remains open. + /// + /// + /// Immutable keyed objects may retain canonical encoded blocks of at most 64 KiB each. Larger + /// objects remain streamed, and cached bytes never contain source objects or generation references. + /// + /// + /// The destination is null. + /// + /// + /// The destination cannot be written. + /// + /// + /// The destination rejects a write; its partial bytes remain the caller's responsibility. + /// + public void WriteTo(Stream destination) + { + ArgumentNullException.ThrowIfNull(destination); + if (!destination.CanWrite) + throw new ArgumentException("A writable serialization destination is required.", nameof(destination)); + BinarySerializationFormat.WriteTo(destination, root); + } + + internal SerializationNode root { get; } +} diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/FrameworkReferenceResolver.cs b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/FrameworkReferenceResolver.cs index adc3ba6f1..8e51a2565 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/FrameworkReferenceResolver.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/FrameworkReferenceResolver.cs @@ -14,9 +14,11 @@ namespace Inno.Scripting.Compiler; internal static class FrameworkReferenceResolver { - internal static ScriptCompilationEnvironment Resolve(CancellationToken cancellationToken = default) - { - string referenceDirectory = FindReferenceDirectory(); + internal static ScriptCompilationEnvironment Resolve( + IScriptCompilationEnvironmentProvider provider, + CancellationToken cancellationToken = default + ) { + ScriptCompilationInputs inputs = provider.Capture(cancellationToken); var references = new List(); var names = new HashSet(StringComparer.OrdinalIgnoreCase) { "System.Private.CoreLib" }; using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); @@ -26,14 +28,14 @@ internal static ScriptCompilationEnvironment Resolve(CancellationToken cancellat ScriptCompilationEnvironment.C_LANGUAGE_VERSION, typeof(CSharpCompilation).Module.ModuleVersionId.ToString("N"), typeof(FrameworkReferenceResolver).Module.ModuleVersionId.ToString("N"), - RuntimeInformation.FrameworkDescription + inputs.sdkIdentity, + string.Join(";", inputs.predefinedSymbols) }) { hash.AppendData(Encoding.UTF8.GetBytes(identity)); hash.AppendData([0]); } - foreach (string path in Directory.EnumerateFiles(referenceDirectory, "*.dll") - .OrderBy(static path => Path.GetFileName(path), StringComparer.Ordinal)) + foreach (string path in inputs.frameworkReferences) { cancellationToken.ThrowIfCancellationRequested(); hash.AppendData(Encoding.UTF8.GetBytes(Path.GetFileName(path))); @@ -45,61 +47,7 @@ internal static ScriptCompilationEnvironment Resolve(CancellationToken cancellat names.Add(Path.GetFileNameWithoutExtension(path)); } return new ScriptCompilationEnvironment( - references.AsReadOnly(), names, Convert.ToHexStringLower(hash.GetHashAndReset())); + references.AsReadOnly(), names, Convert.ToHexStringLower(hash.GetHashAndReset()), inputs); } - private static string FindReferenceDirectory() - { - const string targetFramework = ScriptCompilationEnvironment.C_TARGET_FRAMEWORK; - foreach (string dotnetRoot in GetDotnetRoots()) - { - string packRoot = Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref"); - if (!Directory.Exists(packRoot)) - continue; - string? directory = Directory - .EnumerateDirectories(packRoot) - .Select(path => new - { - path, - version = ParseVersion(Path.GetFileName(path)) - }) - .Where(candidate => candidate.version is not null && - candidate.version.Major == 10 && candidate.version.Minor == 0 && - !Path.GetFileName(candidate.path).Contains('-', StringComparison.Ordinal)) - .OrderByDescending(static candidate => candidate.version) - .Select(candidate => Path.Combine(candidate.path, "ref", targetFramework)) - .FirstOrDefault(Directory.Exists); - if (directory is not null) - return directory; - } - throw new InvalidOperationException( - $"The {targetFramework} reference pack is required to generate script API assemblies. " + - "Install the matching .NET SDK, publish the Editor with its reference pack, or configure DOTNET_ROOT."); - } - - private static IEnumerable GetDotnetRoots() - { - var roots = new List(); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - Add(AppContext.BaseDirectory); - Add(Environment.GetEnvironmentVariable("DOTNET_ROOT")); - Add(Environment.GetEnvironmentVariable("DOTNET_ROOT_X64")); - Add(Environment.GetEnvironmentVariable("DOTNET_ROOT_ARM64")); - string runtimeDirectory = Path.GetFullPath(RuntimeEnvironment.GetRuntimeDirectory()); - Add(Path.GetFullPath(Path.Combine(runtimeDirectory, "..", "..", ".."))); - return roots; - - void Add(string? path) - { - if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path)) - { - string root = Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)); - if (seen.Add(root)) - roots.Add(root); - } - } - } - - private static Version? ParseVersion(string value) - => Version.TryParse(value, out Version? result) ? result : null; } diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationEnvironment.cs b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationEnvironment.cs index ae9decba8..e599f4306 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationEnvironment.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilationEnvironment.cs @@ -14,14 +14,17 @@ internal sealed class ScriptCompilationEnvironment internal ScriptCompilationEnvironment( IReadOnlyList frameworkReferences, IReadOnlySet frameworkAssemblyNames, - string fingerprint + string fingerprint, + ScriptCompilationInputs inputs ) { this.frameworkReferences = frameworkReferences; this.frameworkAssemblyNames = frameworkAssemblyNames; this.fingerprint = fingerprint; + this.inputs = inputs; } internal IReadOnlyList frameworkReferences { get; } internal IReadOnlySet frameworkAssemblyNames { get; } internal string fingerprint { get; } + internal ScriptCompilationInputs inputs { get; } } diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilerEngine.cs b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilerEngine.cs index 34d0b8ba6..02ef21cff 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilerEngine.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptCompilerEngine.cs @@ -15,6 +15,7 @@ using Inno.Plugins.Authoring; using Inno.Extensibility.Modules; using Inno.Runtime.Generators; +using Inno.Core.IO; using Inno.Core.Serialization.Generators; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -83,7 +84,7 @@ CancellationToken cancellationToken : null; progress.Complete("Script API profile built."); progress.Begin("Resolving script references..."); - ScriptCompilationEnvironment environment = FrameworkReferenceResolver.Resolve(cancellationToken); + ScriptCompilationEnvironment environment = FrameworkReferenceResolver.Resolve(options.compilationEnvironment, cancellationToken); ScriptApiReferenceSet runtimeApiReferences = ScriptApiReferenceBuilder.Build( options, runtimeApi, environment, cancellationToken: cancellationToken); ScriptApiReferenceSet? editorApiReferences = editorApi is null @@ -110,7 +111,7 @@ CancellationToken cancellationToken ? editorApiReferences ?? throw new InvalidOperationException( "A runtime deployment cannot contain an editor script assembly.") : runtimeApiReferences; - IReadOnlyList defines = GetCompilationDefines(assembly, includeEditor); + IReadOnlyList defines = GetCompilationDefines(assembly, includeEditor, environment.inputs.predefinedSymbols); assemblyKeys.Add( assembly.name, ComputeAssemblyBuildKey( @@ -199,7 +200,7 @@ CancellationToken cancellationToken string.IsNullOrEmpty(assembly.ownerPluginId) ? AssetSourceId.project.value : assembly.ownerPluginId, - GetCompilationDefines(assembly, includeEditor), + GetCompilationDefines(assembly, includeEditor, environment.inputs.predefinedSymbols), assembly.nullable, assembly.allowUnsafe, progress, @@ -222,7 +223,8 @@ CancellationToken cancellationToken CommitAssemblyCache( assemblyStagingDirectory, assemblyCacheDirectory, - assembly.name); + assembly.name, + cancellationToken); if (!TryCopyCachedAssembly( assemblyCacheDirectory, assembly.name, @@ -239,7 +241,7 @@ CancellationToken cancellationToken File.WriteAllBytes( GetDiagnosticsPath(stagingDirectory), ScriptCompilerCacheSerialization.EncodeDiagnostics(diagnostics)); - CommitGenerationCache(stagingDirectory, outputDirectory, sources); + CommitGenerationCache(stagingDirectory, outputDirectory, sources, cancellationToken); progress.Begin("Preparing the script reload..."); IReadOnlyList requests = CreateModuleDeployments( @@ -581,16 +583,17 @@ IReadOnlyList defines private static IReadOnlyList GetCompilationDefines( ScriptAssemblyInput assembly, - bool includeEditor + bool includeEditor, + IReadOnlyList predefinedSymbols ) { ArgumentNullException.ThrowIfNull(assembly); return includeEditor - ? assembly.defines + ? assembly.defines.Concat(predefinedSymbols) .Append(C_EDITOR_COMPILATION_SYMBOL) .Distinct(StringComparer.Ordinal) .OrderBy(static value => value, StringComparer.Ordinal) .ToArray() - : assembly.defines; + : assembly.defines.Concat(predefinedSymbols).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); } private static string ComputeGenerationBuildKey( @@ -657,11 +660,12 @@ out ScriptDiagnostic[] diagnostics private static void CommitAssemblyCache( string stagingDirectory, string cacheDirectory, - string assemblyName + string assemblyName, + CancellationToken cancellationToken ) { lock (S_CACHE_SYNC) { - Directory.CreateDirectory(Path.GetDirectoryName(cacheDirectory)!); + cancellationToken.ThrowIfCancellationRequested(); if (Directory.Exists(cacheDirectory)) { if (IsCachedAssemblyComplete(cacheDirectory, assemblyName)) @@ -670,9 +674,10 @@ string assemblyName TryTouchCacheDirectory(cacheDirectory); return; } - DeleteStagingDirectory(cacheDirectory); + AtomicDirectory.Install(stagingDirectory, cacheDirectory, cancellationToken); + return; } - Directory.Move(stagingDirectory, cacheDirectory); + AtomicDirectory.Publish(stagingDirectory, cacheDirectory, cancellationToken); } } @@ -718,11 +723,12 @@ IOException or private static void CommitGenerationCache( string stagingDirectory, string outputDirectory, - ScriptSourceSet sources + ScriptSourceSet sources, + CancellationToken cancellationToken ) { lock (S_CACHE_SYNC) { - Directory.CreateDirectory(Path.GetDirectoryName(outputDirectory)!); + cancellationToken.ThrowIfCancellationRequested(); if (Directory.Exists(outputDirectory)) { if (TryCreateCachedResult(outputDirectory, sources, out _)) @@ -731,9 +737,10 @@ ScriptSourceSet sources TryTouchCacheDirectory(outputDirectory); return; } - DeleteStagingDirectory(outputDirectory); + AtomicDirectory.Install(stagingDirectory, outputDirectory, cancellationToken); + return; } - Directory.Move(stagingDirectory, outputDirectory); + AtomicDirectory.Publish(stagingDirectory, outputDirectory, cancellationToken); } } diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptProjectGenerator.cs b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptProjectGenerator.cs index a3d1a5826..d81f4b087 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptProjectGenerator.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/Compilation/ScriptProjectGenerator.cs @@ -33,7 +33,7 @@ PluginEnvironment plugins .ToArray(); ScriptApiProfile runtimeApi = ScriptApiCatalog.Build(includeEditor: false); ScriptApiProfile editorApi = ScriptApiCatalog.Build(includeEditor: true); - ScriptCompilationEnvironment environment = FrameworkReferenceResolver.Resolve(); + ScriptCompilationEnvironment environment = FrameworkReferenceResolver.Resolve(options.compilationEnvironment); ScriptApiReferenceSet runtimeApiReferences = ScriptApiReferenceBuilder.Build(options, runtimeApi, environment); ScriptApiReferenceSet editorApiReferences = ScriptApiReferenceBuilder.Build( options, @@ -162,6 +162,7 @@ private static XDocument CreateProject( bool allowUnsafe, string sourcePathMap ) { + string frozenDefines = string.Join(";", defines.Concat(apiReferences.environment.inputs.predefinedSymbols).Distinct(StringComparer.Ordinal)); var earlyPropertyGroup = new XElement("PropertyGroup", new XElement("ImportDirectoryBuildProps", "false"), new XElement("ImportDirectoryBuildTargets", "false"), @@ -178,7 +179,11 @@ string sourcePathMap new XElement("ImplicitUsings", "disable"), new XElement("Nullable", nullable ? "enable" : "disable"), new XElement("AllowUnsafeBlocks", allowUnsafe ? "true" : "false"), - new XElement("DefineConstants", string.Join(";", defines)), + new XElement("DisableImplicitFrameworkReferences", "true"), + new XElement("DisableImplicitFrameworkDefines", "true"), + new XElement("DisableImplicitConfigurationDefines", "true"), + new XElement("NoStdLib", "true"), + new XElement("DefineConstants", frozenDefines), new XElement("PathMap", sourcePathMap), new XElement("LangVersion", ScriptCompilationEnvironment.C_LANGUAGE_VERSION)); var compileGroup = new XElement( @@ -191,7 +196,7 @@ string sourcePathMap new XElement("None", new XAttribute("Include", $"Assets/**/*{extension}")))); var referenceGroup = new XElement("ItemGroup"); - foreach (string path in apiReferences.ideReferencePaths) + foreach (string path in apiReferences.ideReferencePaths.Concat(apiReferences.environment.inputs.frameworkReferences)) AddReference(referenceGroup, path); foreach (string path in plugins .Distinct(StringComparer.OrdinalIgnoreCase) @@ -219,6 +224,28 @@ string sourcePathMap contentGroup, referenceGroup, codeAnalysisGroup); + project.Add(new XElement("Target", new XAttribute("Name", "ValidateInnoScriptSdk"), + new XAttribute("BeforeTargets", "ResolveReferences"), + new XElement("Error", new XAttribute("Condition", "'$(NETCoreSdkVersion)' != '" + apiReferences.environment.inputs.sdkIdentity + "'"), + new XAttribute("Text", "The IDE selected a different SDK from the active script compilation. Regenerate project files with the intended SDK selection.")))); + XElement validation = project.Elements("Target").Single(); + foreach ((string property, string expected) in new[] + { + ("TargetFramework", ScriptCompilationEnvironment.C_TARGET_FRAMEWORK), + ("LangVersion", ScriptCompilationEnvironment.C_LANGUAGE_VERSION), + ("ImplicitUsings", "disable"), ("DisableImplicitFrameworkReferences", "true"), + ("DisableImplicitFrameworkDefines", "true"), ("NoStdLib", "true"), + ("DisableImplicitConfigurationDefines", "true"), + ("Nullable", nullable ? "enable" : "disable"), ("AllowUnsafeBlocks", allowUnsafe ? "true" : "false"), + ("DefineConstants", frozenDefines) + }) + { + string condition = property == "DefineConstants" + ? "$([System.String]::CompareOrdinal('$(DefineConstants)', '" + expected + "')) != 0" + : "'$(" + property + ")' != '" + expected + "'"; + validation.Add(new XElement("Error", new XAttribute("Condition", condition), + new XAttribute("Text", "The IDE compiler input '" + property + "' differs from the active script contract. Actual: '$(" + property + ")'. Expected: '" + expected + "'. Regenerate project files instead of overriding derived compiler inputs."))); + } project.Add(folderGroup); if (projectReferences.Count > 0) { diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/IScriptCompilationEnvironmentProvider.cs b/src/runtime/scripting/Inno.Scripting.Compiler/IScriptCompilationEnvironmentProvider.cs new file mode 100644 index 000000000..3a7e7b5dd --- /dev/null +++ b/src/runtime/scripting/Inno.Scripting.Compiler/IScriptCompilationEnvironmentProvider.cs @@ -0,0 +1,20 @@ +using System.Threading; + +namespace Inno.Scripting.Compiler; + +/// +/// Supplies the host-selected framework contract for one compilation or IDE projection operation. +/// +public interface IScriptCompilationEnvironmentProvider +{ + /// + /// Captures current SDK inputs without choosing a framework inside the scripting compiler. + /// + /// + /// Cancels input preparation before any script artifact is published. + /// + /// + /// Complete immutable inputs; missing or inconsistent SDK inputs must fail explicitly. + /// + ScriptCompilationInputs Capture(CancellationToken cancellationToken = default); +} diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/Inno.Scripting.Compiler.csproj b/src/runtime/scripting/Inno.Scripting.Compiler/Inno.Scripting.Compiler.csproj index 24477707b..176a7f2f9 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/Inno.Scripting.Compiler.csproj +++ b/src/runtime/scripting/Inno.Scripting.Compiler/Inno.Scripting.Compiler.csproj @@ -13,6 +13,9 @@ + +/// Describes framework references and predefined symbols selected by the authoring host's SDK. +/// +public sealed class ScriptCompilationInputs +{ + /// + /// Freezes a complete C# 14 and .NET 10 reference contract without retaining SDK objects. + /// + /// + /// The exact SDK identity that evaluated this contract. + /// + /// + /// Absolute framework assembly paths selected by SDK reference resolution. + /// + /// + /// Framework and configuration symbols produced by SDK target execution. + /// + /// + /// The identity, reference set or a reference path is invalid. + /// + /// + /// A collection is null. + /// + public ScriptCompilationInputs( + string sdkIdentity, + IEnumerable frameworkReferences, + IEnumerable predefinedSymbols + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(sdkIdentity); + ArgumentNullException.ThrowIfNull(frameworkReferences); + ArgumentNullException.ThrowIfNull(predefinedSymbols); + string[] references = frameworkReferences.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); + if (references.Length == 0 || references.Any(static path => !Path.IsPathFullyQualified(path))) + throw new ArgumentException("SDK framework references must be a nonempty set of absolute paths.", nameof(frameworkReferences)); + this.sdkIdentity = sdkIdentity; + this.frameworkReferences = Array.AsReadOnly(references); + this.predefinedSymbols = Array.AsReadOnly(predefinedSymbols.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray()); + } + + /// + /// Gets the exact SDK identity used for IDE consistency checks and artifact identity. + /// + public string sdkIdentity { get; } + + /// + /// Gets the SDK-selected framework paths, whose content is frozen before Roslyn compilation. + /// + public IReadOnlyList frameworkReferences { get; } + + /// + /// Gets SDK-produced symbols shared by runtime compilation and IDE projects. + /// + public IReadOnlyList predefinedSymbols { get; } +} diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs b/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs index b9e2fb043..71fb666cf 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompiler.cs @@ -45,10 +45,12 @@ PluginEnvironment plugins ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(assets); ArgumentNullException.ThrowIfNull(plugins); + ArgumentNullException.ThrowIfNull(options.compilationEnvironment); ArgumentException.ThrowIfNullOrWhiteSpace(options.projectRootDirectory); m_options = new ScriptCompilerOptions { - projectRootDirectory = System.IO.Path.GetFullPath(options.projectRootDirectory) + projectRootDirectory = System.IO.Path.GetFullPath(options.projectRootDirectory), + compilationEnvironment = options.compilationEnvironment }; m_assets = assets; m_plugins = plugins; @@ -76,6 +78,8 @@ public void GenerateProjectFiles() /// Source discovery captures live inputs on the owner thread. Reference generation, Roslyn and /// artifact writes run in the background; progress observers must accept callbacks from either thread. /// Retain the source generation until the returned operation drains, including after cancellation. + /// Complete cache trees use Core IO atomic publication and replacement. A committed cache remains + /// valid if cleanup of its previous tree fails; compilation does not activate either generation. /// /// /// Optional observer for monotonic compiler progress. @@ -89,6 +93,15 @@ public void GenerateProjectFiles() /// /// Thrown when is canceled. /// + /// + /// Artifact publication fails, or cleanup fails after a complete cache tree commits. + /// + /// + /// The filesystem continues denying artifact access after the bounded Core IO wait. + /// + /// + /// Cache replacement and restoration both fail; the exception identifies the preserved previous tree. + /// /// /// Optional isolated authoring sources. Capture on its owner thread and keep its transaction /// alive until compilation drains; null selects the current Plugin candidate or active sources. @@ -116,6 +129,11 @@ progress is null /// /// Compiles only the runtime assembly closure required by a deployed Player and binds it to that Player runtime. /// + /// + /// Complete cache trees use Core IO atomic publication and replacement. Cancellation before commit + /// preserves the previous tree; cancellation does not undo a committed cache. Cleanup failure after + /// commit preserves the installed tree and remaining backup without activating a generation. + /// /// /// The verified Player Support Pack directory whose runtime assemblies will execute the emitted scripts. /// @@ -140,6 +158,15 @@ progress is null /// /// Thrown when the target runtime has no valid Inno runtime assembly closure. /// + /// + /// Artifact publication fails, or cleanup fails after a complete cache tree commits. + /// + /// + /// The filesystem continues denying artifact access after the bounded Core IO wait. + /// + /// + /// Cache replacement and restoration both fail; the exception identifies the preserved previous tree. + /// public ValueTask CompileRuntimeDeploymentAsync( string targetRuntimeDirectory, IProgress? progress = null, diff --git a/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs b/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs index 3b6af6e92..38069b56b 100644 --- a/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs +++ b/src/runtime/scripting/Inno.Scripting.Compiler/ScriptCompilerOptions.cs @@ -12,6 +12,11 @@ public sealed class ScriptCompilerOptions /// public required string projectRootDirectory { get; init; } + /// + /// Gets the host-owned source of SDK framework inputs for each operation. + /// + public required IScriptCompilationEnvironmentProvider compilationEnvironment { get; init; } + internal string ideDirectory => Path.Combine(projectRootDirectory, "Library", "IDE"); internal string outputDirectory => Path.Combine(projectRootDirectory, "Library", "Artifacts", "ScriptAssemblies"); diff --git a/tests/Invoke-EditorSmoke.ps1 b/tests/Invoke-EditorSmoke.ps1 index 94bccb94d..8f2694c7f 100644 --- a/tests/Invoke-EditorSmoke.ps1 +++ b/tests/Invoke-EditorSmoke.ps1 @@ -2,6 +2,8 @@ param( [Parameter(Mandatory = $true)][string]$EditorAssembly, [Parameter(Mandatory = $true)][string]$ProjectDirectory, [Parameter(Mandatory = $true)][string]$BackendPattern, + [string]$GraphicsApi, + [switch]$HiddenWindow, [string]$Dotnet = 'dotnet', [int]$Frames = 3, [int]$TimeoutSeconds = 180 @@ -30,6 +32,14 @@ $start.WorkingDirectory = $ProjectDirectory foreach ($argument in @($EditorAssembly, $ProjectDirectory, '--smoke-frames', [string]$Frames)) { $start.ArgumentList.Add($argument) } +if ($HiddenWindow) { + $start.ArgumentList.Add('--window-visible') + $start.ArgumentList.Add('false') +} +if ($GraphicsApi) { + $start.ArgumentList.Add('--graphics-api') + $start.ArgumentList.Add($GraphicsApi) +} $process = [Diagnostics.Process]::new() $process.StartInfo = $start $copies = [Threading.CancellationTokenSource]::new() diff --git a/tests/Invoke-EditorUiAcceptance.ps1 b/tests/Invoke-EditorUiAcceptance.ps1 new file mode 100644 index 000000000..32066e578 --- /dev/null +++ b/tests/Invoke-EditorUiAcceptance.ps1 @@ -0,0 +1,174 @@ +param( + [Alias('Profile')][ValidateSet('Contracts', 'Workflows', 'Visual', 'Lifecycle', 'Performance', 'Products', 'All')] + [string]$AcceptanceProfile = 'Contracts', + [ValidateSet('Debug', 'Release')][string]$Configuration = 'Debug', + [string]$Dotnet = 'dotnet', + [Parameter(Mandatory = $true)][string]$ResultsDirectory, + [Parameter(Mandatory = $true)][string]$ArtifactsPath, + [string]$TemporaryDirectory, + [string]$ProductProject, + [string[]]$GraphicsApis = @(), + [string]$BackendPattern, + [switch]$NoBuild +) + +$ErrorActionPreference = 'Stop' +$repository = Split-Path $PSScriptRoot -Parent +$Dotnet = (Get-Command $Dotnet -ErrorAction Stop).Source +$results = Join-Path ([IO.Path]::GetFullPath($ResultsDirectory, $repository)) ([Guid]::NewGuid().ToString('N')) +$artifacts = [IO.Path]::GetFullPath($ArtifactsPath, $repository) +New-Item -ItemType Directory -Force -Path $results | Out-Null +$records = [Collections.Generic.List[object]]::new() +$revision = (& git -C $repository rev-parse HEAD).Trim() +if ($LASTEXITCODE -ne 0) { throw 'The candidate revision could not be recorded.' } +$dirty = @(& git -C $repository status --porcelain=v1) +if ($LASTEXITCODE -ne 0) { throw 'The candidate working state could not be recorded.' } +$sdk = @(& $Dotnet --info) +if ($LASTEXITCODE -ne 0) { throw 'The selected SDK could not be inspected.' } +$sdk | Set-Content -LiteralPath (Join-Path $results 'sdk.txt') -Encoding utf8 + +function Save-Result( + [string]$Stage, + [string]$Status, + [string]$Diagnostic +) { + $records.Add([pscustomobject]@{ stage = $Stage; status = $Status; diagnostic = $Diagnostic }) + [pscustomobject]@{ + revision = $revision + workingChanges = $dirty + profile = $AcceptanceProfile + configuration = $Configuration + buildRequested = -not $NoBuild + scope = 'Selected automated suites; issue closure additionally requires the complete tracked acceptance matrix.' + results = @($records.ToArray()) + } | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $results 'summary.json') -Encoding utf8 +} + +$entry = Join-Path $repository 'entry/InnoEngine.Editor/InnoEngine.Editor.csproj' +$evaluated = @(& $Dotnet msbuild $entry -nologo "-p:Configuration=$Configuration" "-p:ArtifactsPath=$artifacts" ` + '-getProperty:InnoNativeTarget,InnoToolTarget,InnoProductBuildProperties,TargetPath') +if ($LASTEXITCODE -ne 0) { throw 'The official Editor entry could not select its product owner.' } +$product = ($evaluated -join [Environment]::NewLine | ConvertFrom-Json).Properties +if (-not $product.InnoNativeTarget -or -not $product.InnoToolTarget -or + -not (Test-Path -LiteralPath $product.InnoProductBuildProperties)) { + throw 'The selected development host does not provide an Editor product.' +} +$buildArguments = @("-p:ArtifactsPath=$artifacts", "-p:InnoNativeTarget=$($product.InnoNativeTarget)", + "-p:InnoToolTarget=$($product.InnoToolTarget)", + "-p:InnoProductBuildProperties=$($product.InnoProductBuildProperties)") +$profiles = if ($AcceptanceProfile -eq 'All') { @('Contracts', 'Workflows', 'Visual', 'Lifecycle', 'Performance', 'Products') } else { @($AcceptanceProfile) } + +foreach ($selected in $profiles) { + $stageRoot = Join-Path $results $selected + New-Item -ItemType Directory -Force -Path $stageRoot | Out-Null + try { + if ($selected -eq 'Products') { + if (-not $ProductProject -or -not (Test-Path -LiteralPath (Join-Path $ProductProject 'Assets'))) { + Save-Result $selected 'Blocked' 'Supply an independent FlappyBird acceptance copy using -ProductProject.' + continue + } + if ($GraphicsApis.Count -eq 0) { + Save-Result $selected 'Blocked' 'Supply the explicit graphics API matrix for the selected product using -GraphicsApis.' + continue + } + if (-not $BackendPattern) { + Save-Result $selected 'Blocked' 'Supply the product initialization expectation using -BackendPattern.' + continue + } + if (-not $NoBuild) { + & $Dotnet build $entry -c $Configuration @buildArguments *> (Join-Path $stageRoot 'build.log') + if ($LASTEXITCODE -ne 0) { throw 'The official Editor build failed.' } + } + if (-not (Test-Path -LiteralPath $product.TargetPath)) { throw 'The SDK-selected Editor output is unavailable.' } + foreach ($api in $GraphicsApis) { + & (Join-Path $PSScriptRoot 'Invoke-EditorSmoke.ps1') -Dotnet $Dotnet -EditorAssembly $product.TargetPath ` + -ProjectDirectory $ProductProject -GraphicsApi $api -BackendPattern $BackendPattern -Frames 300 -HiddenWindow ` + *> (Join-Path $stageRoot "$api.log") + & (Join-Path $PSScriptRoot 'Invoke-EditorUiProductJourney.ps1') -Dotnet $Dotnet -EditorAssembly $product.TargetPath ` + -ProjectDirectory $ProductProject -GraphicsApi $api -ResultsDirectory (Join-Path $stageRoot "journey-$api") ` + *> (Join-Path $stageRoot "journey-$api.log") + & (Join-Path $PSScriptRoot 'Invoke-EditorUiProductJourney.ps1') -Dotnet $Dotnet -EditorAssembly $product.TargetPath ` + -ProjectDirectory $ProductProject -GraphicsApi $api -Scenario Lifecycle -ResultsDirectory (Join-Path $stageRoot "lifecycle-$api") ` + *> (Join-Path $stageRoot "lifecycle-$api.log") + } + Save-Result $selected 'Passed' 'Hidden renderer smoke, eight product workflows and 100 cycles per actual panel completed; physical-device experience remains separately tracked.' + continue + } + $names = switch ($selected) { + 'Contracts' { @('Inno.Editor.Interactions.Tests', 'Inno.Editor.ImGui.Tests', 'Inno.Editor.Graph.Tests', 'Inno.Editor.Inspection.Tests', 'Inno.Scripting.Compiler.Tests', 'Inno.Build.Tests', 'Inno.Tooling.Architecture.Tests') } + 'Workflows' { @('Inno.Editor.PlayMode.Tests', 'Inno.Editor.Scripting.Tests', 'Inno.Editor.Hosting.Tests') } + 'Visual' { @('Inno.Editor.ImGui.Tests', 'Inno.Editor.PlayMode.Tests', 'Inno.Editor.Scripting.Tests') } + 'Lifecycle' { @('Inno.Editor.ImGui.Tests', 'Inno.Editor.Graph.Tests', 'Inno.Editor.PlayMode.Tests', 'Inno.Editor.Scripting.Tests') } + 'Performance' { @('Inno.Editor.Interactions.Tests', 'Inno.Editor.PlayMode.Tests', 'Inno.Editor.Scripting.Tests') } + } + $filter = switch ($selected) { + 'Visual' { 'FullyQualifiedName~WidgetLayoutTests|FullyQualifiedName~ViewportMetricsTests|Acceptance=Visual' } + 'Lifecycle' { 'Acceptance=Lifecycle' } + 'Performance' { 'Acceptance=Performance|Category=Performance' } + default { '(Acceptance!=Performance)&(Category!=Performance)' } + } + $projects = foreach ($name in $names) { + $projectFiles = @(Get-ChildItem -LiteralPath (Join-Path $repository 'tests') -Filter "$name.csproj" -Recurse) + if ($projectFiles.Count -ne 1) { throw "The suite '$name' does not resolve to one test project." } + [IO.Path]::GetRelativePath($repository, $projectFiles[0].FullName) + } + $selection = Join-Path $stageRoot 'tests.slnf' + @{ solution = @{ path = (Join-Path $repository 'InnoEngine.sln'); projects = @($projects) } } | + ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $selection -Encoding utf8 + if (-not $NoBuild) { + if ($selected -eq 'Contracts') { + $sourceConfiguration = "Source$Configuration" + & $Dotnet build (Join-Path $repository 'InnoEngine.sln') -c $sourceConfiguration "-p:ArtifactsPath=$artifacts" ` + *> (Join-Path $stageRoot 'source-build.log') + if ($LASTEXITCODE -ne 0) { throw 'The complete neutral source graph required by repository architecture and Wiki checks did not build.' } + $contracts = @() + foreach ($candidate in Get-ChildItem -LiteralPath (Join-Path $repository 'platforms') -Filter '*.csproj' -Recurse) { + $declaration = [xml](Get-Content -LiteralPath $candidate.FullName -Raw) + if (-not $declaration.SelectSingleNode('//InnoProductTarget')) { continue } + $productEvaluation = @(& $Dotnet msbuild $candidate.FullName -nologo "-p:Configuration=$Configuration" "-p:ArtifactsPath=$artifacts" ` + '-getProperty:InnoProductTarget,TargetPath,MSBuildProjectName') + if ($LASTEXITCODE -ne 0) { throw "The SDK could not evaluate the declared product '$($candidate.FullName)'." } + $contract = ($productEvaluation -join [Environment]::NewLine | ConvertFrom-Json).Properties + if ($contract.InnoProductTarget -ne $product.InnoNativeTarget) { continue } + $contracts += [pscustomobject]@{ project = $candidate.FullName; target = $contract.InnoProductTarget; output = $contract.TargetPath } + & $Dotnet build $candidate.FullName -c $Configuration "-p:ArtifactsPath=$artifacts" ` + *> (Join-Path $stageRoot "source-product-$($contract.MSBuildProjectName).log") + if ($LASTEXITCODE -ne 0) { throw "The selected product contract '$($candidate.FullName)' did not build." } + } + $contracts | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $stageRoot 'product-contracts.json') -Encoding utf8 + if ($contracts.Count -eq 0) { throw 'No declared product contract matches the selected target.' } + } + & $Dotnet build $selection -c $Configuration @buildArguments *> (Join-Path $stageRoot 'build.log') + if ($LASTEXITCODE -ne 0) { throw 'The selected test graph did not build.' } + } + & (Join-Path $PSScriptRoot 'Invoke-IsolatedTests.ps1') -Dotnet $Dotnet -Solution $selection ` + -Configuration $Configuration -ResultsDirectory $stageRoot -ArtifactsPath $artifacts ` + -Target $product.InnoNativeTarget -ToolTarget $product.InnoToolTarget -ProductProperties $product.InnoProductBuildProperties -Filter $filter ` + -TemporaryDirectory $TemporaryDirectory ` + *> (Join-Path $stageRoot 'execution.log') + $executed = 0 + $observedSuites = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + foreach ($trx in Get-ChildItem -LiteralPath $stageRoot -Filter '*.trx') { + $report = [xml](Get-Content -LiteralPath $trx.FullName -Raw) + $executed += [int]$report.TestRun.ResultSummary.Counters.executed + if ([int]$report.TestRun.ResultSummary.Counters.executed -gt 0) { + foreach ($method in $report.TestRun.TestDefinitions.UnitTest.TestMethod) { + [void]$observedSuites.Add([IO.Path]::GetFileNameWithoutExtension($method.codeBase)) + } + } + if ([int]$report.TestRun.ResultSummary.Counters.failed -ne 0 -or $report.TestRun.ResultSummary.outcome -eq 'Aborted') { + throw "The test result '$($trx.Name)' did not pass." + } + } + if ($executed -le 0) { throw 'The selected suite executed no tests.' } + foreach ($name in $names) { + if (-not $observedSuites.Contains($name)) { throw "The selected project '$name' executed no tests." } + } + Save-Result $selected 'Passed' "$executed test cases executed; consult TRX and isolation logs." + } catch { + Save-Result $selected 'Failed' $_.Exception.Message + } +} +if (@($records | Where-Object status -ne 'Passed').Count -ne 0) { + throw "Acceptance has failed or blocked stages. See $results/summary.json." +} diff --git a/tests/Invoke-EditorUiProductJourney.ps1 b/tests/Invoke-EditorUiProductJourney.ps1 new file mode 100644 index 000000000..df16264ec --- /dev/null +++ b/tests/Invoke-EditorUiProductJourney.ps1 @@ -0,0 +1,116 @@ +param( + [Parameter(Mandatory)][string]$EditorAssembly, + [Parameter(Mandatory)][string]$ProjectDirectory, + [Parameter(Mandatory)][string]$ResultsDirectory, + [string]$Dotnet = 'dotnet', + [Parameter(Mandatory)][string]$GraphicsApi, + [ValidateSet('Workflows', 'Lifecycle')][string]$Scenario = 'Workflows', + [int]$Frames = 30000, + [int]$TimeoutSeconds = 600 +) + +$ErrorActionPreference = 'Stop' +if ($Frames -lt 120 -or $TimeoutSeconds -le 0) { throw 'The journey needs at least 120 frames and a positive deadline.' } +$repository = Split-Path $PSScriptRoot -Parent +$source = [IO.Path]::GetFullPath($ProjectDirectory) +$editor = [IO.Path]::GetFullPath($EditorAssembly) +$Dotnet = (Get-Command $Dotnet -ErrorAction Stop).Source +if (-not (Test-Path -LiteralPath $editor -PathType Leaf)) { throw 'Build the selected official Editor output first.' } +if (-not (Test-Path -LiteralPath (Join-Path $source 'Assets') -PathType Container)) { throw 'A complete project input is required.' } +$results = [IO.Path]::GetFullPath($ResultsDirectory) +if (Test-Path -LiteralPath $results) { throw 'Use a new result owner; prior evidence must not be overwritten.' } +New-Item -ItemType Directory -Path $results | Out-Null +$project = Join-Path $results 'Project' +New-Item -ItemType Directory -Path $project | Out-Null +foreach ($name in @('Assets', 'Plugins')) { + $directory = Join-Path $source $name + if (Test-Path -LiteralPath $directory) { Copy-Item -LiteralPath $directory -Destination $project -Recurse } +} +foreach ($file in Get-ChildItem -LiteralPath $source -File | Where-Object { $_.Extension -eq '.inno' }) { + Copy-Item -LiteralPath $file.FullName -Destination $project +} +Copy-Item -LiteralPath (Join-Path $repository 'src/composition/editor/hosting/Inno.Editor.Hosting/Layout/DefaultEditorLayout.ini') ` + -Destination (Join-Path $project 'editor.ini') +$fixtures = Join-Path $project 'Assets/Acceptance' +New-Item -ItemType Directory -Force -Path $fixtures | Out-Null +$fixtureNames = if ($Scenario -eq 'Lifecycle') { @('PanelLifecycle.editor.cs') } else { @('WorkflowState.cs.txt', 'WorkflowJourney.editor.cs.txt') } +foreach ($name in $fixtureNames) { + Copy-Item -LiteralPath (Join-Path $PSScriptRoot "fixtures/editor-ui/$name") -Destination (Join-Path $fixtures ($name -replace '\.txt$', '')) +} +Get-ChildItem -LiteralPath $project -File -Recurse | ForEach-Object { + [pscustomobject]@{ path = [IO.Path]::GetRelativePath($project, $_.FullName); sha256 = (Get-FileHash -LiteralPath $_.FullName).Hash } +} | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $results 'inputs.json') -Encoding utf8 + +$start = [Diagnostics.ProcessStartInfo]::new($Dotnet) +$start.UseShellExecute = $false +$start.CreateNoWindow = $true +$start.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden +$start.RedirectStandardOutput = $true +$start.RedirectStandardError = $true +$start.WorkingDirectory = $project +foreach ($argument in @($editor, $project, '--window-visible', 'false', '--smoke-frames', [string]$Frames, + '--graphics-api', $GraphicsApi)) { $start.ArgumentList.Add($argument) } +$process = [Diagnostics.Process]::new() +$process.StartInfo = $start +$code = $null +$failure = $null +$started = $false +try { + if (-not $process.Start()) { throw 'The owned Editor process did not start.' } + $started = $true + $output = $process.StandardOutput.ReadToEndAsync() + $errors = $process.StandardError.ReadToEndAsync() + if (-not $process.WaitForExit($TimeoutSeconds * 1000)) { throw 'The owned Editor journey exceeded its deadline.' } + $code = $process.ExitCode + if ($code -ne 0) { throw "The owned Editor exited with code $code." } + $evidence = Join-Path $project $(if ($Scenario -eq 'Lifecycle') { 'Library/Acceptance/panel-lifecycle.tsv' } else { 'Library/Acceptance/workflow-journey.tsv' }) + if (-not (Test-Path -LiteralPath $evidence)) { throw 'The source extension did not complete the journey.' } + $verified = @(Get-Content -LiteralPath $evidence) + if ($Scenario -eq 'Workflows') { + if ($verified.Count -ne 8) { throw "Expected eight completed workflow stages, got $($verified.Count)." } + } else { + $panels = @(Import-Csv -LiteralPath $evidence -Delimiter "`t" | Where-Object panel -ne 'protocol') + if ($panels.Count -lt 9 -or @($panels | Where-Object 'completed-open-close-cycles' -ne '100').Count -ne 0) { + throw 'Every actual product panel must complete 100 open/close cycles.' + } + $resources = @(Import-Csv -LiteralPath (Join-Path $project 'Library/Acceptance/panel-lifecycle-resources.tsv') -Delimiter "`t") + $preparation = @(Import-Csv -LiteralPath (Join-Path $project 'Library/Acceptance/panel-lifecycle-preparation.tsv') -Delimiter "`t") + if ($preparation.Count -ne 2 -or $preparation[0].phase -ne 'before-first-panel-draw' -or $preparation[1].phase -ne 'after-120-drawn-frames') { + throw 'The lifecycle run must record cold resource preparation separately from its repeated open/close baseline.' + } + if ($resources.Count -ne 11 -or [int]$resources[0].'completed-cycles' -ne 0 -or [int]$resources[-1].'completed-cycles' -ne 100) { + throw 'The lifecycle run did not record its complete full-GC resource series.' + } + $growth = [long]$resources[-1].'managed-bytes-after-full-gc' - [long]$resources[0].'managed-bytes-after-full-gc' + if ($growth -gt 4MB) { throw "Panel lifecycle retained $growth bytes beyond its 4 MiB final live-memory budget." } + if (@($resources | Where-Object { [long]$_.'pending-events' -ne 0 }).Count -ne 0) { + throw 'Panel lifecycle retained pending events after closed-frame reconciliation.' + } + } +} +catch { $failure = $_ } +finally { + if ($started -and -not $process.HasExited) { + $process.Kill($true) + if (-not $process.WaitForExit(10000)) { throw 'The owned Editor process did not terminate.' } + } + if ($output) { + $output.WaitAsync([TimeSpan]::FromSeconds(10)).GetAwaiter().GetResult() | + Set-Content -LiteralPath (Join-Path $results 'stdout.log') -Encoding utf8 + } + if ($errors) { + $errors.WaitAsync([TimeSpan]::FromSeconds(10)).GetAwaiter().GetResult() | + Set-Content -LiteralPath (Join-Path $results 'stderr.log') -Encoding utf8 + } + $process.Dispose() + [pscustomobject]@{ + revision = (& git -C $repository rev-parse HEAD).Trim() + workingChanges = @(& git -C $repository status --porcelain=v1) + editor = $editor; graphicsApi = $GraphicsApi; frames = $Frames; exitCode = $code; scenario = $Scenario + status = $(if ($failure) { 'Failed' } else { 'Passed' }) + diagnostic = $(if ($failure) { $failure.Exception.Message } elseif ($Scenario -eq 'Lifecycle') { 'Every actual product panel completed 100 cycles and eleven full-GC resource observations.' } else { 'Eight actual-product feature workflows completed.' }) + limitation = 'Public feature commands with real panel drawing; native pointer/keyboard contracts and physical-device experience are separate evidence.' + } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $results 'result.json') -Encoding utf8 +} +if ($failure) { throw $failure } +Write-Output "Verified product ${Scenario}: $results" diff --git a/tests/Invoke-IsolatedTests.ps1 b/tests/Invoke-IsolatedTests.ps1 index 288979140..962ac8f27 100644 --- a/tests/Invoke-IsolatedTests.ps1 +++ b/tests/Invoke-IsolatedTests.ps1 @@ -3,9 +3,11 @@ param( [string]$ResultsDirectory = 'artifacts/acceptance/ci-tests', [string]$Dotnet = 'dotnet', [string]$Target, + [string]$ToolTarget, [string]$ProductProperties, [string]$ArtifactsPath, [string]$Filter, + [string]$TemporaryDirectory, [string]$Solution = 'InnoEngine.sln' ) @@ -14,13 +16,26 @@ $Dotnet = (Get-Command $Dotnet -ErrorAction Stop).Source $sdkDirectory = Split-Path $Dotnet -Parent $sdkEnvironment = @('--environment', "DOTNET_ROOT=$sdkDirectory", '--environment', "PATH=$sdkDirectory$([IO.Path]::PathSeparator)$env:PATH") -# Project-mode dotnet test supplies DOTNET_HOST_PATH itself; assembly-mode VSTest needs the explicit host. -$assemblyEnvironment = @($sdkEnvironment) + @('--environment', "DOTNET_HOST_PATH=$Dotnet") +# Assembly-mode VSTest derives DOTNET_ROOT from the explicit host; passing both duplicates its environment key. +$assemblyEnvironment = @('--environment', "DOTNET_HOST_PATH=$Dotnet", + '--environment', "PATH=$sdkDirectory$([IO.Path]::PathSeparator)$env:PATH") $repositoryRoot = Split-Path $PSScriptRoot -Parent $solutionPath = [System.IO.Path]::GetFullPath($Solution, $repositoryRoot) $resultsRoot = [System.IO.Path]::GetFullPath($ResultsDirectory, $repositoryRoot) New-Item -ItemType Directory -Force -Path $resultsRoot | Out-Null +if ($TemporaryDirectory) { + $temporaryOwner = [IO.Path]::GetFullPath($TemporaryDirectory, $repositoryRoot) + New-Item -ItemType Directory -Force -Path $temporaryOwner | Out-Null + $processTemp = Join-Path $temporaryOwner ([Guid]::NewGuid().ToString('N').Substring(0, 12)) + New-Item -ItemType Directory -Path $processTemp | Out-Null + @{ directory = $processTemp; purpose = 'Owned temporary files for the selected test processes.' } | + ConvertTo-Json | Set-Content -LiteralPath (Join-Path $resultsRoot 'temporary-owner.json') -Encoding utf8 + $tempEnvironment = @('--environment', "TMP=$processTemp", '--environment', "TEMP=$processTemp", + '--environment', "TMPDIR=$processTemp") + $sdkEnvironment += $tempEnvironment + $assemblyEnvironment += $tempEnvironment +} $checks = [System.Collections.Generic.List[object]]::new() function Add-CheckResult( @@ -53,8 +68,16 @@ if ($Filter) { } if ($Target) { if (-not $ProductProperties) { throw 'A target test build requires explicit product properties.' } - $productArguments += @("-p:InnoNativeTarget=$Target", "-p:InnoToolTarget=$Target", - "-p:InnoProductBuildProperties=$ProductProperties", "-p:InnoTestNativeTarget=$Target", '-p:InnoTestNativeProduct=editor') + if (-not $ToolTarget) { + $entry = Join-Path $repositoryRoot 'entry/InnoEngine.Editor/InnoEngine.Editor.csproj' + $selectedTool = @(& $Dotnet msbuild $entry -nologo -getProperty:InnoToolTarget) + if ($LASTEXITCODE -ne 0 -or $selectedTool.Count -ne 1 -or -not $selectedTool[0]) { + throw 'The SDK-selected development host did not provide a tool target.' + } + $ToolTarget = $selectedTool[0].Trim() + } + $productArguments += @("-p:InnoNativeTarget=$Target", "-p:InnoToolTarget=$ToolTarget", + "-p:InnoProductBuildProperties=$ProductProperties") } # Process-global Native state and deliberately retained Faulted owners require separate hosts. @@ -100,7 +123,9 @@ foreach ($project in $projects) { $assembly = $targetPath[0] $listed = @(& $Dotnet test $assembly --list-tests --filter $isolatedFilter @assemblyEnvironment 2>&1) $discoveryExit = $LASTEXITCODE - if ($discoveryExit -ne 0) { + $listed | Set-Content -LiteralPath (Join-Path $resultsRoot ($project.BaseName + '.discovery.log')) -Encoding utf8 + $discoveryErrors = @($listed | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }) + if ($discoveryExit -ne 0 -or $discoveryErrors.Count -ne 0) { Add-CheckResult 'Discovery' $project.FullName 'Failed' $discoveryExit ($listed -join [Environment]::NewLine) continue } diff --git a/tests/Invoke-WikiChecks.ps1 b/tests/Invoke-WikiChecks.ps1 index 094f3ed9c..e6cd2758b 100644 --- a/tests/Invoke-WikiChecks.ps1 +++ b/tests/Invoke-WikiChecks.ps1 @@ -6,7 +6,8 @@ param( [string]$Dotnet = 'dotnet', [ValidateSet('Debug', 'Release')] [string]$Configuration = 'Debug', - [string]$ResultsDirectory = 'artifacts/acceptance/wiki-gate' + [string]$ResultsDirectory = 'artifacts/acceptance/wiki-gate', + [string]$ArtifactsPath ) $ErrorActionPreference = 'Stop' @@ -16,7 +17,14 @@ $project = [IO.Path]::GetFullPath($ProjectDirectory, $repository) $results = [IO.Path]::GetFullPath($ResultsDirectory, $repository) New-Item -ItemType Directory -Force -Path $results | Out-Null $entryProject = Join-Path $repository 'entry/InnoEngine.Build/InnoEngine.Build.csproj' -$entry = @(& $Dotnet msbuild $entryProject -nologo -m:1 -nr:false "-p:Configuration=$Configuration" -getProperty:TargetPath) +$outputArguments = @() +$verificationArguments = @() +if ($ArtifactsPath) { + $outputOwner = [IO.Path]::GetFullPath($ArtifactsPath, $repository) + $outputArguments = @("-p:ArtifactsPath=$outputOwner") + $verificationArguments = @('--artifacts-path', $outputOwner) +} +$entry = @(& $Dotnet msbuild $entryProject -nologo -m:1 -nr:false "-p:Configuration=$Configuration" -getProperty:TargetPath @outputArguments) if ($LASTEXITCODE -ne 0 -or $entry.Count -ne 1 -or -not (Test-Path -LiteralPath $entry[0])) { throw 'Build the selected Build entry and product references before running the Wiki gate.' } @@ -38,7 +46,7 @@ $runtimeReference = Read-Reference 'Inno.ScriptApi.Runtime' $editorReference = Read-Reference 'Inno.ScriptApi.Editor' @{ runtime = $runtimeReference; editor = $editorReference } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $results 'script-references.json') -Encoding utf8 -& $Dotnet $entry[0] verify $repository --dotnet $Dotnet --configuration $Configuration --wiki --wiki-target $Target ` +& $Dotnet $entry[0] verify $repository --dotnet $Dotnet --configuration $Configuration --wiki --wiki-target $Target @verificationArguments ` --wiki-runtime-reference $runtimeReference --wiki-editor-reference $editorReference ` --wiki-api (Join-Path $results 'api.json') --wiki-examples (Join-Path $results 'examples.json') 2>&1 | Tee-Object -FilePath (Join-Path $results 'wiki.log') diff --git a/tests/RepositoryInput.targets b/tests/RepositoryInput.targets index 4a72b7c79..81bc8ee98 100644 --- a/tests/RepositoryInput.targets +++ b/tests/RepositoryInput.targets @@ -3,4 +3,11 @@ + + + + + + + diff --git a/tests/assets/Inno.Assets.Pipeline.Tests/AssetFileSystemTests.cs b/tests/assets/Inno.Assets.Pipeline.Tests/AssetFileSystemTests.cs index 3f6b41df7..a33cf931f 100644 --- a/tests/assets/Inno.Assets.Pipeline.Tests/AssetFileSystemTests.cs +++ b/tests/assets/Inno.Assets.Pipeline.Tests/AssetFileSystemTests.cs @@ -13,6 +13,91 @@ namespace Inno.Assets.Pipeline.Tests; public sealed class AssetFileSystemTests { + [Fact] + public void RefreshPreservesUnchangedEntryInstancesAndRuntimeIdentities() + { + string root = CreateRoot(); + try + { + Directory.CreateDirectory(Path.Combine(root, "Folder")); + File.WriteAllText(Path.Combine(root, "Folder", "value.txt"), "value"); + using var fileSystem = new AssetFileSystem(root, autoStart: false); + AssetFileEntry[] previous = fileSystem.GetEntries().ToArray(); + var identities = previous.Select(static entry => entry.identity.runtimeIdentity).ToArray(); + fileSystem.Refresh(); + for (int index = 0; index < previous.Length; index++) + { + Assert.True(fileSystem.TryGetEntry(previous[index].assetPath, out AssetFileEntry current)); + Assert.Same(previous[index], current); + Assert.Equal(identities[index], current.identity.runtimeIdentity); + } + } + finally + { + DeleteRoot(root); + } + } + + [Fact] + public void FailedEnumerationKeepsThePreviousCompleteIndexAndItsIdentities() + { + string root = CreateRoot(); + string moved = root + ".temporarily-moved"; + try + { + File.WriteAllText(Path.Combine(root, "value.txt"), "value"); + using var fileSystem = new AssetFileSystem(root, autoStart: false); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("value.txt"), out AssetFileEntry previous)); + var identity = previous.identity.runtimeIdentity; + Directory.Move(root, moved); + try + { + Assert.Throws(() => fileSystem.Refresh()); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("value.txt"), out AssetFileEntry retained)); + Assert.Same(previous, retained); + Assert.Equal(identity, retained.identity.runtimeIdentity); + } + finally + { + Directory.Move(moved, root); + } + fileSystem.Refresh(); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("value.txt"), out AssetFileEntry restored)); + Assert.Same(previous, restored); + } + finally + { + DeleteRoot(root); + } + } + + [Fact] + public void RefreshRetiresRemovedEntriesAndPreservesUnaffectedEntries() + { + string root = CreateRoot(); + try + { + File.WriteAllText(Path.Combine(root, "keep.txt"), "keep"); + File.WriteAllText(Path.Combine(root, "remove.txt"), "remove"); + using var fileSystem = new AssetFileSystem(root, autoStart: false); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("keep.txt"), out AssetFileEntry keep)); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("remove.txt"), out AssetFileEntry removed)); + File.Delete(Path.Combine(root, "remove.txt")); + File.WriteAllText(Path.Combine(root, "added.txt"), "added"); + fileSystem.Refresh(); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("keep.txt"), out AssetFileEntry retained)); + Assert.Same(keep, retained); + Assert.False(fileSystem.TryGetEntry(AssetPath.Project("remove.txt"), out _)); + Assert.Null(removed.identity.runtimeIdentity); + Assert.True(fileSystem.TryGetEntry(AssetPath.Project("added.txt"), out AssetFileEntry added)); + Assert.NotNull(added.identity.runtimeIdentity); + } + finally + { + DeleteRoot(root); + } + } + [Fact] public void Queries_RejectRootedAndTraversalPaths() { diff --git a/tests/assets/Inno.Assets.Pipeline.Tests/AssetLoaderTests.cs b/tests/assets/Inno.Assets.Pipeline.Tests/AssetLoaderTests.cs index 02ee41abc..8c39415f8 100644 --- a/tests/assets/Inno.Assets.Pipeline.Tests/AssetLoaderTests.cs +++ b/tests/assets/Inno.Assets.Pipeline.Tests/AssetLoaderTests.cs @@ -1050,7 +1050,7 @@ public void PathAndPersistentIdLoads_ReturnCanonicalInstance() } [Fact] - public void CurrentCatalogStamp_LoadsArtifactWithoutOpeningSourceContent() + public void BlockedSourceVerificationRetainsLastGoodArtifactAndReportsImportFailure() { using TestWorkspace workspace = new(); workspace.WriteText("Text/cached.txt", "cached"); @@ -1068,6 +1068,9 @@ public void CurrentCatalogStamp_LoadsArtifactWithoutOpeningSourceContent() loader.Load(AssetPath.Project("Text/cached.txt"), typeof(TextAsset))); Assert.Equal("cached", asset.content); + Assert.True(loader.TryGetInfo(AssetPath.Project("Text/cached.txt"), out AssetInfo? info)); + Assert.Equal(AssetImportStatus.Failed, info!.status); + Assert.Contains(info.diagnostics, diagnostic => diagnostic.Contains("IOException", StringComparison.Ordinal)); } [Fact] @@ -1092,7 +1095,7 @@ public void ChangedStampWithEqualContent_DoesNotReimportCanonicalAsset() } [Fact] - public void CurrentDependencyStamp_DoesNotOpenSourceDependencyContent() + public void BlockedDependencyVerificationRetainsLastGoodArtifactAndReportsImportFailure() { using TestWorkspace workspace = new(); workspace.WriteText("Import/schema.inc", "schema"); @@ -1109,6 +1112,8 @@ public void CurrentDependencyStamp_DoesNotOpenSourceDependencyContent() loader.Load(AssetPath.Project("Import/root.importgraph"), typeof(ImportGraphAsset))); Assert.False(asset.isMissing); + Assert.True(loader.TryGetInfo(AssetPath.Project("Import/root.importgraph"), out AssetInfo? info)); + Assert.Equal(AssetImportStatus.Failed, info!.status); } [Fact] @@ -1136,7 +1141,32 @@ public async Task ConcurrentAsyncLoads_ShareImportAndCancellationOnlyCancelsOneW } [Fact] - public void StaleSource_ReimportsAndUpdatesCanonicalInstanceInPlace() + public void ChangedDependencyWithPreservedTimestampReimportsTheCanonicalAsset() + { + using TestWorkspace workspace = new(); + workspace.WriteText("Import/schema.inc", "first"); + workspace.WriteText("Import/root.importgraph", "Import/schema.inc"); + using var loader = workspace.CreateLoader(m_types, m_serialization, m_identities, m_diagnostics, m_logs); + ImportGraphAsset before = Assert.IsType( + loader.Load(AssetPath.Project("Import/root.importgraph"), typeof(ImportGraphAsset))); + long version = before.contentVersion; + string dependency = workspace.SourcePath("Import/schema.inc"); + DateTime timestamp = System.IO.File.GetLastWriteTimeUtc(dependency); + workspace.WriteText("Import/schema.inc", "other"); + System.IO.File.SetLastWriteTimeUtc(dependency, timestamp); + + ImportGraphAsset after = Assert.IsType( + loader.Load(AssetPath.Project("Import/root.importgraph"), typeof(ImportGraphAsset))); + + Assert.Same(before, after); + Assert.Equal(version + 1, after.contentVersion); + Assert.False(after.isMissing); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void StaleSource_ReimportsAndUpdatesCanonicalInstanceInPlace(bool preserveTimestamp) { using TestWorkspace workspace = new(); workspace.WriteText("Text/reload.txt", "one"); @@ -1144,8 +1174,12 @@ public void StaleSource_ReimportsAndUpdatesCanonicalInstanceInPlace() TextAsset before = Assert.IsType(loader.Load(AssetPath.Project("Text/reload.txt"), typeof(TextAsset))); Guid persistentId = before.identity.persistentId; long version = before.contentVersion; + string sourcePath = workspace.SourcePath("Text/reload.txt"); + DateTime sourceTime = System.IO.File.GetLastWriteTimeUtc(sourcePath); workspace.WriteText("Text/reload.txt", "two"); + if (preserveTimestamp) + System.IO.File.SetLastWriteTimeUtc(sourcePath, sourceTime); TextAsset after = Assert.IsType(loader.Load(AssetPath.Project("Text/reload.txt"), typeof(TextAsset))); Assert.Same(before, after); diff --git a/tests/audio/Inno.Audio.Tests/AudioScriptingApiTests.cs b/tests/audio/Inno.Audio.Tests/AudioScriptingApiTests.cs index ddff1b12c..f4c64dedc 100644 --- a/tests/audio/Inno.Audio.Tests/AudioScriptingApiTests.cs +++ b/tests/audio/Inno.Audio.Tests/AudioScriptingApiTests.cs @@ -72,8 +72,15 @@ public async Task FacadeMixerProviderAndSceneComponentsCompileThroughLogicalName Path.Combine(projectRoot, "Library"), scan, host.modules.generations); + string dotnetHost = Path.Combine( + Path.GetFullPath("../../..", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); var compiler = new ScriptCompiler( - new ScriptCompilerOptions { projectRootDirectory = projectRoot }, + new ScriptCompilerOptions + { + projectRootDirectory = projectRoot, + compilationEnvironment = new Inno.Build.SdkScriptCompilationEnvironment(dotnetHost, projectRoot) + }, assets, plugins); File.WriteAllText(Path.Combine(projectRoot, "Assets", "AudioProbe.cs"), """ diff --git a/tests/audio/Inno.Audio.Tests/Inno.Audio.Tests.csproj b/tests/audio/Inno.Audio.Tests/Inno.Audio.Tests.csproj index 17c203d97..09d6d4a3a 100644 --- a/tests/audio/Inno.Audio.Tests/Inno.Audio.Tests.csproj +++ b/tests/audio/Inno.Audio.Tests/Inno.Audio.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/build/Inno.Build.Tests/BuildOutputOwnershipTests.cs b/tests/build/Inno.Build.Tests/BuildOutputOwnershipTests.cs index d2273a094..3e5f9c5ff 100644 --- a/tests/build/Inno.Build.Tests/BuildOutputOwnershipTests.cs +++ b/tests/build/Inno.Build.Tests/BuildOutputOwnershipTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Text.Json; using System.Threading.Tasks; using System.Xml.Linq; @@ -34,13 +35,18 @@ public async Task ConcurrentSdkRequestsKeepExplicitOutputOwnersForTheSameTarget( .Save(project); string first = Path.Combine(root, "requests", "first"); string second = Path.Combine(root, "requests", "second"); - await Task.WhenAll(Build(project, first), Build(project, second)); - foreach (string owner in new[] { first, second }) + string[][] outputs = await Task.WhenAll(Build(project, first), Build(project, second)); + for (int index = 0; index < outputs.Length; index++) { - Assert.True(File.Exists(Path.Combine(owner, "bin", "Fixture", "debug", "Fixture.dll"))); - Assert.True(File.Exists(Path.Combine(owner, "obj", "Fixture", "project.assets.json"))); - Assert.True(File.Exists(Path.Combine(owner, "obj", "Fixture", "debug", "Fixture.dll"))); + string owner = Path.GetFullPath(index == 0 ? first : second) + Path.DirectorySeparatorChar; + Assert.Equal(3, outputs[index].Length); + foreach (string path in outputs[index]) + { + Assert.StartsWith(owner, path, StringComparison.Ordinal); + Assert.True(File.Exists(path), $"The SDK-selected output does not exist: {path}"); + } } + Assert.All(outputs[0], path => Assert.DoesNotContain(path, outputs[1])); Assert.False(Directory.Exists(Path.Combine(root, "bin"))); Assert.False(Directory.Exists(Path.Combine(root, "obj"))); } @@ -50,7 +56,7 @@ public async Task ConcurrentSdkRequestsKeepExplicitOutputOwnersForTheSameTarget( } } - private static async Task Build( + private static async Task Build( string project, string outputOwner ) { @@ -61,14 +67,37 @@ string outputOwner WorkingDirectory = Path.GetDirectoryName(project)!, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true }; + string evaluated = Path.Combine(Path.GetDirectoryName(outputOwner)!, Path.GetFileName(outputOwner) + ".json"); + Directory.CreateDirectory(Path.GetDirectoryName(evaluated)!); foreach (string argument in new[] { "build", project, "--artifacts-path", outputOwner, "-p:InnoNativeTarget=fixture-target", "-p:InnoNativeBuildFingerprint=fixture-fingerprint", + "-target:Build", "-getProperty:TargetPath,ProjectAssetsFile", "-getItem:IntermediateAssembly", "-getResultOutputFile:" + evaluated, "--disable-build-servers", "-m:1", "-nodeReuse:false", "-v:minimal" }) start.ArgumentList.Add(argument); using Process process = Process.Start(start) ?? throw new InvalidOperationException("The SDK did not start."); Task output = process.StandardOutput.ReadToEndAsync(); Task error = process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); + try + { + await process.WaitForExitAsync().WaitAsync(TimeSpan.FromMinutes(2)); + } + finally + { + if (!process.HasExited) + { + process.Kill(entireProcessTree: true); + await process.WaitForExitAsync(); + } + } Assert.True(process.ExitCode == 0, await output + await error); + using JsonDocument result = JsonDocument.Parse(File.ReadAllText(evaluated)); + JsonElement properties = result.RootElement.GetProperty("Properties"); + JsonElement intermediate = Assert.Single(result.RootElement.GetProperty("Items").GetProperty("IntermediateAssembly").EnumerateArray()); + return + [ + Path.GetFullPath(properties.GetProperty("TargetPath").GetString()!), + Path.GetFullPath(properties.GetProperty("ProjectAssetsFile").GetString()!), + Path.GetFullPath(intermediate.GetProperty("FullPath").GetString()!) + ]; } } diff --git a/tests/build/Inno.Build.Tests/BuildPipelineTests.ExportPresentation.cs b/tests/build/Inno.Build.Tests/BuildPipelineTests.ExportPresentation.cs new file mode 100644 index 000000000..f40a6707d --- /dev/null +++ b/tests/build/Inno.Build.Tests/BuildPipelineTests.ExportPresentation.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using BGCS.Runtime; +using Inno.Build.Managed; +using Inno.Core.Input; +using Inno.Core.Logging; +using Inno.Editor.Core; +using Inno.Editor.Exporting; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; +using EditorUiSession = Inno.Editor.ImGui.EditorUiSession; + +namespace Inno.Build.Tests; + +public sealed partial class BuildPipelineTests +{ + [Fact] + [Trait("ExternalDependency", "NativeProduct")] + public unsafe void ExportFormBalancesItsTablesWhenARegisteredTargetLabelFails() + { + var target = new ExportLabelTarget(); + var pipeline = new BuildPipeline(m_assets, m_plugins, m_settings, m_engine.serialization, + m_engine.generations, m_compiler, m_supportPackRoot, + [new GameBuildTargetBinding(target, target)], CreateManagedDeployments()); + var defaults = BuildSettings.CreateDefault("Test Game", "Scenes/Startup.iscene", target.id); + var settings = new BuildSettingsStore(System.IO.Path.Combine(m_projectRoot, "Settings.Build.inno"), + m_engine.serialization, defaults); + m_engine.modules.Register("Tests.Exporting", [typeof(EditorExportService).Assembly, + Assembly.Load(new AssemblyName("Inno.Editor.Workbench"))]); + var context = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(KeyModifier.Control, "Ctrl")); + var logs = new ExportLogSink(); + ImGuiNativeContext* native = NativeGui.Raw.CreateContext(null); + try + { + m_engine.logs.RegisterSink(logs); + using var session = new EditorUiSession(); + using IDisposable scope = session.Enter(); + using var runtime = new EditorInteractionRuntime(context, m_engine.types, m_engine.logs, + [pipeline, settings, m_settings, session]); + runtime.Start(); + Assert.True(runtime.interactions.For("editor/main-menu").Execute("export/game")); + EditorModalExtension modal = Assert.Single(runtime.modals, item => item.id == "export.game"); + ImGuiIO* io = NativeGui.Raw.GetIO(); + io->DisplaySize = new ImVec2C { X = 1200f, Y = 900f }; + io->DeltaTime = 1f / 60f; + io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; + io->Fonts->RendererHasTextures = 1; + Assert.True(DrawExportFrame(context, modal)); + target.reject = true; + Assert.False(DrawExportFrame(context, modal)); + Assert.False(modal.TryGetPresentation(out _)); + m_engine.logs.Flush(); + Assert.Contains(logs.messages, message => message.Contains("Export target label failed", StringComparison.Ordinal)); + target.reject = false; + m_engine.types.Rebuild(); + runtime.Update(new EditorFrame(1f / 60f, 1f, true)); + Assert.True(runtime.interactions.For("editor/main-menu").Execute("export/game")); + modal = Assert.Single(runtime.modals, item => item.id == "export.game"); + Assert.True(DrawExportFrame(context, modal)); + Assert.True(DrawExportFrame(context, modal)); + } + finally + { + m_engine.logs.UnregisterSink(logs); + NativeGui.Raw.DestroyContext(native); + } + } + + private static unsafe bool DrawExportFrame( + EditorContext context, + EditorModalExtension modal + ) { + NativeGui.NewFrame(); + NativeGui.SetNextWindowPos(new Vector2(20f), (int)ImGuiCond.Always); + NativeGui.SetNextWindowSize(new Vector2(1000f, 800f), (int)ImGuiCond.Always); + using NativeStringLease title = Utf8Codec.EncodeNullTerminated("Export exception ownership", allowsNull: false); + _ = NativeGui.Raw.Begin((sbyte*)title.pointer, null, (int)ImGuiWindowFlags.NoSavedSettings); + try + { + bool drawn = modal.Draw(context); + bool leaked = NativeGui.Raw.GetCurrentTable() != null; + // Retire only a table opened by this fixture's modal before reporting a failed regression. + if (leaked) + NativeGui.EndTable(); + Assert.False(leaked, "A modal failure left its own native Table active."); + return drawn; + } + finally + { + NativeGui.End(); + NativeGui.Render(); + } + } + + private sealed class ExportLogSink : ILogSink + { + internal List messages { get; } = []; + public void Receive(LogEntry entry) => messages.Add(entry.message); + } + + private sealed class ExportLabelTarget : IGameBuildTarget, IGameContentCompiler + { + internal bool reject { get; set; } + public BuildTargetId id => new("fixture-export"); + public BuildTargetId target => id; + public ManagedDeploymentId defaultManagedDeployment => ManagedDeploymentId.coreClr; + public string runtimeIdentifier => "fixture"; + public string displayName => reject ? throw new InvalidOperationException("Export target label failed.") : "Fixture target"; + public void Validate(string directory) => throw new InvalidOperationException("This presentation fixture cannot validate products."); + + public ValueTask PackageAsync( + GameBuildPackageContext context, + CancellationToken cancellationToken = default + ) => throw new InvalidOperationException("This presentation fixture cannot package products."); + + public ValueTask CompileAsync( + GameBuildContentContext context, + CancellationToken cancellationToken = default + ) => throw new InvalidOperationException("This presentation fixture cannot compile content."); + } +} diff --git a/tests/build/Inno.Build.Tests/BuildPipelineTests.cs b/tests/build/Inno.Build.Tests/BuildPipelineTests.cs index e8b472ef8..02248f6f2 100644 --- a/tests/build/Inno.Build.Tests/BuildPipelineTests.cs +++ b/tests/build/Inno.Build.Tests/BuildPipelineTests.cs @@ -25,6 +25,7 @@ using Inno.Core.Settings; using Inno.Extensibility.Types; using Inno.Editor.Core; +using Inno.Editor.Exporting; using Inno.Editor.Interactions; using Inno.Native.ImGui; using Inno.Plugins.Authoring; @@ -38,7 +39,7 @@ namespace Inno.Build.Tests; [Collection("Build pipeline serialization")] -public sealed class BuildPipelineTests : IDisposable +public sealed partial class BuildPipelineTests : IDisposable { private readonly AssetPipeline m_assets; private readonly EngineHost m_engine; @@ -102,7 +103,8 @@ public BuildPipelineTests() m_compiler = new ScriptCompiler( new ScriptCompilerOptions { - projectRootDirectory = m_projectRoot + projectRootDirectory = m_projectRoot, + compilationEnvironment = new Inno.Build.SdkScriptCompilationEnvironment(Path.Combine(Path.GetFullPath("../../..", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()), OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"), m_projectRoot) }, m_assets, m_plugins); @@ -693,7 +695,10 @@ public unsafe void EditorShutdownDrainsSupportPackCleanupBeforeReleasingExportSe var defaults = BuildSettings.CreateDefault("Test Game", "Scenes/Startup.iscene", new BuildTargetId("macos-arm64")); var buildSettings = new BuildSettingsStore( Path.Combine(m_projectRoot, "Settings.Build.inno"), m_engine.serialization, defaults); - m_engine.modules.Register("Tests.Exporting", [Assembly.Load(new AssemblyName("Inno.Editor.Exporting"))]); + m_engine.modules.Register("Tests.Exporting", [ + typeof(EditorExportService).Assembly, + Assembly.Load(new AssemblyName("Inno.Editor.Workbench")) + ]); var context = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); using var runtime = new EditorInteractionRuntime( context, m_engine.types, m_engine.logs, [pipeline, buildSettings, m_settings]); @@ -704,6 +709,8 @@ public unsafe void EditorShutdownDrainsSupportPackCleanupBeforeReleasingExportSe ImGuiNativeContext* imgui = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = new ImVec2C { X = 1200f, Y = 900f }; io->DeltaTime = 1f / 60f; @@ -737,14 +744,28 @@ public unsafe void EditorShutdownDrainsSupportPackCleanupBeforeReleasingExportSe Assert.True(plugin.TryGetPresentation(out EditorModalExtension.Presentation pluginPresentation)); Assert.False(pluginPresentation.isVisible); - Assert.True(runtime.interactions.TryGetModule(out EditorModule? module)); + Assert.True(runtime.interactions.TryGetModule(out EditorExportService? module)); Assert.NotNull(module); Assert.Equal("Inno.Editor.Exporting", module.GetType().Assembly.GetName().Name); Assert.Throws(() => module.Stop(context)); Assert.True(provisioner.canceled.Task.IsCompletedSuccessfully); Assert.False(provisioner.completed); provisioner.cleanup.SetResult(); - runtime.Dispose(); + var retirement = System.Diagnostics.Stopwatch.StartNew(); + while (true) + { + try + { + runtime.Dispose(); + break; + } + catch (Exception pending) when (RetirementPendingException.Find(pending) is not null) + { + if (retirement.Elapsed >= TimeSpan.FromSeconds(5)) + throw; + Thread.Sleep(1); + } + } Assert.True(provisioner.completed); m_engine.generations.EnsureReady("verify export retirement drained its services"); } diff --git a/tests/build/Inno.Build.Tests/CompilerToolBuildScopeTests.cs b/tests/build/Inno.Build.Tests/CompilerToolBuildScopeTests.cs new file mode 100644 index 000000000..fa506b7a5 --- /dev/null +++ b/tests/build/Inno.Build.Tests/CompilerToolBuildScopeTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Inno.Build.Toolchains; +using Inno.Testing.Source; +using Microsoft.Build.Logging; +using Xunit; + +namespace Inno.Build.Tests; + +public sealed class CompilerToolBuildScopeTests +{ + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task AnalyzerRuntimeAndTransitiveEdgesShareOneCompilerToolBuild(string configuration) + { + string repository = TestRepository.GetRoot(typeof(CompilerToolBuildScopeTests).Assembly); + string root = Path.Combine(Path.GetTempPath(), "InnoCompilerToolScope", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + try + { + File.Copy(Path.Combine(repository, "global.json"), Path.Combine(root, "global.json")); + foreach (string suffix in new[] { "props", "targets" }) + File.WriteAllText(Path.Combine(root, "Directory.Build." + suffix), + ""); + WriteProject(root, "Tool", """ + + true + + netstandard2.0 + + + """); + WriteProject(root, "Runtime", """ + + net10.0 + + + """); + WriteProject(root, "Analyzer", """ + + net10.0 + + + """); + WriteProject(root, "Root", """ + + net10.0 + + + + + + """); + string dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + for (int run = 0; run < 3; run++) + { + if (run == 2) + File.AppendAllText(Path.Combine(root, "Tool", "Tool.cs"), "\npublic sealed class ChangedToolMarker { }\n"); + string binlog = Path.Combine(root, run + ".binlog"); + await ToolchainEnvironment.RunAsync(dotnet, + ["build", "Root/Root.csproj", "--nologo", "-m:4", "-nodeReuse:false", "-c", configuration, + "-p:ArtifactsPath=" + Path.Combine(root, "managed"), "-p:InnoNativeTarget=fixture-target", "-bl:" + binlog], + root, timeout.Token, DotNetSdkEnvironment.Create(dotnet)); + int compilations = 0; + var identities = new HashSet(StringComparer.Ordinal); + var events = new BinaryLogReplayEventSource(); + events.ProjectStarted += ( + _, + project + ) => { + if (Path.GetFileName(project.ProjectFile) != "Tool.csproj" || !string.IsNullOrEmpty(project.TargetNames)) + return; + Assert.NotNull(project.GlobalProperties); + Assert.DoesNotContain("InnoNativeTarget", project.GlobalProperties!.Keys); + Assert.DoesNotContain("BuildProjectReferences", project.GlobalProperties.Keys); + Assert.DoesNotContain("InnoTaskBootstrapOwner", project.GlobalProperties.Keys); + Assert.DoesNotContain("BuildingInsideVisualStudio", project.GlobalProperties.Keys); + identities.Add(string.Join(";", project.GlobalProperties.OrderBy(static pair => pair.Key) + .Select(static pair => pair.Key + "=" + pair.Value))); + }; + events.TaskStarted += ( + _, + task + ) => { + if (task.TaskName == "Csc" && Path.GetFileName(task.ProjectFile) == "Tool.csproj") + compilations++; + }; + events.Replay(binlog); + Assert.Single(identities); + Assert.Equal(run == 1 ? 0 : 1, compilations); + } + } + finally + { + Directory.Delete(root, recursive: true); + } + } + + private static void WriteProject( + string root, + string name, + string source + ) { + string directory = Path.Combine(root, name); + Directory.CreateDirectory(directory); + File.WriteAllText(Path.Combine(directory, name + ".csproj"), source); + File.WriteAllText(Path.Combine(directory, name + ".cs"), "public sealed class " + name + "Marker { }"); + } +} diff --git a/tests/build/Inno.Build.Tests/DevelopmentAppHostSdkTests.cs b/tests/build/Inno.Build.Tests/DevelopmentAppHostSdkTests.cs new file mode 100644 index 000000000..fa2d84c8d --- /dev/null +++ b/tests/build/Inno.Build.Tests/DevelopmentAppHostSdkTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Inno.Build.Composition; +using Inno.Build.Distribution.Standard; +using Inno.Build.Toolchains; +using Inno.Core.IO; +using Xunit; + +namespace Inno.Build.Tests; + +public sealed class DevelopmentAppHostSdkTests +{ + [Fact] + public async Task DirectAppHostUsesItsLoadedRuntimeBeforeAnUnrelatedPath() + { + string executable = OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"; + string host = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "..", "..", "..", executable)); + Assert.True(File.Exists(host)); + using ProcessOwnedDirectory owner = await ProcessOwnedDirectory.CreateAsync(Path.Combine(AppContext.BaseDirectory, "SdkHostFixtures")); + string project = Path.Combine(owner.directory, "SdkHostProbe.csproj"); + Type[] references = [typeof(StandardBuildEnvironment), typeof(BuildCompositionContext), typeof(BuildTargetId)]; + var items = new XElement("ItemGroup"); + foreach (Type reference in references) + items.Add(new XElement("Reference", new XAttribute("Include", reference.Assembly.GetName().Name!), + new XElement("HintPath", reference.Assembly.Location))); + new XDocument(new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), + new XElement("PropertyGroup", new XElement("TargetFramework", "net10.0"), new XElement("OutputType", "Exe"), + new XElement("ImplicitUsings", "disable"), new XElement("UseAppHost", "true")), items)).Save(project); + await File.WriteAllTextAsync(Path.Combine(owner.directory, "Program.cs"), """ + using System; + using Inno.Build; + using Inno.Build.Distribution.Standard; + Console.Write(StandardBuildEnvironment.Capture(AppContext.BaseDirectory, new BuildTargetId("sdk-probe")).dotnetHost); + """); + using var buildOutput = new StringWriter(); + using var buildErrors = new StringWriter(); + try + { + await ToolchainEnvironment.RunAsync(host, ["build", project, "-nologo", "-nodeReuse:false"], + owner.directory, CancellationToken.None, DotNetSdkEnvironment.Create(host), buildOutput, buildErrors); + } + catch (InvalidOperationException failure) + { + throw new InvalidOperationException(buildOutput + Environment.NewLine + buildErrors, failure); + } + string output = Path.Combine(owner.directory, "bin", "Debug", "net10.0"); + foreach (string dependency in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll")) + File.Copy(dependency, Path.Combine(output, Path.GetFileName(dependency)), overwrite: true); + var environment = new Dictionary + { + ["DOTNET_HOST_PATH"] = null, + ["DOTNET_ROOT"] = Path.GetDirectoryName(host), + ["PATH"] = owner.directory + }; + string appHost = Path.Combine(output, OperatingSystem.IsWindows() ? "SdkHostProbe.exe" : "SdkHostProbe"); + string selected = await ToolchainEnvironment.CaptureOutputAsync(appHost, [], owner.directory, + CancellationToken.None, environment); + Assert.Equal(host, selected.Trim()); + } +} diff --git a/tests/build/Inno.Build.Tests/Inno.Build.Tests.csproj b/tests/build/Inno.Build.Tests/Inno.Build.Tests.csproj index ffcc2547a..1e289743d 100644 --- a/tests/build/Inno.Build.Tests/Inno.Build.Tests.csproj +++ b/tests/build/Inno.Build.Tests/Inno.Build.Tests.csproj @@ -7,6 +7,8 @@ false true true + editor + $(InnoNativeTarget) @@ -15,6 +17,7 @@ + diff --git a/tests/build/Inno.Build.Tests/ManagedOutputOwnershipTests.cs b/tests/build/Inno.Build.Tests/ManagedOutputOwnershipTests.cs new file mode 100644 index 000000000..b6468234d --- /dev/null +++ b/tests/build/Inno.Build.Tests/ManagedOutputOwnershipTests.cs @@ -0,0 +1,157 @@ +using Inno.Build.Toolchains; +using Inno.Core.IO; +using Inno.Testing.Source; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Inno.Build.Tests; + +public sealed class ManagedOutputOwnershipTests +{ + [Theory] + [InlineData("Debug", false)] + [InlineData("Release", false)] + [InlineData("Debug", true)] + [InlineData("Release", true)] + public async Task SdkSeparatesNeutralAndProductOutputsBeforeRestore( + string configuration, + bool artifacts + ) { + string root = TestRepository.GetRoot(typeof(ManagedOutputOwnershipTests).Assembly); + string owner = Path.Combine(Path.GetTempPath(), "InnoManagedOutputTests"); + string fixture = PathBoundary.Resolve(owner, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(fixture); + try + { + File.Copy(Path.Combine(root, "global.json"), Path.Combine(fixture, "global.json")); + await File.WriteAllTextAsync(Path.Combine(fixture, "Directory.Build.props"), $""" + + + + """); + string project = Path.Combine(fixture, "Shared.csproj"); + await File.WriteAllTextAsync(project, """ + + net10.0 + + """); + await File.WriteAllTextAsync(Path.Combine(fixture, "Shared.cs"), "public sealed class Shared { }"); + string host = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") + ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + var common = new List { $"-p:Configuration={configuration}" }; + if (artifacts) + common.Add($"-p:ArtifactsPath={Path.Combine(fixture, "outputs")}"); + Dictionary neutral = await EvaluateAsync(host, project, common); + var productArguments = new List(common) { "-p:InnoNativeTarget=test-target" }; + Dictionary product = await EvaluateAsync(host, project, productArguments); + foreach (string property in new[] { "ProjectAssetsFile", "IntermediateOutputPath", "TargetPath" }) + Assert.NotEqual(neutral[property], product[property]); + + var compileArguments = new List(productArguments) + { + "-p:BuildProjectReferences=true", "-p:BuildingInsideVisualStudio=false" + }; + Dictionary compile = await EvaluateAsync(host, project, compileArguments); + Assert.Equal(product["ProjectAssetsFile"], compile["ProjectAssetsFile"]); + Assert.Equal(product["TargetPath"], compile["TargetPath"]); + + if (artifacts) + { + foreach (string input in new[] { "InnoToolTarget", "InnoNativeBuildFingerprint" }) + { + var changedArguments = new List(productArguments) { $"-p:{input}=different-input" }; + Dictionary changed = await EvaluateAsync(host, project, changedArguments); + Assert.NotEqual(product["ProjectAssetsFile"], changed["ProjectAssetsFile"]); + Assert.NotEqual(product["TargetPath"], changed["TargetPath"]); + } + string productProperties = Path.Combine(fixture, "Product.props"); + await File.WriteAllTextAsync(productProperties, ""); + var changedProductArguments = new List(productArguments) + { + $"-p:InnoProductBuildProperties={productProperties}" + }; + Dictionary changedProduct = await EvaluateAsync(host, project, changedProductArguments); + Assert.NotEqual(product["ProjectAssetsFile"], changedProduct["ProjectAssetsFile"]); + Assert.NotEqual(product["TargetPath"], changedProduct["TargetPath"]); + var equivalentProductArguments = new List(productArguments) + { + $"-p:InnoProductBuildProperties={Path.Combine(fixture, ".", "Product.props")}" + }; + Dictionary equivalentProduct = await EvaluateAsync(host, project, equivalentProductArguments); + Assert.Equal(changedProduct["ProjectAssetsFile"], equivalentProduct["ProjectAssetsFile"]); + Assert.Equal(changedProduct["TargetPath"], equivalentProduct["TargetPath"]); + } + + var toolArguments = new List(productArguments) { "-p:IsRoslynComponent=true" }; + Dictionary compilerTool = await EvaluateAsync(host, project, toolArguments); + Assert.Equal(neutral["ProjectAssetsFile"], compilerTool["ProjectAssetsFile"]); + Assert.Equal(neutral["TargetPath"], compilerTool["TargetPath"]); + + var ridArguments = new List(productArguments) { "-p:RuntimeIdentifier=win-x64" }; + Dictionary rid = await EvaluateAsync(host, project, ridArguments); + Assert.Equal("win-x64", rid["RuntimeIdentifier"]); + Assert.NotEqual(product["TargetPath"], rid["TargetPath"]); + + foreach (List request in new[] { common, productArguments }) + await RunAsync(host, fixture, ["restore", project, .. request, "-v:quiet"]); + string driver = Path.Combine(fixture, "Build.proj"); + await File.WriteAllTextAsync(driver, $""" + + + + + InnoNativeTarget=test-target;BuildProjectReferences=true;BuildingInsideVisualStudio=false + + + + + + + """); + await RunAsync(host, fixture, ["msbuild", driver, "-t:Build", "-m:2", "-nodeReuse:false", "-v:quiet", .. common]); + Assert.True(File.Exists(neutral["TargetPath"])); + Assert.True(File.Exists(product["TargetPath"])); + } + finally + { + Directory.Delete(PathBoundary.RequireUnlinkedPath(owner, fixture), recursive: true); + } + } + + private static async Task> EvaluateAsync( + string host, + string project, + List properties + ) { + using var output = new StringWriter(); + await RunAsync(host, Path.GetDirectoryName(project)!, [ + "msbuild", project, "-nologo", .. properties, + "-getProperty:ProjectAssetsFile,IntermediateOutputPath,TargetPath,RuntimeIdentifier" + ], output); + using JsonDocument document = JsonDocument.Parse(output.ToString()); + var values = new Dictionary(StringComparer.Ordinal); + foreach (JsonProperty property in document.RootElement.GetProperty("Properties").EnumerateObject()) + values.Add(property.Name, property.Value.GetString()!); + return values; + } + + private static async Task RunAsync( + string host, + string directory, + IReadOnlyList arguments, + StringWriter? output = null + ) { + using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + using var error = new StringWriter(); + using var captured = new StringWriter(); + Exception? failure = await Record.ExceptionAsync(() => ToolchainEnvironment.RunAsync( + host, arguments, directory, cancellation.Token, DotNetSdkEnvironment.Create(host), output ?? captured, error)); + Assert.True(failure is null, $"{failure}{Environment.NewLine}{output ?? captured}{Environment.NewLine}{error}"); + } +} diff --git a/tests/build/Inno.Build.Tests/NuGetLoadedIdentityTests.cs b/tests/build/Inno.Build.Tests/NuGetLoadedIdentityTests.cs new file mode 100644 index 000000000..8d3fefc20 --- /dev/null +++ b/tests/build/Inno.Build.Tests/NuGetLoadedIdentityTests.cs @@ -0,0 +1,172 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Security.Cryptography; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using BGCS.Runtime; +using Inno.Build.Toolchains; +using Inno.Testing.Source; +using Xunit; + +namespace Inno.Build.Tests; + +public sealed class NuGetLoadedIdentityTests +{ + private const string C_EXPECTED_RUNTIME = "INNO_PACKAGE_IDENTITY_EXPECTED_RUNTIME"; + private const string C_MISMATCH_RUNTIME = "INNO_PACKAGE_IDENTITY_MISMATCH_RUNTIME"; + private const string C_PROBE_RESULT = "INNO_PACKAGE_IDENTITY_PROBE_RESULT"; + private const string C_VERIFIED_PROBE = "loaded-content-mismatch-rejected;context-collected;assembly-collected"; + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public Task SdkOutputScopesMatchTheActuallyLoadedRuntime( + bool isolated, + bool staleDefault + ) => VerifyScope(isolated, staleDefault); + + [Fact] + [Trait("ProcessIsolation", "Required")] + public Task IsolatedHostRejectsARealLoadedPayloadMismatch() + { + string? expected = Environment.GetEnvironmentVariable(C_EXPECTED_RUNTIME); + if (expected is null) + return VerifyScope(true, true); + string mismatch = Environment.GetEnvironmentVariable(C_MISMATCH_RUNTIME) + ?? throw new InvalidOperationException("The loaded-package probe has no owned mismatch input."); + string result = Environment.GetEnvironmentVariable(C_PROBE_RESULT) + ?? throw new InvalidOperationException("The loaded-package probe has no result owner."); + (WeakReference retired, WeakReference retiredAssembly) = LoadMismatchedContent(expected, mismatch, typeof(Utf8Codec).Assembly); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(retired.IsAlive); + Assert.False(retiredAssembly.IsAlive); + File.WriteAllText(result, C_VERIFIED_PROBE); + return Task.CompletedTask; + } + + private static async Task VerifyScope( + bool isolated, + bool staleDefault + ) { + string root = Path.Combine(Path.GetTempPath(), "InnoLoadedPackageIdentity", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + try + { + string repository = TestRepository.GetRoot(typeof(NuGetLoadedIdentityTests).Assembly); + string sdk = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + string version = (await ToolchainEnvironment.CaptureOutputAsync(sdk, + ["msbuild", Path.Combine(repository, "backends/ImGui/native/Inno.Native.ImGui/Inno.Native.ImGui.csproj"), + "-nologo", "-nr:false", "-getProperty:InnoBgcsPackageVersion"], + repository, cancellation.Token, DotNetSdkEnvironment.Create(sdk))).Trim(); + string project = Path.Combine(root, "Consumer.csproj"); + new XDocument(new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), + new XElement("PropertyGroup", new XElement("TargetFramework", "net10.0"), + new XElement("LangVersion", "14.0"), new XElement("ImplicitUsings", "disable")), + new XElement("ItemGroup", new XElement("PackageReference", new XAttribute("Include", "BGCS.Runtime"), + new XAttribute("Version", "[" + version + "]"))))).Save(project); + string defaultAssets = Path.Combine(root, "obj", "project.assets.json"); + const string stale = "unrelated stale default assets must never be consumed"; + if (staleDefault) + { + Directory.CreateDirectory(Path.GetDirectoryName(defaultAssets)!); + File.WriteAllText(defaultAssets, stale); + } + string[] owner = isolated ? ["-p:ArtifactsPath=" + Path.Combine(root, "owned-output")] : []; + string evaluated = await ToolchainEnvironment.CaptureOutputAsync(sdk, + ["msbuild", project, "-t:Restore", "-nologo", "-nr:false", "-v:quiet", "-getProperty:ProjectAssetsFile", .. owner], + root, cancellation.Token, DotNetSdkEnvironment.Create(sdk)); + string assetsPath = evaluated.Trim(); + Assert.True(File.Exists(assetsPath), evaluated); + if (isolated) + { + Assert.StartsWith(Path.Combine(root, "owned-output"), assetsPath, StringComparison.OrdinalIgnoreCase); + if (staleDefault) + Assert.Equal(stale, File.ReadAllText(defaultAssets)); + else + Assert.False(File.Exists(defaultAssets)); + } + else + Assert.Equal(defaultAssets, assetsPath); + using JsonDocument assets = JsonDocument.Parse(File.ReadAllBytes(assetsPath)); + string library = "BGCS.Runtime/" + version; + JsonElement package = assets.RootElement.GetProperty("libraries").GetProperty(library); + string packageRoot = assets.RootElement.GetProperty("packageFolders").EnumerateObject() + .Select(folder => Path.Combine(folder.Name, package.GetProperty("path").GetString()!)).Single(Directory.Exists); + JsonElement target = assets.RootElement.GetProperty("targets").EnumerateObject().Single().Value.GetProperty(library); + string relative = target.GetProperty("runtime").EnumerateObject().Single(item => item.Name.EndsWith(".dll", StringComparison.Ordinal)).Name; + string runtime = Path.Combine(packageRoot, relative); + Assembly loaded = typeof(Utf8Codec).Assembly; + AssertLoadedContent(runtime, loaded); + + string mismatch = Path.Combine(root, "mismatched", "BGCS.Runtime.dll"); + Directory.CreateDirectory(Path.GetDirectoryName(mismatch)!); + File.WriteAllBytes(mismatch, File.ReadAllBytes(runtime)); + await using (FileStream altered = new(mismatch, FileMode.Append, FileAccess.Write)) + await altered.WriteAsync(new byte[] { 0x49, 0x4E, 0x4E, 0x4F }, cancellation.Token); + await RunLoadedMismatchProbeAsync(sdk, runtime, mismatch, root, cancellation.Token); + } + finally + { + Directory.Delete(root, recursive: true); + } + } + + private static async Task RunLoadedMismatchProbeAsync( + string sdk, + string expected, + string mismatch, + string root, + CancellationToken cancellationToken + ) { + string result = Path.Combine(root, "loaded-package-probe.txt"); + string output = await ToolchainEnvironment.CaptureOutputAsync(sdk, + ["test", typeof(NuGetLoadedIdentityTests).Assembly.Location, + "--filter", "FullyQualifiedName=" + typeof(NuGetLoadedIdentityTests).FullName + "." + nameof(IsolatedHostRejectsARealLoadedPayloadMismatch), + "--results-directory", Path.Combine(root, "probe-results"), + "--logger", "trx;LogFileName=loaded-package.trx", + "--environment", C_EXPECTED_RUNTIME + "=" + expected, + "--environment", C_MISMATCH_RUNTIME + "=" + mismatch, + "--environment", C_PROBE_RESULT + "=" + result], + root, cancellationToken, DotNetSdkEnvironment.Create(sdk)); + Assert.True(File.Exists(result), output); + Assert.Equal(C_VERIFIED_PROBE, File.ReadAllText(result)); + // The physical image belongs to the probe process; normal cleanup follows its complete exit. + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static (WeakReference context, WeakReference assembly) LoadMismatchedContent( + string expected, + string mismatch, + Assembly loaded + ) { + var context = new AssemblyLoadContext("package-content-mismatch", isCollectible: true); + try + { + Assembly different = context.LoadFromAssemblyPath(mismatch); + Assert.Equal(loaded.GetName().FullName, different.GetName().FullName); + Assert.Equal(loaded.ManifestModule.ModuleVersionId, different.ManifestModule.ModuleVersionId); + Assert.Throws(() => AssertLoadedContent(expected, different)); + return (new WeakReference(context), new WeakReference(different)); + } + finally + { + context.Unload(); + } + } + + private static void AssertLoadedContent( + string expected, + Assembly loaded + ) => Assert.Equal(Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(expected))), + Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(loaded.Location)))); +} diff --git a/tests/build/Inno.Build.Tests/RepositoryWikiTests.cs b/tests/build/Inno.Build.Tests/RepositoryWikiTests.cs index 2c6413c26..aff3e1e7d 100644 --- a/tests/build/Inno.Build.Tests/RepositoryWikiTests.cs +++ b/tests/build/Inno.Build.Tests/RepositoryWikiTests.cs @@ -1,5 +1,6 @@ using Inno.Testing.Source; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -91,7 +92,8 @@ public void LogicalEditorReferencesIncludeTheSharedProductFeatures() public async Task CurrentWikiCompilesWithTheCurrentLogicalScriptContracts() { string repository = TestRepository.GetRoot(typeof(RepositoryWikiTests).Assembly); - string owner = Path.Combine(repository, "artifacts", "acceptance", "wiki-gate", Guid.NewGuid().ToString("N")); + string? artifacts = TestRepository.GetArtifactsPath(typeof(RepositoryWikiTests).Assembly); + string owner = Path.Combine(artifacts ?? Path.Combine(repository, "artifacts"), "acceptance", "wiki-gate", Guid.NewGuid().ToString("N")); string project = Path.Combine(owner, "Project"); Directory.CreateDirectory(project); try @@ -106,15 +108,22 @@ public async Task CurrentWikiCompilesWithTheCurrentLogicalScriptContracts() TextWriter previousError = Console.Error; using var errors = new StringWriter(); int result; + string dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + var arguments = new List + { + "verify", repository, "--configuration", configuration, "--wiki", "--wiki-target", target, + "--dotnet", dotnet, + "--wiki-api", Path.Combine(owner, "api.json"), + "--wiki-examples", Path.Combine(owner, "examples.json"), + "--wiki-runtime-reference", runtime, "--wiki-editor-reference", editor + }; + if (artifacts is not null) + arguments.AddRange(["--artifacts-path", artifacts]); try { Console.SetError(errors); result = await BuildApplication.Run( - ["verify", repository, "--configuration", configuration, "--wiki", "--wiki-target", target, - "--dotnet", ToolchainEnvironment.ResolveExecutable("dotnet"), - "--wiki-api", Path.Combine(owner, "api.json"), - "--wiki-examples", Path.Combine(owner, "examples.json"), - "--wiki-runtime-reference", runtime, "--wiki-editor-reference", editor], + arguments.ToArray(), CancellationToken.None); } finally @@ -160,7 +169,15 @@ params System.Reflection.Assembly[] roots AssetSerializationContext.Create(assets)); using var plugins = new PluginEnvironment(assets, settings, host.serialization, pluginsRoot, libraryRoot, scan, host.modules.generations); - var compiler = new ScriptCompiler(new ScriptCompilerOptions { projectRootDirectory = project }, assets, plugins); + string dotnetHost = Path.Combine( + Path.GetFullPath("../../..", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + var compiler = new ScriptCompiler( + new ScriptCompilerOptions + { + projectRootDirectory = project, + compilationEnvironment = new Inno.Build.SdkScriptCompilationEnvironment(dotnetHost, project) + }, assets, plugins); compiler.GenerateProjectFiles(); } diff --git a/tests/build/Inno.Build.Tests/ShaderBuildInputsTests.cs b/tests/build/Inno.Build.Tests/ShaderBuildInputsTests.cs new file mode 100644 index 000000000..8ed8e24a2 --- /dev/null +++ b/tests/build/Inno.Build.Tests/ShaderBuildInputsTests.cs @@ -0,0 +1,157 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Inno.Assets.Pipeline; +using Inno.Build.Toolchains; +using Inno.Build.Distribution.Standard; +using Inno.Build.Toolchains.Bgfx.Shaders; +using Inno.Core.IO; +using Inno.Integration.WindowsX64.Bgfx; +using Inno.Rendering; +using Xunit; + +namespace Inno.Build.Tests; + +public sealed class ShaderBuildInputsTests : IDisposable +{ + private readonly string m_root = Path.Combine(Path.GetTempPath(), "InnoShaderInputs", Guid.NewGuid().ToString("N")); + + public ShaderBuildInputsTests() => Directory.CreateDirectory(m_root); + + [Fact] + public void LivePublicationIsNotAnAssetButPermanentImportSettingsAreBuildInputs() + { + string source = Path.Combine(m_root, "shader.ishader"); + File.WriteAllText(source, "source"); + File.WriteAllText(source + ".imeta", "permanent settings"); + File.WriteAllText(source + ".abin", "derived artifact"); + ShaderBuildInputs baseline = ShaderBuildInputs.Capture(m_root); + string fingerprint = baseline.fingerprint; + string candidate = source + ".imeta.staging-" + Guid.NewGuid().ToString("N"); + using (FileStream publication = new(candidate, FileMode.CreateNew, FileAccess.Write, FileShare.None)) + { + publication.Write("not yet published"u8); + Assert.True(AssetSourcePolicy.defaultPolicy.IsIgnored(Path.GetFileName(candidate), false)); + ShaderBuildInputs current = ShaderBuildInputs.Capture(m_root); + Assert.Equal(fingerprint, current.fingerprint); + baseline.VerifyUnchanged(); + } + File.WriteAllText(source + ".imeta", "changed settings"); + Assert.Throws(() => baseline.VerifyUnchanged()); + } + + [Theory] + [InlineData("edit")] + [InlineData("add")] + [InlineData("delete")] + public void FreshVerificationIncludesContentAndTheCompleteInventory(string mutation) + { + string source = Path.Combine(m_root, "shader.txt"); + File.WriteAllText(source, "before"); + DateTime timestamp = File.GetLastWriteTimeUtc(source); + ShaderBuildInputs captured = ShaderBuildInputs.Capture(m_root); + switch (mutation) + { + case "edit": + File.WriteAllText(source, "edited"); + File.SetLastWriteTimeUtc(source, timestamp); + break; + case "add": + File.WriteAllText(Path.Combine(m_root, "new.imeta"), "settings"); + break; + case "delete": + File.Delete(source); + break; + } + Assert.Throws(() => captured.VerifyUnchanged()); + } + + [Fact] + public void SourceNoiseDoesNotWeakenExplicitNativeInputDeclarations() + { + string candidate = Path.Combine(m_root, "source.staging-" + Guid.NewGuid().ToString("N")); + File.WriteAllText(candidate, "first"); + NativeBuildInput[] explicitInputs = [new("explicit-input", candidate)]; + string before = NativeBuildFingerprint.Create([], explicitInputs); + File.WriteAllText(candidate, "other"); + Assert.NotEqual(before, NativeBuildFingerprint.Create([], explicitInputs)); + string sourceIdentity = ShaderBuildInputs.Capture(m_root).fingerprint; + File.WriteAllText(candidate, "again"); + Assert.Equal(sourceIdentity, ShaderBuildInputs.Capture(m_root).fingerprint); + } + + [Fact] + public void EmptyAuthoredDirectoriesParticipateInTheFrozenInventory() + { + ShaderBuildInputs before = ShaderBuildInputs.Capture(m_root); + string directory = Path.Combine(m_root, "Includes"); + Directory.CreateDirectory(directory); + Assert.NotEqual(before.fingerprint, ShaderBuildInputs.Capture(m_root).fingerprint); + Assert.Throws(() => before.VerifyUnchanged()); + ShaderBuildInputs withDirectory = ShaderBuildInputs.Capture(m_root); + Directory.Delete(directory); + Assert.Throws(() => withDirectory.VerifyUnchanged()); + Assert.Equal(before.fingerprint, ShaderBuildInputs.Capture(m_root).fingerprint); + } + + [Fact] + public async Task FailedImportDoesNotWriteTheAuthoringTreeOrReplaceAnExistingArtifact() + { + string sources = Path.Combine(m_root, "Assets"); + Directory.CreateDirectory(sources); + File.WriteAllText(Path.Combine(sources, "invalid.ishader"), "invalid authored graph"); + string output = Path.Combine(m_root, "program.bin"); + File.WriteAllText(output, "last good artifact"); + string[] original = Directory.GetFiles(sources); + byte[] bytes = File.ReadAllBytes(original.Single()); + ShaderBuildInputs inputs = ShaderBuildInputs.Capture(sources); + using ProcessOwnedDirectory compilation = await ProcessOwnedDirectory.CreateAsync(Path.Combine(m_root, "requests")); + Assert.Throws(() => ShaderArtifactBuilder.CompileGraphicsProgram( + inputs, compilation, "invalid.ishader", WindowsX64BgfxIntegration.shaderProfile, GraphicsApi.Direct3D12, output)); + Assert.Equal(original, Directory.GetFiles(sources)); + Assert.Equal(bytes, File.ReadAllBytes(original.Single())); + Assert.Equal("last good artifact", File.ReadAllText(output)); + inputs.VerifyUnchanged(); + } + + [Fact] + public async Task CancellationCannotPublishAnArtifact() + { + using var canceled = new CancellationTokenSource(); + canceled.Cancel(); + Assert.Throws(() => ShaderBuildInputs.Capture(m_root, canceled.Token)); + ShaderBuildInputs inputs = ShaderBuildInputs.Capture(m_root); + string output = Path.Combine(m_root, "program.bin"); + File.WriteAllText(output, "last good artifact"); + using ProcessOwnedDirectory compilation = await ProcessOwnedDirectory.CreateAsync(Path.Combine(m_root, "requests")); + Assert.Throws(() => ShaderArtifactBuilder.CompileGraphicsProgram( + inputs, compilation, "graph.ishader", WindowsX64BgfxIntegration.shaderProfile, GraphicsApi.Direct3D12, output, + cancellationToken: canceled.Token)); + Assert.Equal("last good artifact", File.ReadAllText(output)); + } + + [Fact] + public async Task CompilationWorkspaceMetadataUsesTheExistingRegisteredRetirementProtocol() + { + string directory; + using (ProcessOwnedDirectory workspace = await StandardBuildCaches.CreateShaderWorkspaceAsync(m_root)) + { + directory = workspace.directory; + Assert.True(Directory.Exists(directory)); + var registration = Assert.Single(BuildCacheOwnerCatalog.ReadOwners(m_root)); + Assert.Equal("shader-requests", registration.id); + Assert.Equal(Path.GetDirectoryName(directory), registration.directory); + } + Assert.False(Directory.Exists(directory)); + string owner = Path.GetDirectoryName(directory)!; + using FileLease exclusive = await FileLease.AcquireAsync(Path.Combine(owner, "owner.lock"), TimeSpan.FromSeconds(5)); + Assert.Equal(1, ProcessOwnedDirectory.RetireMetadataUnderOwnership(owner)); + Assert.Empty(Directory.GetFiles(owner, "*.work.json")); + Assert.Empty(Directory.GetFiles(owner, "*.work.lock")); + Assert.True(File.Exists(Path.Combine(owner, "owner.lock"))); + } + + public void Dispose() => Directory.Delete(m_root, recursive: true); +} diff --git a/tests/build/Inno.Build.Tests/StaticGraphRestoreTests.cs b/tests/build/Inno.Build.Tests/StaticGraphRestoreTests.cs index 49df390fe..69875663c 100644 --- a/tests/build/Inno.Build.Tests/StaticGraphRestoreTests.cs +++ b/tests/build/Inno.Build.Tests/StaticGraphRestoreTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Security; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Inno.Build.Toolchains; @@ -13,6 +14,57 @@ namespace Inno.Build.Tests; public sealed class StaticGraphRestoreTests { + [Fact] + public async Task DevelopmentEntryRestoresItsDirectProductReferenceOwners() + { + string root = TestRepository.GetRoot(typeof(StaticGraphRestoreTests).Assembly); + string owner = Path.Combine(Path.GetTempPath(), "InnoEntryRestoreTests"); + string fixture = PathBoundary.Resolve(owner, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(fixture); + try + { + string host = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") + ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + string entry = Path.Combine(root, "entry", "InnoEngine.Editor", "InnoEngine.Editor.csproj"); + string artifacts = Path.Combine(fixture, "managed"); + string[] common = ["-p:Configuration=Debug", "-p:ArtifactsPath=" + artifacts, "-nodeReuse:false"]; + using JsonDocument route = await EvaluateAsync(host, entry, common); + JsonElement properties = route.RootElement.GetProperty("Properties"); + string product = properties.GetProperty("InnoProductRestoreProject").GetString()!; + Assert.True(File.Exists(product)); + string[] productProperties = [.. common, + "-p:InnoNativeTarget=" + properties.GetProperty("InnoNativeTarget").GetString(), + "-p:InnoToolTarget=" + properties.GetProperty("InnoToolTarget").GetString(), + "-p:InnoProductBuildProperties=" + properties.GetProperty("InnoProductBuildProperties").GetString()]; + using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + await ToolchainEnvironment.RunAsync(host, + ["msbuild", entry, "-t:RestoreProductTargetGraph", "-v:quiet", .. common], + root, cancellation.Token, DotNetSdkEnvironment.Create(host)); + + var references = new HashSet(StringComparer.Ordinal); + foreach (JsonElement reference in route.RootElement.GetProperty("Items").GetProperty("ProjectReference").EnumerateArray()) + references.Add(reference.GetProperty("FullPath").GetString()!); + using JsonDocument selectedProduct = await EvaluateAsync(host, product, productProperties); + foreach (JsonElement reference in selectedProduct.RootElement.GetProperty("Items").GetProperty("ProjectReference").EnumerateArray()) + { + if (!reference.TryGetProperty("PrivateAssets", out JsonElement privateAssets) || string.IsNullOrEmpty(privateAssets.GetString())) + references.Add(reference.GetProperty("FullPath").GetString()!); + } + Assert.NotEmpty(references); + foreach (string reference in references) + { + using JsonDocument evaluated = await EvaluateAsync(host, reference, productProperties); + string assets = evaluated.RootElement.GetProperty("Properties").GetProperty("ProjectAssetsFile").GetString()!; + Assert.Equal(Path.GetFullPath(assets), PathBoundary.RequireContained(artifacts, assets)); + Assert.True(File.Exists(assets), $"The entry did not restore its selected reference owner: {assets}"); + } + } + finally + { + Directory.Delete(PathBoundary.RequireUnlinkedPath(owner, fixture), recursive: true); + } + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -50,4 +102,20 @@ await ToolchainEnvironment.RunAsync(host, Directory.Delete(PathBoundary.RequireUnlinkedPath(owner, fixture), recursive: true); } } + + private static async Task EvaluateAsync( + string host, + string project, + IReadOnlyList properties + ) { + using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + using var output = new StringWriter(); + using var error = new StringWriter(); + await ToolchainEnvironment.RunAsync(host, + ["msbuild", project, "-nologo", .. properties, + "-getProperty:ProjectAssetsFile,InnoProductRestoreProject,InnoNativeTarget,InnoToolTarget,InnoProductBuildProperties", + "-getItem:ProjectReference"], + Path.GetDirectoryName(project)!, cancellation.Token, DotNetSdkEnvironment.Create(host), output, error); + return JsonDocument.Parse(output.ToString()); + } } diff --git a/tests/build/Inno.Build.Tests/TaskPublisherBootstrapTests.cs b/tests/build/Inno.Build.Tests/TaskPublisherBootstrapTests.cs index b0db48582..d5b73a95f 100644 --- a/tests/build/Inno.Build.Tests/TaskPublisherBootstrapTests.cs +++ b/tests/build/Inno.Build.Tests/TaskPublisherBootstrapTests.cs @@ -8,14 +8,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Build.Logging; +using Inno.Build.Toolchains; using Xunit; namespace Inno.Build.Tests; public sealed class TaskPublisherBootstrapTests { - [Fact] - public async Task ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuildOwnership() + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuildOwnership(string configuration) { string repository = TestRepository.GetRoot(typeof(TaskPublisherBootstrapTests).Assembly); string root = Path.Combine(Path.GetTempPath(), "InnoPublisherTests", Guid.NewGuid().ToString("N")); @@ -45,13 +48,18 @@ public async Task ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuild }; string references = string.Join(string.Empty, dependencies.Select(static name => $"")); + if (name == "Root") + references += ""; File.WriteAllText(Path.Combine(directory, name + ".csproj"), "net10.0" - + "disable" + references + ""); + + "disablefalse" + + "BuildProjectReferences=true;BuildingInsideVisualStudio=false" + + "" + references + ""); } - var first = await Build(root); + var first = await Build(root, configuration); string firstOwner = Validate(first.output, first.binlog); - var second = await Build(root); + var second = await Build(root, configuration); string secondOwner = Validate(second.output, second.binlog); Assert.NotEqual(firstOwner, secondOwner); } @@ -61,6 +69,66 @@ public async Task ProjectGraphSharesCompilationButKeepsPrivateLoadsAndFreshBuild } } + [Theory] + [InlineData("net10.0-browser", "browser-wasm")] + [InlineData("net10.0", "win-x64")] + public async Task ProductPublishPropertiesDoNotReachFoundationBootstrap( + string framework, + string runtimeIdentifier + ) { + string repository = TestRepository.GetRoot(typeof(TaskPublisherBootstrapTests).Assembly); + string root = Path.Combine(Path.GetTempPath(), "InnoBootstrapTargetTests", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + try + { + string project = Path.Combine(root, "Probe.proj"); + File.WriteAllText(project, $$""" + + Debug + + + + + """); + string dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + string binlog = Path.Combine(root, "probe.binlog"); + using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + using var output = new StringWriter(); + using var errors = new StringWriter(); + Exception? failure = await Record.ExceptionAsync(() => ToolchainEnvironment.RunAsync(dotnet, + ["msbuild", project, "-nologo", "-t:Probe", "-nr:false", "-bl:" + binlog, + "-p:InnoTaskArtifacts=" + Path.Combine(root, "tasks"), "-p:ArtifactsPath=" + Path.Combine(root, "caller"), + "-p:TargetFramework=" + framework, "-p:RuntimeIdentifier=" + runtimeIdentifier, + "-p:PublishAot=true", "-p:PublishSingleFile=true", "-p:SelfContained=true", "-p:WasmBuildNative=true"], + repository, timeout.Token, DotNetSdkEnvironment.Create(dotnet), output, errors)); + Assert.True(failure is null, $"{failure}\n{output}\n{errors}"); + int requests = 0; + var events = new BinaryLogReplayEventSource(); + events.ProjectStarted += ( + _, + item + ) => { + if (Path.GetFileNameWithoutExtension(item.ProjectFile) != "Inno.Core.IO" + || item.TargetNames != "Restore;Build") + return; + requests++; + Assert.NotNull(item.GlobalProperties); + foreach (string property in new[] { "RuntimeIdentifier", "TargetFramework", "PublishAot", "PublishSingleFile", "SelfContained", "WasmBuildNative" }) + Assert.DoesNotContain(property, item.GlobalProperties!.Keys); + Assert.Equal(Path.Combine(root, "caller", ".inno-task-foundation").Replace('\\', '/'), + item.GlobalProperties!["ArtifactsPath"].Replace('\\', '/')); + }; + events.Replay(binlog); + Assert.True(requests > 0, "No neutral Foundation bootstrap was inspected."); + Assert.False(Directory.Exists(Path.Combine(root, "caller", "bin"))); + Assert.False(Directory.Exists(Path.Combine(root, "caller", "obj"))); + } + finally + { + Directory.Delete(root, recursive: true); + } + } + private static string Validate( string output, string binlog @@ -75,6 +143,32 @@ string binlog // Incremental Build targets can report TargetPath without executing the compiler. int compilations = 0; var events = new BinaryLogReplayEventSource(); + int sharedFoundationRequests = 0; + int bootstrapFoundationRequests = 0; + events.ProjectStarted += ( + _, + project + ) => { + if (Path.GetFileNameWithoutExtension(project.ProjectFile) != "Inno.Core.IO" + || project.GlobalProperties is not { } globals + || !globals.TryGetValue("ArtifactsPath", out string? artifacts)) + return; + if (string.Equals(Path.GetFileName(artifacts), ".inno-task-foundation", StringComparison.Ordinal)) + { + bootstrapFoundationRequests++; + Assert.DoesNotContain("InnoNativeTarget", globals.Keys); + Assert.DoesNotContain("BuildingInsideVisualStudio", globals.Keys); + return; + } + if (!string.IsNullOrEmpty(project.TargetNames) + || !string.Equals(Path.GetFileName(artifacts), "managed", StringComparison.Ordinal)) + return; + sharedFoundationRequests++; + Assert.True(globals.TryGetValue("InnoNativeTarget", out string? target)); + Assert.Equal("fixture-target", target); + Assert.True(globals.TryGetValue("BuildingInsideVisualStudio", out string? insideIde)); + Assert.Equal("false", insideIde); + }; events.TaskStarted += ( _, task @@ -83,6 +177,8 @@ string binlog compilations++; }; events.Replay(binlog); + Assert.True(sharedFoundationRequests > 0, "The real shared Foundation graph was not exercised."); + Assert.True(bootstrapFoundationRequests > 0, "The neutral bootstrap output owner was not exercised."); Assert.True(compilations == 1, $"Expected one bootstrap Csc task, observed {compilations}.\n{output}"); string[] runtimeReports = output.Split('\n').Select(static line => line.Trim()) .Where(static line => line.StartsWith("INNO-TASK-RUNTIME ", StringComparison.Ordinal)).ToArray(); @@ -94,8 +190,10 @@ string binlog return owner; } - private static async Task<(string output, string binlog)> Build(string root) - { + private static async Task<(string output, string binlog)> Build( + string root, + string configuration + ) { string dotnet = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); var start = new ProcessStartInfo(dotnet) @@ -109,7 +207,8 @@ string binlog string binlog = Path.Combine(root, Guid.NewGuid().ToString("N") + ".binlog"); foreach (string argument in new[] { - "build", "Root/Root.csproj", "--disable-build-servers", "--nologo", "-m:2", "-nodeReuse:false", + "build", "Root/Root.csproj", "--configuration", configuration, "--disable-build-servers", "--nologo", "-m:2", "-nodeReuse:false", + "-p:InnoNativeTarget=fixture-target", "-p:InnoTaskArtifacts=" + Path.Combine(root, "tasks"), "-p:ArtifactsPath=" + Path.Combine(root, "managed"), "-bl:" + binlog diff --git a/tests/build/Inno.Build.Tests/TaskRegistrationBuildTests.cs b/tests/build/Inno.Build.Tests/TaskRegistrationBuildTests.cs index e9e386b04..63e43e21b 100644 --- a/tests/build/Inno.Build.Tests/TaskRegistrationBuildTests.cs +++ b/tests/build/Inno.Build.Tests/TaskRegistrationBuildTests.cs @@ -31,8 +31,10 @@ await File.WriteAllTextAsync(project, """ Exe disable enable + false + @@ -50,6 +52,7 @@ await File.WriteAllTextAsync(Path.Combine(root, "Program.cs"), """ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Text.Json; using System.Threading; using Microsoft.Build.Evaluation; @@ -98,8 +101,7 @@ await File.WriteAllTextAsync(Path.Combine(root, "Program.cs"), """ if (copies.count != 0) return 3; ProjectInstance completed = result.ProjectStateAfterBuild!; - string foundationProject = Path.Combine(args[3], "src/foundation/core/Inno.Core.IO/Inno.Core.IO.csproj"); - string foundationAssembly = projects.LoadProject(foundationProject).GetPropertyValue("TargetPath"); + string foundationAssembly = completed.GetItems("_InnoFoundationOutput").Single().EvaluatedInclude; using (FileStream writable = File.Open(foundationAssembly, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { if (writable.Length == 0) diff --git a/tests/core/Inno.Core.IO.Tests/AtomicFilePublicationTests.cs b/tests/core/Inno.Core.IO.Tests/AtomicFilePublicationTests.cs new file mode 100644 index 000000000..e9c69367c --- /dev/null +++ b/tests/core/Inno.Core.IO.Tests/AtomicFilePublicationTests.cs @@ -0,0 +1,213 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +using Xunit; + +namespace Inno.Core.IO.Tests; + +public sealed class AtomicFilePublicationTests : IDisposable +{ + private readonly string m_root = Path.Combine(Path.GetTempPath(), "InnoAtomicPublication", Guid.NewGuid().ToString("N")); + + public AtomicFilePublicationTests() => Directory.CreateDirectory(m_root); + + [Theory] + [InlineData("document.imeta.staging-0123456789abcdef0123456789ABCDEF", true)] + [InlineData("document.staging-0123456789abcdef0123456789abcdef.more", false)] + [InlineData("document.staging-0123456789abcdef0123456789abcdeg", false)] + [InlineData("document.staging-", false)] + [InlineData(".staging-0123456789abcdef0123456789abcdef", false)] + [InlineData("ordinary.imeta", false)] + public void PublicationRecognitionRequiresTheCompleteReservedName( + string name, + bool expected + ) { + Assert.Equal(expected, AtomicFile.IsCandidatePath(name)); + Assert.Equal(expected, AtomicFile.IsCandidatePath(Path.Combine(m_root, name))); + } + + [Theory] + [InlineData("")] + [InlineData("external document")] + public void CreateOnlyPreservesAnExistingFileAndTheLosingCandidate(string document) + { + string destination = Path.Combine(m_root, "document.ini"); + string candidate = Path.Combine(m_root, "candidate"); + File.WriteAllText(destination, document); + File.WriteAllText(candidate, "candidate"); + byte[] original = File.ReadAllBytes(destination); + + Assert.ThrowsAny(() => AtomicFile.Install(candidate, destination, overwrite: false)); + Assert.Equal(original, File.ReadAllBytes(destination)); + Assert.Equal("candidate", File.ReadAllText(candidate)); + Assert.ThrowsAny(() => AtomicFile.WriteAllBytes(destination, "replacement"u8, overwrite: false)); + Assert.Equal(original, File.ReadAllBytes(destination)); + Assert.Equal(2, Directory.GetFiles(m_root).Length); + } + + [Fact] + public void CreateOnlyRejectsAnExistingDirectoryWithoutConsumingTheCandidate() + { + string destination = Path.Combine(m_root, "directory"); + string candidate = Path.Combine(m_root, "candidate"); + Directory.CreateDirectory(destination); + File.WriteAllText(candidate, "complete"); + Exception? failure = Record.Exception(() => AtomicFile.Install(candidate, destination, overwrite: false)); + Assert.True(failure is IOException or UnauthorizedAccessException, failure?.ToString()); + Assert.Equal("complete", File.ReadAllText(candidate)); + Assert.Empty(Directory.GetFileSystemEntries(destination)); + } + + [Fact] + public async Task ConcurrentCandidatesPublishExactlyOneCompleteFile() + { + const int count = 16; + for (int round = 0; round < 12; round++) + { + string destination = Path.Combine(m_root, "document-" + round); + string[] candidates = Enumerable.Range(0, count).Select(index => destination + ".candidate-" + index).ToArray(); + foreach (string candidate in candidates) + File.WriteAllBytes(candidate, Enumerable.Repeat((byte)Array.IndexOf(candidates, candidate), 64 * 1024).ToArray()); + int winner = -1; + int successes = 0; + using var ready = new CountdownEvent(count); + using var start = new ManualResetEventSlim(); + Task[] writers = Enumerable.Range(0, count).Select(index => Task.Factory.StartNew(() => + { + ready.Signal(); + start.Wait(); + try + { + AtomicFile.Install(candidates[index], destination, overwrite: false); + Interlocked.Exchange(ref winner, index); + Interlocked.Increment(ref successes); + } + catch (IOException) when (File.Exists(destination)) + { + } + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray(); + bool prepared = ready.Wait(TimeSpan.FromSeconds(20)); + start.Set(); + await Task.WhenAll(writers).WaitAsync(TimeSpan.FromSeconds(20)); + Assert.True(prepared, "All candidate publishers must reach the start barrier."); + Assert.Equal(1, successes); + byte[] bytes = File.ReadAllBytes(destination); + Assert.Equal(64 * 1024, bytes.Length); + Assert.All(bytes, value => Assert.Equal((byte)winner, value)); + for (int index = 0; index < count; index++) + Assert.Equal(index != winner, File.Exists(candidates[index])); + } + } + + [Fact] + public async Task IndependentCreatorsAndAReaderObserveOnlyTheCompleteWinningPayload() + { + string project = Path.Combine(m_root, "Publisher.csproj"); + new XDocument(new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), + new XElement("PropertyGroup", new XElement("TargetFramework", "net10.0"), new XElement("OutputType", "Exe"), + new XElement("ImplicitUsings", "disable")), + new XElement("ItemGroup", new XElement("Reference", new XAttribute("Include", "Inno.Core.IO"), + new XElement("HintPath", typeof(AtomicFile).Assembly.Location))))).Save(project); + File.WriteAllText(Path.Combine(m_root, "Program.cs"), """ + using System; + using System.IO; + using System.Threading; + using Inno.Core.IO; + byte[] payload = new byte[1024 * 1024]; + Array.Fill(payload, byte.Parse(args[2])); + File.WriteAllText(args[1] + ".ready-" + args[2], "ready"); + long deadline = Environment.TickCount64 + 20000; + while (!File.Exists(args[1])) + { + if (Environment.TickCount64 > deadline) return 2; + Thread.Sleep(1); + } + try { AtomicFile.WriteAllBytes(args[0], payload, overwrite: false); return 0; } + catch (IOException) when (File.Exists(args[0])) { return 17; } + """); + using (Process build = Start("build", project, "--disable-build-servers", "-m:1", "-nodeReuse:false", "-v:quiet")) + { + Task output = build.StandardOutput.ReadToEndAsync(); + Task error = build.StandardError.ReadToEndAsync(); + try + { + await build.WaitForExitAsync().WaitAsync(TimeSpan.FromMinutes(2)); + } + finally + { + if (!build.HasExited) + { + build.Kill(entireProcessTree: true); + await build.WaitForExitAsync(); + } + } + Assert.True(build.ExitCode == 0, await output + await error); + } + string destination = Path.Combine(m_root, "concurrent.ini"); + string gate = Path.Combine(m_root, "start"); + string assembly = Path.Combine(m_root, "bin", "Debug", "net10.0", "Publisher.dll"); + Process[] creators = Enumerable.Range(1, 8).Select(index => Start(assembly, destination, gate, index.ToString())).ToArray(); + try + { + await WaitUntil(() => Directory.GetFiles(m_root, "start.ready-*").Length == creators.Length); + File.WriteAllText(gate, "start"); + await WaitUntil(() => File.Exists(destination)); + var document = new FileByteDocumentStore(destination); + byte[] winner = Assert.IsType(document.Read()); + Assert.Equal(1024 * 1024, winner.Length); + Assert.InRange(winner[0], (byte)1, (byte)8); + Assert.All(winner, value => Assert.Equal(winner[0], value)); + for (int read = 0; read < 16; read++) + Assert.Equal(winner, document.Read()); + await Task.WhenAll(creators.Select(process => process.WaitForExitAsync())).WaitAsync(TimeSpan.FromSeconds(30)); + Assert.Single(creators.Where(process => process.ExitCode == 0)); + Assert.All(creators, process => Assert.True(process.ExitCode is 0 or 17)); + Assert.Empty(Directory.GetFiles(m_root, "*.staging-*")); + } + finally + { + foreach (Process process in creators) + { + if (!process.HasExited) + process.Kill(entireProcessTree: true); + await process.WaitForExitAsync(); + process.Dispose(); + } + } + } + + public void Dispose() => Directory.Delete(m_root, recursive: true); + + private Process Start(params string[] arguments) + { + string sdk = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + var start = new ProcessStartInfo(sdk) + { + WorkingDirectory = m_root, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + foreach (string argument in arguments) + start.ArgumentList.Add(argument); + return Process.Start(start) ?? throw new InvalidOperationException("The SDK fixture did not start."); + } + + private static async Task WaitUntil(Func condition) + { + long deadline = Environment.TickCount64 + 20000; + while (!condition()) + { + Assert.True(Environment.TickCount64 < deadline, "The independent publishers did not reach their owned barrier."); + await Task.Delay(5); + } + } +} diff --git a/tests/core/Inno.Core.IO.Tests/ByteDocumentStoreTests.cs b/tests/core/Inno.Core.IO.Tests/ByteDocumentStoreTests.cs index 2ad49a75d..7b64575b9 100644 --- a/tests/core/Inno.Core.IO.Tests/ByteDocumentStoreTests.cs +++ b/tests/core/Inno.Core.IO.Tests/ByteDocumentStoreTests.cs @@ -40,4 +40,37 @@ public void FilesDistinguishAbsentAndEmptyAndReplaceCompleteDocuments() Directory.Delete(root, true); } } + + [Fact] + public void FilesReadTheCompleteDocumentWhileADeleteAccessHandleRemainsOpen() + { + string root = Path.Combine(Path.GetTempPath(), "inno-documents-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + string path = Path.Combine(root, "settings"); + byte[] expected = [3, 4, 5, 6]; + try + { + File.WriteAllBytes(path, expected); + using (var renameHandle = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.Read | FileShare.Delete, + 4096, + FileOptions.DeleteOnClose)) + { + if (OperatingSystem.IsWindows()) + Assert.ThrowsAny(() => File.ReadAllBytes(path)); + var store = new FileByteDocumentStore(path); + for (int read = 0; read < 16; read++) + Assert.Equal(expected, store.Read()); + Assert.True(File.Exists(path)); + } + Assert.False(File.Exists(path)); + } + finally + { + Directory.Delete(root, recursive: true); + } + } } diff --git a/tests/core/Inno.Core.IO.Tests/FileSystemSafetyTests.cs b/tests/core/Inno.Core.IO.Tests/FileSystemSafetyTests.cs index c33ba6b08..eea6589e0 100644 --- a/tests/core/Inno.Core.IO.Tests/FileSystemSafetyTests.cs +++ b/tests/core/Inno.Core.IO.Tests/FileSystemSafetyTests.cs @@ -123,6 +123,85 @@ public void DirectoryInstallReplacesACompleteTreeWithoutLeavingBackupDirectories Assert.Empty(Directory.EnumerateDirectories(m_root).Where(path => path.Contains(".backup-", StringComparison.Ordinal))); } + /// + /// Rejects canceled installation before creating parents or modifying either owned tree. + /// + /// Whether a previous destination is already present. + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DirectoryInstallPriorCancellationPreservesBothOwners(bool existing) + { + string candidate = Path.Combine(m_root, "candidate"); + string destination = Path.Combine(m_root, "publication", "destination"); + Directory.CreateDirectory(candidate); + File.WriteAllText(Path.Combine(candidate, "current.txt"), "current"); + if (existing) + { + Directory.CreateDirectory(destination); + File.WriteAllText(Path.Combine(destination, "previous.txt"), "previous"); + } + using var cancellation = new CancellationTokenSource(); + cancellation.Cancel(); + + Assert.ThrowsAny(() => AtomicDirectory.Install(candidate, destination, cancellation.Token)); + + Assert.Equal("current", File.ReadAllText(Path.Combine(candidate, "current.txt"))); + if (existing) + Assert.Equal("previous", File.ReadAllText(Path.Combine(destination, "previous.txt"))); + else + Assert.False(Directory.Exists(Path.GetDirectoryName(destination))); + Assert.Empty(Directory.EnumerateDirectories(m_root, "*.backup-*", SearchOption.AllDirectories)); + } + + /// + /// Restores an evacuated destination even though the request token is already canceled. + /// + /// The completed cancellation and rollback checks. + [WindowsDirectorySharingFact] + public async Task DirectoryInstallCancellationRestoresThePreviousTreeDuringABlockedRename() + { + string candidate = Path.Combine(m_root, "candidate"); + string destination = Path.Combine(m_root, "destination"); + Directory.CreateDirectory(candidate); + Directory.CreateDirectory(destination); + string candidateFile = Path.Combine(candidate, "current.txt"); + File.WriteAllText(candidateFile, "current"); + File.WriteAllText(Path.Combine(destination, "previous.txt"), "previous"); + using var reader = new FileStream(candidateFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); + using var cancellation = new CancellationTokenSource(); + var evacuated = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var watcher = new FileSystemWatcher(m_root) { NotifyFilter = NotifyFilters.DirectoryName }; + watcher.Renamed += ( + _, + change + ) => { + if (string.Equals(change.OldFullPath, destination, StringComparison.OrdinalIgnoreCase)) + evacuated.TrySetResult(true); + }; + watcher.EnableRaisingEvents = true; + Task installation = Task.Run(() => AtomicDirectory.Install(candidate, destination, cancellation.Token)); + try + { + await evacuated.Task.WaitAsync(TimeSpan.FromSeconds(1)); + Assert.False(installation.IsCompleted); + Assert.False(Directory.Exists(destination)); + cancellation.Cancel(); + await Assert.ThrowsAnyAsync(() => installation); + } + finally + { + cancellation.Cancel(); + reader.Dispose(); + watcher.EnableRaisingEvents = false; + _ = await Record.ExceptionAsync(() => installation); + } + + Assert.Equal("previous", File.ReadAllText(Path.Combine(destination, "previous.txt"))); + Assert.Equal("current", File.ReadAllText(candidateFile)); + Assert.Empty(Directory.EnumerateDirectories(m_root, "*.backup-*")); + } + [Fact] public async Task DirectoryPublicationToleratesATemporaryWindowsReaderWithoutDeleteSharing() { @@ -261,4 +340,14 @@ public void Dispose() if (Directory.Exists(m_root)) Directory.Delete(m_root, recursive: true); } + + private sealed class WindowsDirectorySharingFactAttribute : FactAttribute + { + /// Marks the Windows directory-sharing fixture as unexecuted on other hosts. + public WindowsDirectorySharingFactAttribute() + { + if (!OperatingSystem.IsWindows()) + Skip = "This fixture requires Windows directory rename sharing semantics."; + } + } } diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.ArrayKeys.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.ArrayKeys.cs new file mode 100644 index 000000000..295cf2fd7 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.ArrayKeys.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Inno.Core.Identity; +using Inno.Core.Serialization.Converters; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void KeyedEncodingIsCanonicalUnderConcurrentReadersAndFailedDestinations() + { + var identities = new IdentityAllocator(); + var row = new CaptureArrayRow { number = 7 }; + Assert.True(identities.Register(row)); + var source = new CaptureArrayRoot { rows = [row] }; + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(source); + byte[] expected = m_serialization.Serialize(source); + Parallel.For(0, 64, _ => + { + using var destination = new MemoryStream(); + snapshot.WriteTo(destination); + Assert.Equal(expected, destination.ToArray()); + }); + using var rejected = new RejectCaptureStream(); + Assert.Throws(() => snapshot.WriteTo(rejected)); + byte[] callerOwned = snapshot.ToArray(); + callerOwned[0] ^= 0x7F; + Assert.Equal(expected, snapshot.ToArray()); + Assert.Same(snapshot, m_serialization.CaptureSnapshot(source, previous: snapshot)); + } + + [Fact] + public void LargeKeyedObjectsRemainStreamedWithoutAContiguousPayloadAllocation() + { + var identities = new IdentityAllocator(); + var source = new CaptureBlobRoot { data = new byte[256 * 1024] }; + Array.Fill(source.data, (byte)0xA5); + Assert.True(identities.Register(source)); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(source); + Assert.Equal(m_serialization.Serialize(source), snapshot.ToArray()); + snapshot.WriteTo(Stream.Null); + long before = GC.GetAllocatedBytesForCurrentThread(); + snapshot.WriteTo(Stream.Null); + Assert.InRange(GC.GetAllocatedBytesForCurrentThread() - before, 0, 16 * 1024); + } + + [Fact] + public void KeyedArrayCapturesOnlyChangedRowsAndKeepsOrdinarySerializationAuthoritative() + { + var identities = new IdentityAllocator(); + var first = new CaptureArrayRow { number = 1 }; + var second = new CaptureArrayRow { number = 2 }; + Assert.True(identities.Register(first)); + Assert.True(identities.Register(second)); + var source = new CaptureArrayRoot { rows = [first, second] }; + SerializationSnapshot before = m_serialization.CaptureSnapshot(source); + Assert.Same(before, m_serialization.CaptureSnapshot(source, previous: before)); + Assert.Equal(1, first.writes); + Assert.Equal(1, second.writes); + first.number = 3; + first.revision++; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(source, previous: before); + Assert.Equal(2, first.writes); + Assert.Equal(1, second.writes); + int keys = source.keys; + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + Assert.Equal(keys, source.keys); + Assert.Equal(3, first.writes); + Assert.Equal(2, second.writes); + source.mode = 4; + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + source.rows.Reverse(); + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + source.rows.RemoveAt(0); + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + source.rows.Clear(); + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + Assert.Same(changed, m_serialization.CaptureSnapshot(source, previous: changed)); + } + + [Fact] + public void ArrayKeysVerifyExactIdentifiersAndPreservePreviousSnapshotsOnFailure() + { + var identities = new IdentityAllocator(); + var row = new CaptureArrayRow(); + Assert.True(identities.Register(row)); + var source = new CaptureArrayRoot { rows = [row] }; + SerializationSnapshot before = m_serialization.CaptureSnapshot(source); + byte[] bytes = before.ToArray(); + source.scope = Guid.NewGuid(); + SerializationSnapshot changed = m_serialization.CaptureSnapshot(source, previous: before); + Assert.NotEqual(bytes, changed.ToArray()); + row.revision++; + source.onWrite = () => source.scope = Guid.NewGuid(); + Assert.Throws(() => m_serialization.CaptureSnapshot(source, previous: changed)); + source.onWrite = null; + Assert.Equal(bytes, before.ToArray()); + Assert.Throws(() => source.savedBuilder!.AddValue(1UL)); + source.failKey = true; + Assert.Throws(() => m_serialization.CaptureSnapshot(source, previous: changed)); + source.failKey = false; + Assert.True(identities.Unregister(row)); + Assert.True(identities.Register(row)); + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + } + + [Fact] + public void NestedStatefulConvertersPreventEnclosingArrayKeyReuse() + { + var identities = new IdentityAllocator(); + var row = new CaptureArrayRow(); + Assert.True(identities.Register(row)); + var source = new CaptureArrayRoot { rows = [row], stateful = true }; + SerializationSnapshot before = m_serialization.CaptureSnapshot(source); + int calls = CaptureConvertedValueConverter.calls; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(source, previous: before); + Assert.Equal(calls + 1, CaptureConvertedValueConverter.calls); + Assert.Equal(2, row.writes); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + } + + [Fact] + public void DeclinedArrayKeysCannotReuseAnEarlierCertifiedElement() + { + var identities = new IdentityAllocator(); + var row = new CaptureArrayRow(); + Assert.True(identities.Register(row)); + var source = new CaptureArrayRoot { rows = [row] }; + SerializationSnapshot before = m_serialization.CaptureSnapshot(source); + source.allowKeys = false; + row.number = 8; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(source, previous: before); + Assert.Equal(2, row.writes); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + row.number = 11; + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + source.allowKeys = true; + row.revision++; + changed = m_serialization.CaptureSnapshot(source, previous: changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + } +} + +internal sealed class CaptureArrayRoot : ISerializable +{ + internal List rows = []; + internal Guid scope = Guid.NewGuid(); + internal int mode; + internal int keys; + internal bool allowKeys = true; + internal bool stateful; + internal bool failKey; + internal Action? onWrite; + internal SerializationCaptureKeyBuilder? savedBuilder; +} + +internal sealed class CaptureArrayRow : IdentityObject, ISerializable +{ + internal int number; + internal int writes; + internal ulong? revision = 0; + internal ConvertedCaptureProperties state = new(); +} + +internal sealed class CaptureArrayRootConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + CaptureArrayRoot value + ) { + writer.Write("scope", value.scope); + writer.WriteObjectArray("rows", value.rows, ( + child, + row + ) => { + row.writes++; + value.onWrite?.Invoke(); + child.Write("number", row.number); + child.Write("mode", value.mode); + if (value.stateful) + child.WritePropertiesData("state", row.state); + }, captureKey: ( + row, + key + ) => { + value.keys++; + value.savedBuilder = key; + if (value.failKey) + throw new InvalidOperationException("Array key failure."); + if (!value.allowKeys || !key.TryAddOwner(row, row.revision)) + return false; + key.AddValue(value.scope); + key.AddValue(unchecked((ulong)value.mode)); + key.AddValue(value.stateful ? 1UL : 0UL); + return true; + }); + } + + public override CaptureArrayRoot Read(SerializationReader reader) => throw new NotSupportedException(); +} + +internal sealed class CaptureBlobRoot : IdentityObject, ISerializable +{ + internal byte[] data = []; +} + +internal sealed class CaptureBlobRootConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + CaptureBlobRoot value + ) => writer.Write("data", value.data); + + public override CaptureBlobRoot Read(SerializationReader reader) => throw new NotSupportedException(); + + protected override bool TryCaptureKey( + CaptureBlobRoot value, + SerializationCaptureKeyBuilder key + ) => key.TryAddOwner(value, 0); +} + +internal sealed class RejectCaptureStream : MemoryStream +{ + public override void Write(ReadOnlySpan buffer) => throw new IOException("The test destination rejects writes."); +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.CaptureKeys.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.CaptureKeys.cs new file mode 100644 index 000000000..103574b17 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.CaptureKeys.cs @@ -0,0 +1,550 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Threading.Tasks; +using Inno.Adapter.Serialization.DotNet; +using Inno.Core.Identity; +using Inno.Core.Serialization.Converters; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void DeclaredCaptureKeysReuseOnlyCompleteCurrentInputsAndNeverChangeEncodedBytes() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot { number = 4, text = "text" }; + Assert.True(identities.Register(value)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + byte[] bytes = before.ToArray(); + Assert.Equal(m_serialization.Serialize(value), bytes); + int writes = value.writeCalls; + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(writes, value.writeCalls); + + value.Touch(); + value.number = 9; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: before); + Assert.NotSame(before, changed); + Assert.Equal(m_serialization.Serialize(value), changed.ToArray()); + Assert.Equal(bytes, before.ToArray()); + Assert.Same(changed, m_serialization.CaptureSnapshot(value, previous: changed)); + + int keys = value.keyCalls; + _ = m_serialization.Serialize(value); + Assert.Equal(keys, value.keyCalls); + } + + [Fact] + public void AParentKeyCannotImplicitlyCertifyANestedConverterState() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot { converted = new CaptureConvertedValue { number = 4 } }; + Assert.True(identities.Register(value)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + int calls = CaptureConvertedValueConverter.calls; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(calls + 1, CaptureConvertedValueConverter.calls); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + Assert.Equal(2, value.writeCalls); + } + + [Fact] + public void CaptureKeysIncludeContextInputsAndCurrentConverterGeneration() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot(); + Assert.True(identities.Register(value)); + var dependency = new CaptureKeyContext { value = 1 }; + SerializationContext context = SerializationContext.empty.With(dependency); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value, context); + Assert.Same(before, m_serialization.CaptureSnapshot(value, context, before)); + dependency.value = 2; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, context, before); + Assert.Equal(m_serialization.Serialize(value, context), changed.ToArray()); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + + int writes = value.writeCalls; + m_types.Rebuild(); + SerializationSnapshot refreshed = m_serialization.CaptureSnapshot(value, context, changed); + Assert.True(value.writeCalls > writes); + Assert.Equal(changed.ToArray(), refreshed.ToArray()); + Assert.Same(refreshed, m_serialization.CaptureSnapshot(value, context, refreshed)); + } + + [Fact] + public void CaptureKeysQualifyIdentityByDomainAndRejectRetiredBindings() + { + var first = new IdentityAllocator(); + var second = new IdentityAllocator(); + var original = new CaptureKeyRoot(); + var replacement = new CaptureKeyRoot(); + Assert.True(first.Register(original)); + Assert.True(second.Register(replacement, original.identity.persistentId)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(original); + SerializationSnapshot changed = m_serialization.CaptureSnapshot(replacement, previous: before); + Assert.NotSame(before, changed); + Assert.Equal(before.ToArray(), changed.ToArray()); + Assert.Equal(1, replacement.writeCalls); + + Assert.True(second.Unregister(replacement)); + Assert.True(second.Register(replacement)); + int writes = replacement.writeCalls; + SerializationSnapshot rebound = m_serialization.CaptureSnapshot(replacement, previous: changed); + Assert.True(replacement.writeCalls > writes); + Assert.NotSame(changed, rebound); + Assert.Equal(changed.ToArray(), rebound.ToArray()); + } + + [Fact] + public void DeclinedAndUnavailableKeysCannotPreserveAnEarlierHint() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot(); + Assert.True(identities.Register(value)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + value.allowKey = false; + SerializationSnapshot declined = m_serialization.CaptureSnapshot(value, previous: before); + int writes = value.writeCalls; + _ = m_serialization.CaptureSnapshot(value, previous: declined); + Assert.Equal(writes + 1, value.writeCalls); + Assert.Equal(before.ToArray(), declined.ToArray()); + + value.allowKey = true; + value.revision = null; + value.ignoreDecline = true; + value.number = 7; + SerializationSnapshot unavailable = m_serialization.CaptureSnapshot(value, previous: declined); + Assert.Equal(m_serialization.Serialize(value), unavailable.ToArray()); + writes = value.writeCalls; + _ = m_serialization.CaptureSnapshot(value, previous: unavailable); + Assert.Equal(writes + 1, value.writeCalls); + } + + [Fact] + public void UntrackedDirectPropertyWritesRemainAuthoritativeEvenWhenAProducerIgnoresDecline() + { + var identities = new IdentityAllocator(); + var properties = new UntrackedCaptureProperties(); + var value = new CaptureKeyRoot { properties = properties, ignoreDecline = true }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + properties.number = 27; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(m_serialization.Serialize(value), changed.ToArray()); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + int writes = value.writeCalls; + _ = m_serialization.CaptureSnapshot(value, previous: changed); + Assert.Equal(writes + 1, value.writeCalls); + } + + [Fact] + public void ProvenPropertyRevisionsInvalidateWithoutPersistingTheirCounter() + { + var identities = new IdentityAllocator(); + var properties = new CaptureRevisionProperties { number = 3 }; + var value = new CaptureKeyRoot { properties = properties }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + _ = m_serialization.CapturePropertiesData(properties); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + int writes = value.writeCalls; + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(writes, value.writeCalls); + + properties.number = 12; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: before); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + Assert.Equal(m_serialization.Serialize(value), changed.ToArray()); + Assert.Single(m_serialization.CaptureProperties(properties)); + Assert.Equal("number", m_serialization.CaptureProperties(properties)[0].name); + } + + [Fact] + public void CaptureKeyQueriesDoNotConstructOrSkipStatefulConverters() + { + CaptureConvertedValueConverter.events.Clear(); + CaptureConvertedValueConverter.calls = 0; + var identities = new IdentityAllocator(); + var properties = new ConvertedCaptureProperties(); + var value = new CaptureKeyRoot + { + properties = properties, + onWrite = () => CaptureConvertedValueConverter.events.Add("root-write") + }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + Assert.Equal(new[] { "root-write", "construct", "convert-write" }, CaptureConvertedValueConverter.events); + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(2, CaptureConvertedValueConverter.calls); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + } + + [Fact] + public void PureFieldMetadataPreservesExactFloatBitsAcrossTrackedPropertyChanges() + { + var identities = new IdentityAllocator(); + var properties = new PureFieldCaptureProperties { value = new PureCaptureFields { number = -0.0f } }; + var value = new CaptureKeyRoot { properties = properties }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + _ = m_serialization.CapturePropertiesData(properties); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + properties.value = new PureCaptureFields { number = 0.0f }; + SerializationSnapshot zero = m_serialization.CaptureSnapshot(value, previous: before); + Assert.NotEqual(before.ToArray(), zero.ToArray()); + foreach (int bits in new[] { 0x7fc00001, 0x7fc00002 }) + { + properties.value = new PureCaptureFields { number = BitConverter.Int32BitsToSingle(bits) }; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(value, previous: zero); + Assert.NotEqual(zero.ToArray(), changed.ToArray()); + Assert.Equal(m_serialization.Serialize(value), changed.ToArray()); + zero = changed; + } + } + + [Fact] + public void NestedPropertyGettersCannotBeMistakenForPureValueFields() + { + CaptureGetterStruct.calls = 0; + var identities = new IdentityAllocator(); + var properties = new GetterCaptureProperties(); + var value = new CaptureKeyRoot { properties = properties }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + int calls = CaptureGetterStruct.calls; + int writes = value.writeCalls; + _ = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(calls + 1, CaptureGetterStruct.calls); + Assert.Equal(writes + 1, value.writeCalls); + } + + [Fact] + public void CaptureKeyFailureAndMutationNeverPublishOverThePreviousSnapshot() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot { number = 2 }; + Assert.True(identities.Register(value)); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + byte[] bytes = before.ToArray(); + value.failKey = true; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Throws(() => value.savedBuilder!.AddValue(1)); + value.failKey = false; + value.Touch(); + value.failWrite = true; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + value.failWrite = false; + value.onWrite = value.Touch; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(bytes, before.ToArray()); + value.onWrite = null; + Assert.Equal(m_serialization.Serialize(value), m_serialization.CaptureSnapshot(value, previous: before).ToArray()); + } + + [Fact] + public void CaptureKeysValidateEveryBorrowedIdentityAfterTheWrite() + { + var identities = new IdentityAllocator(); + var properties = new CaptureRevisionProperties(); + var value = new CaptureKeyRoot { properties = properties }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register(properties)); + _ = m_serialization.CapturePropertiesData(properties); + value.onWrite = () => Assert.True(identities.Unregister(properties)); + Assert.Throws(() => m_serialization.CaptureSnapshot(value)); + Assert.Throws(() => value.savedBuilder!.context); + value.onWrite = null; + Assert.True(identities.Register(properties)); + Assert.NotEmpty(m_serialization.CaptureSnapshot(value).ToArray()); + } + + [Fact] + public void CaptureKeyBuildersRejectAnotherThreadAndRetireAfterEveryCallback() + { + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot + { + onKey = key => Task.Run(() => + Assert.Throws(() => key.AddText("wrong thread"))).GetAwaiter().GetResult() + }; + Assert.True(identities.Register(value)); + _ = m_serialization.CaptureSnapshot(value); + SerializationCaptureKeyBuilder builder = value.savedBuilder!; + Assert.Throws(() => builder.context); + Assert.Throws(() => builder.EnsureCapacity(1)); + Assert.Throws(() => builder.TryAddOwner(value, 0)); + } + + [Fact] + public void EquivalentFreshMetadataDeclinesHintsWithoutBreakingOrdinaryCapture() + { + using var serialization = new SerializationRegistry(m_types, new FreshCaptureMetadataSource()); + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot(); + Assert.True(identities.Register(value)); + SerializationSnapshot before = serialization.CaptureSnapshot(value); + int writes = value.writeCalls; + SerializationSnapshot after = serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(writes + 1, value.writeCalls); + Assert.Equal(before.ToArray(), after.ToArray()); + Assert.Equal(serialization.Serialize(value), after.ToArray()); + } + + [Fact] + public void MetadataRevisionDeclarationsRejectCountersInTheSerializedPayload() + { + var metadata = new ReflectionSerializationMetadataSource(); + Assert.Throws(() => metadata.GetMetadata(typeof(SerializedCaptureCounter))); + } + + [Fact] + public void RetainedCaptureKeysAndRetiredBuildersDoNotPinACollectiblePropertyAssembly() + { + (SerializationSnapshot snapshot, SerializationCaptureKeyBuilder builder, WeakReference assembly, WeakReference context) = + CaptureCollectibleProperties(); + for (int index = 0; index < 10 && (assembly.IsAlive || context.IsAlive); index++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + Assert.False(assembly.IsAlive); + Assert.False(context.IsAlive); + Assert.Throws(() => builder.context); + Assert.NotEmpty(snapshot.ToArray()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private (SerializationSnapshot, SerializationCaptureKeyBuilder, WeakReference, WeakReference) CaptureCollectibleProperties() + { + var context = new CapturePropertyLoadContext(); + var assembly = context.LoadFromAssemblyPath(typeof(CaptureRevisionProperties).Assembly.Location); + Type type = assembly.GetType(typeof(CaptureRevisionProperties).FullName!, throwOnError: true)!; + var properties = (ISerializable)Activator.CreateInstance(type)!; + var identities = new IdentityAllocator(); + var value = new CaptureKeyRoot { properties = properties }; + Assert.True(identities.Register(value)); + Assert.True(identities.Register((IdentityObject)properties)); + _ = m_serialization.CapturePropertiesData(properties); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(value); + Assert.Same(snapshot, m_serialization.CaptureSnapshot(value, previous: snapshot)); + SerializationCaptureKeyBuilder builder = value.savedBuilder!; + Assert.True(identities.Unregister((IdentityObject)properties)); + Assert.True(identities.Unregister(value)); + value.properties = null; + var assemblyReference = new WeakReference(assembly); + var contextReference = new WeakReference(context); + context.Unload(); + return (snapshot, builder, assemblyReference, contextReference); + } +} + +internal sealed class CaptureKeyRoot : IdentityObject, ISerializable +{ + internal ulong? revision = 0; + internal int number; + internal string? text; + internal bool allowKey = true; + internal bool ignoreDecline; + internal bool failKey; + internal bool failWrite; + internal int writeCalls; + internal int keyCalls; + internal ISerializable? properties; + internal CaptureConvertedValue? converted; + internal Action? onWrite; + internal Action? onKey; + internal SerializationCaptureKeyBuilder? savedBuilder; + + internal void Touch() => revision++; +} + +internal sealed class CaptureKeyContext +{ + internal ulong value; +} + +internal sealed class CaptureKeyRootConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + CaptureKeyRoot value + ) { + value.writeCalls++; + value.onWrite?.Invoke(); + if (value.failWrite) + throw new InvalidOperationException("Capture writer failure."); + writer.Write("number", value.number); + writer.Write("text", value.text); + writer.Write("context", writer.context.TryGet(out CaptureKeyContext? context) ? context!.value : 0UL); + if (value.properties is not null) + writer.WritePropertiesData("properties", value.properties); + if (value.converted.HasValue) + writer.Write("converted", value.converted.Value); + } + + public override CaptureKeyRoot Read(SerializationReader reader) => throw new NotSupportedException(); + + protected override bool TryCaptureKey( + CaptureKeyRoot value, + SerializationCaptureKeyBuilder key + ) { + value.keyCalls++; + value.savedBuilder = key; + if (value.failKey) + throw new InvalidOperationException("Capture key failure."); + if (!value.allowKey) + return false; + key.EnsureCapacity(5); + bool complete = key.TryAddOwner(value, value.revision); + key.AddValue(key.context.TryGet(out CaptureKeyContext? context) ? context!.value : 0UL); + if (value.properties is not null) + complete &= key.TryAddProperties(value.properties); + value.onKey?.Invoke(key); + return complete || value.ignoreDecline; + } +} + +/// +/// Provides a real public extension type for collectible metadata and capture-key acceptance. +/// +[SerializationRevision("m_revision")] +public sealed class CaptureRevisionProperties : IdentityObject, ISerializable +{ + private ulong? m_revision = 0; + private int m_number; + + /// + /// Creates an initially empty tracked property source. + /// + public CaptureRevisionProperties() { } + + /// + /// Gets or sets the serialized number, advancing its complete source-owned revision before mutation. + /// + [SerializableProperty] + public int number + { + get => m_number; + set + { + if (m_revision is { } revision) + m_revision = revision == ulong.MaxValue ? null : revision + 1; + m_number = value; + } + } +} + +internal sealed class UntrackedCaptureProperties : IdentityObject, ISerializable +{ + [SerializableProperty] public int number; +} + +[SerializationRevision("revision")] +internal sealed class ConvertedCaptureProperties : IdentityObject, ISerializable +{ + private ulong? revision => 0; + [SerializableProperty] public CaptureConvertedValue value = new() { number = 1 }; +} + +[SerializationRevision("m_revision")] +internal sealed class PureFieldCaptureProperties : IdentityObject, ISerializable +{ + private ulong? m_revision = 0; + private PureCaptureFields m_value; + + [SerializableProperty] + public PureCaptureFields value + { + get => m_value; + set + { + m_revision++; + m_value = value; + } + } +} + +internal struct PureCaptureFields +{ + public float number; +} + +[SerializationRevision("revision")] +internal sealed class GetterCaptureProperties : IdentityObject, ISerializable +{ + private ulong? revision => 0; + [SerializableProperty] public CaptureGetterStruct value = new(); +} + +internal struct CaptureGetterStruct +{ + internal static int calls; + + public int value + { + get + { + calls++; + return 1; + } + set { } + } +} + +internal struct CaptureConvertedValue +{ + public int number; +} + +internal sealed class CaptureConvertedValueConverter : SerializationConverter +{ + internal static readonly List events = []; + internal static int calls; + + public CaptureConvertedValueConverter() => events.Add("construct"); + + public override void Write( + SerializationWriter writer, + CaptureConvertedValue value + ) { + events.Add("convert-write"); + writer.Write("number", value.number); + writer.Write("call", ++calls); + } + + public override CaptureConvertedValue Read(SerializationReader reader) => new() { number = reader.Read("number") }; +} + +[SerializationRevision("revision")] +internal sealed class SerializedCaptureCounter : IdentityObject, ISerializable +{ + [SerializableProperty(PropertyVisibility.Serialize)] public ulong revision => 0; +} + +internal sealed class FreshCaptureMetadataSource : ISerializationMetadataSource +{ + private readonly ReflectionSerializationMetadataSource m_source = new(); + + public SerializationTypeMetadata GetMetadata(Type type) + { + SerializationTypeMetadata original = m_source.GetMetadata(type); + return new(type, original.members, original.CreateInstance, original.restored, original.collection, original.requiresConverter); + } +} + +internal sealed class CapturePropertyLoadContext() : AssemblyLoadContext(isCollectible: true) +{ + protected override System.Reflection.Assembly? Load(System.Reflection.AssemblyName name) + => Default.Assemblies.FirstOrDefault(assembly => assembly.GetName().Name == name.Name); +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Concurrency.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Concurrency.cs new file mode 100644 index 000000000..520497b77 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Concurrency.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Inno.Core.Serialization.Converters; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void ConcurrentCapturedOperationsPublishOneConverterAndRetireItAfterTheLastLease() + { + ConcurrentCaptureConverter.constructions = 0; + ConcurrentCaptureConverter.disposals = 0; + using SerializationGeneration generation = m_serialization.CaptureGeneration(); + var scalar = new DefaultSample { count = 73, name = "Pinned" }; + byte[] expected = generation.Serialize(scalar); + + Parallel.For(0, 256, new ParallelOptions { MaxDegreeOfParallelism = 8 }, index => + { + var value = new ConcurrentCaptureValue(index); + byte[] bytes = generation.Serialize(value); + Assert.Equal(index, generation.Deserialize(bytes).count); + Assert.Equal(expected, generation.Serialize(scalar)); + }); + + Assert.Equal(1, ConcurrentCaptureConverter.constructions); + m_serialization.Dispose(); + Assert.Equal(0, ConcurrentCaptureConverter.disposals); + Assert.Equal(expected, generation.Serialize(scalar)); + generation.Dispose(); + Assert.Equal(1, ConcurrentCaptureConverter.disposals); + Assert.Throws(() => generation.Serialize(scalar)); + } +} + +internal sealed class ConcurrentCaptureValue(int count) : ISerializable +{ + internal int count { get; } = count; +} + +internal sealed class ConcurrentCaptureConverter : SerializationConverter, IDisposable +{ + internal static int constructions; + internal static int disposals; + + /// + /// Records construction of the converter owned by the captured generation. + /// + public ConcurrentCaptureConverter() => Interlocked.Increment(ref constructions); + + /// + public override void Write( + SerializationWriter writer, + ConcurrentCaptureValue value + ) => writer.Write("count", value.count); + + /// + public override ConcurrentCaptureValue Read(SerializationReader reader) => new(reader.Read("count")); + + /// + public void Dispose() => Interlocked.Increment(ref disposals); +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Efficiency.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Efficiency.cs new file mode 100644 index 000000000..1b58c184c --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Efficiency.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Inno.Adapter.Serialization.DotNet; +using Inno.Extensibility.Types; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReplacingContextServicesReleasesTheReplacedValueAtEveryDepth(bool nested) + { + (SerializationContext context, WeakReference replaced) = ReplaceContextService(nested); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(replaced.IsAlive); + Assert.NotNull(context.GetRequired()); + if (nested) + { + Assert.Equal("marker", context.GetRequired()); + Assert.Equal(new[] { 1, 2 }, context.GetRequired>()); + } + GC.KeepAlive(context); + } + + [Fact] + public void PreparedSerializationContextsAvoidRepeatedOwnerCopies() + { + SerializationContext? prepared = null; + _ = m_serialization.Encode(writer => prepared = writer.context); + var source = new DefaultSample { count = 73 }; + byte[] expected = m_serialization.CapturePropertyData(source, nameof(DefaultSample.count)); + long fresh = Measure(null); + long inherited = Measure(prepared); + + Assert.True(inherited < fresh && inherited <= 1024, + $"Prepared context allocated {inherited} bytes; fresh context allocated {fresh} bytes."); + + long Measure(SerializationContext? context) + { + for (int index = 0; index < 16; index++) + _ = m_serialization.CapturePropertyData(source, nameof(DefaultSample.count), context); + byte[]? actual = null; + long before = GC.GetAllocatedBytesForCurrentThread(); + for (int index = 0; index < 128; index++) + actual = m_serialization.CapturePropertyData(source, nameof(DefaultSample.count), context); + long allocated = (GC.GetAllocatedBytesForCurrentThread() - before) / 128; + Assert.Equal(expected, actual); + return allocated; + } + } + + [Fact] + public void SerializationRebindsForeignOwnersAndPreservesCallerContext() + { + var metadata = new ReflectionSerializationMetadataSource(); + using var foreign = new SerializationRegistry(m_types, metadata); + var service = new SampleContext(); + SerializationContext? prepared = null; + _ = foreign.Encode(writer => prepared = writer.context, SerializationContext.empty.With(service)); + ISerializationMetadataSource? currentMetadata = null; + _ = m_serialization.Encode(writer => currentMetadata = writer.context.GetRequired()); + + _ = m_serialization.Encode(writer => + { + Assert.Same(m_serialization, writer.context.GetRequired()); + Assert.Same(m_types, writer.context.GetRequired()); + Assert.Same(currentMetadata, writer.context.GetRequired()); + Assert.Same(service, writer.context.GetRequired()); + }, prepared); + + Assert.Same(foreign, prepared!.GetRequired()); + Assert.Same(metadata, prepared.GetRequired()); + } + + [Fact] + public void BinaryObjectKeysRemainOrdinalAndIndependentOfInsertionOrder() + { + string[] keys = ["z", "A", "a", "é", "中文", "😀"]; + byte[] forward = m_serialization.Encode(writer => + { + foreach (string key in keys) + writer.Write(key, key); + }); + byte[] reverse = m_serialization.Encode(writer => + { + for (int index = keys.Length - 1; index >= 0; index--) + writer.Write(keys[index], keys[index]); + }); + + Assert.Equal(forward, reverse); + } + + [Fact] + public void SmallBinaryDocumentsStayWithinABoundedAllocationBudget() + { + byte[] expected = m_serialization.Encode(static writer => writer.Write("value", 73)); + for (int index = 0; index < 16; index++) + _ = m_serialization.Encode(static writer => writer.Write("value", 73)); + + const int C_ITERATIONS = 128; + byte[]? actual = null; + long before = GC.GetAllocatedBytesForCurrentThread(); + for (int index = 0; index < C_ITERATIONS; index++) + actual = m_serialization.Encode(static writer => writer.Write("value", 73)); + long allocated = GC.GetAllocatedBytesForCurrentThread() - before; + + Assert.Equal(expected, actual); + Assert.InRange(allocated / C_ITERATIONS, 0, 4096); + } + + [Fact] + public void PropertyCaptureDoesNotReadUnrelatedAnnotatedGetters() + { + var source = new SelectiveCaptureSample { count = 73 }; + byte[] data = m_serialization.CapturePropertyData(source, nameof(SelectiveCaptureSample.count)); + var target = new DefaultSample { count = 2, name = "Preserved" }; + + SerializationPropertyRestoreResult result = m_serialization.RestorePropertiesData(target, data); + + Assert.True(result.success); + Assert.Equal(1, result.restoredCount); + Assert.Equal(73, target.count); + Assert.Equal("Preserved", target.name); + Assert.Equal(0, source.unrelatedReadCount); + } + + [Theory] + [InlineData("missing")] + [InlineData(nameof(SelectiveCaptureSample.ignored))] + public void PropertyCaptureRejectsMissingMembersWithoutReadingUnrelatedGetters(string name) + { + var source = new SelectiveCaptureSample(); + + Assert.Throws(() => m_serialization.CapturePropertyData(source, name)); + + Assert.Equal(0, source.unrelatedReadCount); + } + + [Theory] + [InlineData(0)] + [InlineData(63)] + [InlineData(257)] + [InlineData(10_240)] + [InlineData(200_000)] + public void BinaryDocumentsRoundTripAcrossBufferGrowthBoundaries(int length) + { + var payload = new byte[length]; + for (int index = 0; index < payload.Length; index++) + payload[index] = unchecked((byte)index); + + byte[] data = m_serialization.Serialize(new DefaultSample { bytes = payload }); + DefaultSample restored = m_serialization.Deserialize(data); + + Assert.Equal(payload, restored.bytes); + } + + private sealed class SelectiveCaptureSample : ISerializable + { + private int m_unrelatedReads; + + [SerializableProperty] + public int count { get; set; } + + [SerializableProperty] + public int unrelated + { + get + { + m_unrelatedReads++; + throw new InvalidOperationException("An unrelated getter must not be evaluated."); + } + set { } + } + + public int ignored { get; set; } + public int unrelatedReadCount => m_unrelatedReads; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static (SerializationContext, WeakReference) ReplaceContextService(bool nested) + { + var replaced = new object(); + SerializationContext context = SerializationContext.empty.With(replaced); + if (nested) + context = context.With("marker").With>(new[] { 1, 2 }); + return (context.With(new object()), new WeakReference(replaced)); + } + +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Encoding.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Encoding.cs new file mode 100644 index 000000000..b3c267f72 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Encoding.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using System.Text; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void EncodedScalarBytesMatchTheBinaryWriterContract() + { + object?[] values = + [ + null, false, true, byte.MaxValue, sbyte.MinValue, short.MinValue, ushort.MaxValue, + int.MinValue, uint.MaxValue, long.MinValue, ulong.MaxValue, + float.NegativeInfinity, float.PositiveInfinity, float.NaN, -0f, float.Epsilon, + double.NegativeInfinity, double.PositiveInfinity, double.NaN, -0d, double.Epsilon, + decimal.MinValue, decimal.MaxValue, new decimal(0, 0, 0, true, 28), + string.Empty, "English / 中文 / 🧪", Guid.Empty, Guid.Parse("62ce926a-e339-493d-bb74-743ed44ef37a") + ]; + foreach (object? value in values) + Assert.Equal(EncodeWithBinaryWriter(value), m_serialization.Encode(writer => writer.Write("value", value, value?.GetType() ?? typeof(string)))); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(63)] + [InlineData(127)] + [InlineData(128)] + [InlineData(16_383)] + [InlineData(16_384)] + public void EncodedStringsKeepUtf8AndSevenBitLengthSemantics(int length) + { + string[] values = + [ + new string('a', length), new string('中', length), + new string((char)0xD800, length), "🧪" + new string('a', length) + ]; + foreach (string value in values) + Assert.Equal(EncodeWithBinaryWriter(value), m_serialization.Encode(writer => writer.Write("value", value))); + } + + [Fact] + public void LargeBinaryEncodingAllocatesOnlyTheOwnedCaptureAndResult() + { + var source = new byte[1_000_000]; + source[0] = 73; + source[^1] = 19; + for (int index = 0; index < 4; index++) + _ = m_serialization.Encode(writer => writer.Write("value", source)); + long before = GC.GetAllocatedBytesForCurrentThread(); + byte[] data = []; + const int repetitions = 16; + for (int index = 0; index < repetitions; index++) + data = m_serialization.Encode(writer => writer.Write("value", source)); + long allocated = (GC.GetAllocatedBytesForCurrentThread() - before) / repetitions; + // Retain the per-call bound while amortizing one-time runtime/catalog admission during the suite. + Assert.InRange(allocated, source.Length * 2, source.Length * 2 + 8192); + source[0] = 0; + byte[] restored = m_serialization.Decode(data, reader => reader.Read("value")); + Assert.Equal(73, restored[0]); + Assert.Equal(19, restored[^1]); + } + + private static byte[] EncodeWithBinaryWriter(object? value) + { + using var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write("INNO-BINARY-CURRENT"); + writer.Write((byte)20); + writer.Write(1); + writer.Write("value"); + switch (value) + { + case null: + writer.Write((byte)0); + break; + case bool scalar: + writer.Write((byte)1); + writer.Write(scalar); + break; + case byte scalar: + writer.Write((byte)2); + writer.Write(scalar); + break; + case sbyte scalar: + writer.Write((byte)3); + writer.Write(scalar); + break; + case short scalar: + writer.Write((byte)4); + writer.Write(scalar); + break; + case ushort scalar: + writer.Write((byte)5); + writer.Write(scalar); + break; + case int scalar: + writer.Write((byte)6); + writer.Write(scalar); + break; + case uint scalar: + writer.Write((byte)7); + writer.Write(scalar); + break; + case long scalar: + writer.Write((byte)8); + writer.Write(scalar); + break; + case ulong scalar: + writer.Write((byte)9); + writer.Write(scalar); + break; + case float scalar: + writer.Write((byte)10); + writer.Write(scalar); + break; + case double scalar: + writer.Write((byte)11); + writer.Write(scalar); + break; + case decimal scalar: + writer.Write((byte)12); + writer.Write(scalar); + break; + case string scalar: + writer.Write((byte)13); + writer.Write(scalar); + break; + case Guid scalar: + writer.Write((byte)14); + writer.Write(scalar.ToByteArray()); + break; + default: throw new InvalidOperationException("The independent byte oracle received an unsupported scalar."); + } + writer.Flush(); + return stream.ToArray(); + } +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.OrdinalReuse.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.OrdinalReuse.cs new file mode 100644 index 000000000..f333958a3 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.OrdinalReuse.cs @@ -0,0 +1,43 @@ +using System; +using Inno.Core.Serialization.Converters; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void ReusedScalarKeysPreserveCanonicalBytesAndRejectOutOfOrderDuplicates() + { + var value = new OrdinalCapture(); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + byte[] bytes = before.ToArray(); + value.names = ["third", "first", "second"]; + SerializationSnapshot reordered = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(m_serialization.Serialize(value), reordered.ToArray()); + Assert.Equal(bytes, reordered.ToArray()); + value.names = ["third", "first", "third"]; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(bytes, before.ToArray()); + value.names = ["first", "second", "third"]; + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + } +} + +internal sealed class OrdinalCapture : ISerializable +{ + internal string[] names = ["first", "second", "third"]; +} + +internal sealed class OrdinalCaptureConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + OrdinalCapture value + ) { + foreach (string name in value.names) + writer.Write(name, 7); + } + + public override OrdinalCapture Read(SerializationReader reader) => throw new NotSupportedException(); +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Snapshots.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Snapshots.cs new file mode 100644 index 000000000..5aaf3fe51 --- /dev/null +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.Snapshots.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Inno.Core.Serialization.Converters; +using Xunit; + +namespace Inno.Core.Serialization.Tests; + +public sealed partial class SerializationBehaviorTests +{ + [Fact] + public void BinaryValuesFreezeAtTheirOwningCaptureBoundary() + { + byte[] ordinary = m_serialization.Encode(writer => + { + byte[] bytes = [1]; + writer.Write("bytes", bytes); + bytes[0] = 2; + }); + Assert.Equal(new byte[] { 1 }, m_serialization.Decode(ordinary, reader => reader.Read("bytes"))); + + var value = new MutatingBinaryCapture(); + SerializationSnapshot captured = m_serialization.CaptureSnapshot(value); + Assert.Equal(new byte[] { 1 }, m_serialization.Deserialize(captured.ToArray()).bytes); + value.bytes[0] = 1; + Assert.Same(captured, m_serialization.CaptureSnapshot(value, previous: captured)); + value.bytes[0] = 9; + Assert.Equal(new byte[] { 1 }, m_serialization.Deserialize(captured.ToArray()).bytes); + + var properties = new MutatingPropertyCapture(); + byte[] expected = m_serialization.EncodePropertySnapshots(m_serialization.CaptureProperties(properties)); + properties.Reset(); + Assert.Equal(expected, m_serialization.CapturePropertiesData(properties)); + } + + [Fact] + public void SnapshotStreamingUsesTheSameBytesAndLeavesTheDestinationOwnedByItsCaller() + { + var value = new SnapshotEnvelope(); + value.state.bytes = new byte[128 * 1024]; + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(value); + using var stream = new MemoryStream(); + snapshot.WriteTo(stream); + Assert.Equal(m_serialization.Serialize(value), stream.ToArray()); + Assert.True(stream.CanWrite); + Assert.Throws(() => snapshot.WriteTo(null!)); + using var readOnly = new MemoryStream([1, 2, 3], writable: false); + Assert.Throws(() => snapshot.WriteTo(readOnly)); + } + + [Fact] + public void ConverterTypeQueriesUseTheirPinnedGenerationAndRejectExpiredWriters() + { + var value = new SnapshotEnvelope(); + using SerializationGeneration captured = m_serialization.CaptureGeneration(); + _ = captured.Serialize(value); + long original = value.observedTypeGeneration; + m_types.Rebuild(); + _ = m_serialization.Serialize(value); + Assert.True(value.observedTypeGeneration > original); + _ = captured.Serialize(value); + Assert.Equal(original, value.observedTypeGeneration); + SerializationWriter? expired = null; + _ = m_serialization.Encode(writer => expired = writer); + Assert.Throws(() => expired!.typeSnapshot); + } + + [Fact] + public void OrdinaryWriterRetainsItsOperationLifetimeAndRejectsDuplicateObjectsBeforeCallbacks() + { + bool called = false; + Assert.Throws(() => m_serialization.Encode(writer => + { + writer.Write("value", 1); + writer.WriteObject("value", _ => called = true); + })); + Assert.False(called); + byte[] bytes = m_serialization.Encode(writer => + { + SerializationWriter? child = null; + writer.WriteObject("nested", value => child = value); + child!.Write("late", 42); + }); + Assert.Equal(42, m_serialization.Decode(bytes, reader => reader.ReadObject("nested").Read("late"))); + } + + [Fact] + public void SnapshotCapturePreservesOrdinaryBytesAndEveryDirectValueChange() + { + var value = new DefaultSample { count = 7, ratio = -0f, bytes = [1, 2], values = [3, 4] }; + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(value); + byte[] original = snapshot.ToArray(); + Assert.Equal(m_serialization.Serialize(value), original); + Assert.Same(snapshot, m_serialization.CaptureSnapshot(value, previous: snapshot)); + for (int index = 0; index < 12; index++) + { + value.count++; + value.ratio = BitConverter.Int32BitsToSingle(index % 2 == 0 ? unchecked((int)0x80000000) : 0x7fc00001 + index); + value.bytes[index % 2]++; + value.values.Add(index); + SerializationSnapshot next = m_serialization.CaptureSnapshot(value, previous: snapshot); + Assert.NotSame(snapshot, next); + Assert.Equal(m_serialization.Serialize(value), next.ToArray()); + Assert.Same(next, m_serialization.CaptureSnapshot(value, previous: next)); + snapshot = next; + } + Assert.NotEqual(original, snapshot.ToArray()); + } + + [Theory] + [InlineData(3)] + [InlineData(70)] + [InlineData(8192)] + public void SnapshotObjectCaptureHandlesReplacementRemovalAndEmptyObjects(int count) + { + var value = new SnapshotEnvelope { omitState = true }; + for (int index = 0; index < count; index++) + value.fields.Add("item-" + index, index); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(value); + value.fields.Remove("item-0"); + value.fields.Add("replacement", 91); + SerializationSnapshot replaced = m_serialization.CaptureSnapshot(value, previous: snapshot); + Assert.Equal(m_serialization.Serialize(value), replaced.ToArray()); + Assert.NotSame(snapshot, replaced); + Assert.Same(replaced, m_serialization.CaptureSnapshot(value, previous: replaced)); + value.fields.Clear(); + SerializationSnapshot empty = m_serialization.CaptureSnapshot(value, previous: replaced); + Assert.Equal(m_serialization.Serialize(value), empty.ToArray()); + Assert.Same(empty, m_serialization.CaptureSnapshot(value, previous: empty)); + } + + [Fact] + public void SnapshotStateBlobsUseTheExistingPropertyWireFormatAndDetectArrayShrink() + { + var value = new SnapshotEnvelope(); + value.items.AddRange([1, 2, 3]); + value.state.count = 42; + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + byte[] bytes = before.ToArray(); + Assert.Equal(m_serialization.CapturePropertiesData(value.state), + m_serialization.Decode(bytes, reader => reader.Read("state"))); + value.items.RemoveAt(1); + value.state.name = "changed"; + SerializationSnapshot after = m_serialization.CaptureSnapshot(value, previous: before); + Assert.Equal(m_serialization.Serialize(value), after.ToArray()); + Assert.Equal(bytes, before.ToArray()); + Assert.Same(after, m_serialization.CaptureSnapshot(value, previous: after)); + } + + [Fact] + public void SnapshotFailurePreservesTheOriginalAndStillExecutesConverters() + { + var value = new SnapshotEnvelope(); + SerializationSnapshot before = m_serialization.CaptureSnapshot(value); + byte[] bytes = before.ToArray(); + int calls = value.writeCalls; + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(calls + 1, value.writeCalls); + value.fail = true; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(bytes, before.ToArray()); + value.fail = false; + Assert.Same(before, m_serialization.CaptureSnapshot(value, previous: before)); + value.duplicate = true; + Assert.Throws(() => m_serialization.CaptureSnapshot(value, previous: before)); + Assert.Equal(bytes, before.ToArray()); + } + + [Fact] + public void SnapshotReadersShareOnlyImmutableDataAcrossIndependentCapturesAndGenerations() + { + var source = new DefaultSample { count = 7, bytes = [1, 2, 3] }; + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(source); + byte[] original = snapshot.ToArray(); + m_types.Rebuild(); + Parallel.For(0, 32, index => + { + var next = new DefaultSample { count = index, bytes = [1, 2, 3] }; + SerializationSnapshot captured = m_serialization.CaptureSnapshot(next, previous: snapshot); + Assert.Equal(m_serialization.Serialize(next), captured.ToArray()); + byte[] mutable = captured.ToArray(); + mutable[0]++; + Assert.Equal(original, snapshot.ToArray()); + }); + Assert.Same(snapshot, m_serialization.CaptureSnapshot(source, previous: snapshot)); + m_serialization.Dispose(); + Assert.Equal(original, snapshot.ToArray()); + Assert.Throws(() => m_serialization.CaptureSnapshot(source, previous: snapshot)); + } + + [Fact] + public void SnapshotDoesNotOwnTheSourceInstance() + { + (SerializationSnapshot snapshot, WeakReference reference) = CaptureWeakSource(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(reference.IsAlive); + Assert.NotEmpty(snapshot.ToArray()); + } + + [Fact] + public void UnchangedLargeBinaryCaptureDoesNotCopyItsPayload() + { + var source = new SnapshotEnvelope(); + source.state.bytes = new byte[1024 * 1024]; + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(source); + for (int index = 0; index < 8; index++) + Assert.Same(snapshot, m_serialization.CaptureSnapshot(source, previous: snapshot)); + long started = GC.GetAllocatedBytesForCurrentThread(); + for (int index = 0; index < 16; index++) + Assert.Same(snapshot, m_serialization.CaptureSnapshot(source, previous: snapshot)); + long allocated = (GC.GetAllocatedBytesForCurrentThread() - started) / 16; + Assert.True(allocated < 16_384, $"An unchanged 1 MiB payload allocated {allocated} bytes per capture."); + source.state.bytes[500] = 12; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(source, previous: snapshot); + Assert.NotSame(snapshot, changed); + Assert.Equal(m_serialization.Serialize(source), changed.ToArray()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private (SerializationSnapshot, WeakReference) CaptureWeakSource() + { + var value = new DefaultSample { name = "Weak owner" }; + return (m_serialization.CaptureSnapshot(value), new WeakReference(value)); + } +} + +internal sealed class MutatingBinaryCapture : ISerializable +{ + internal byte[] bytes = [1]; +} + +internal sealed class MutatingBinaryCaptureConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + MutatingBinaryCapture value + ) { + writer.Write("bytes", value.bytes); + value.bytes[0] = 2; + } + + public override MutatingBinaryCapture Read(SerializationReader reader) => new() { bytes = reader.Read("bytes") }; +} + +internal sealed class MutatingPropertyCapture : ISerializable +{ + private readonly byte[] m_bytes = [1]; + + [SerializableProperty(PropertyVisibility.Serialize)] public byte[] bytes => m_bytes; + [SerializableProperty(PropertyVisibility.Serialize)] public int mutate => m_bytes[0] = 2; + + internal void Reset() => m_bytes[0] = 1; +} + +internal sealed class SnapshotEnvelope : ISerializable +{ + internal Dictionary fields { get; } = []; + internal List items { get; } = []; + internal DefaultSample state { get; } = new(); + internal bool omitState; + internal bool fail; + internal bool duplicate; + internal int writeCalls; + internal long observedTypeGeneration; +} + +internal sealed class SnapshotEnvelopeConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + SnapshotEnvelope value + ) { + value.writeCalls++; + value.observedTypeGeneration = writer.typeSnapshot.version; + foreach (var pair in value.fields) + writer.Write(pair.Key, pair.Value); + if (value.duplicate) + { + writer.Write("duplicate", 1); + writer.Write("duplicate", 2); + } + if (!value.omitState) + { + writer.WritePropertiesData("state", value.state); + writer.WriteObjectArray("items", value.items, static ( + child, + number + ) => child.Write("value", number)); + } + if (value.fail) + throw new InvalidOperationException("Capture rejected by its source converter."); + } + + public override SnapshotEnvelope Read(SerializationReader reader) => throw new NotSupportedException(); +} diff --git a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.cs b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.cs index b775400f5..c1727e205 100644 --- a/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.cs +++ b/tests/core/Inno.Core.Serialization.Tests/SerializationBehaviorTests.cs @@ -14,7 +14,7 @@ namespace Inno.Core.Serialization.Tests; -public sealed class SerializationBehaviorTests : IDisposable +public sealed partial class SerializationBehaviorTests : IDisposable { private readonly string m_testRoot = Path.Combine( Path.GetTempPath(), diff --git a/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPointerTests.cs b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPointerTests.cs new file mode 100644 index 000000000..2cdeaca75 --- /dev/null +++ b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPointerTests.cs @@ -0,0 +1,121 @@ +using System; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.Graph.ImGui; +using Xunit; + +namespace Inno.Editor.Graph.Tests; + +public sealed class GraphCanvasPointerTests +{ + [Fact] + public void ShiftBoxSelectionKeepsTheExistingSelectionAndAddsIntersectingNodes() + { + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + GraphNodeId original = new("workflow.original"); + GraphNodeId added = new("workflow.added"); + _ = layout.SetNodes([ + new(original, new(500f, 300f), new(100f, 80f), 24f, "Original", []), + new(added, new(50f, 50f), new(100f, 80f), 24f, "Added", []) + ]); + state.SelectNodes([original]); + _ = pointer.Step(view, layout, new() { position = new(10f, 10f), isHovered = true, isPressed = true, isDown = true, shift = true }); + _ = pointer.Step(view, layout, new() { position = new(200f, 200f), isHovered = true, isReleased = true, shift = true }); + Assert.Equal(2, state.selectedNodes.Count); + Assert.Contains(original, state.selectedNodes); + Assert.Contains(added, state.selectedNodes); + Assert.False(pointer.isBoxSelecting); + } + + [Fact] + public void ConnectionReleaseBehindAnotherInputOwnerDoesNotConnect() + { + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + var output = new GraphEndpoint(new("source"), new("out")); + var input = new GraphEndpoint(new("target"), new("in")); + _ = layout.SetNodes([ + new(new("source"), new(0f, 0f), new(100f, 100f), 24f, "Source", [new(output, GraphPortDirection.Output, new(100f, 50f), "Output")]), + new(new("target"), new(200f, 0f), new(100f, 100f), 24f, "Target", [new(input, GraphPortDirection.Input, new(0f, 50f), "Input")]) + ]); + _ = pointer.Step(view, layout, new() { position = new(100f, 50f), isHovered = true, isPressed = true, isDown = true }); + GraphCanvasPointerResult result = pointer.Step(view, layout, new() { position = new(200f, 50f), isReleased = true, isHovered = false }); + Assert.Null(result.output); + Assert.Null(result.input); + Assert.Null(state.pendingConnection); + } + + [Fact] + public void DragReturnsOneNeutralEditAndCancelLeavesTheDocumentUntouched() + { + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + var node = new GraphCanvasNode(new("flow.step"), new(10f, 10f), new(200f, 100f), 24f, "Workflow", []); + _ = layout.SetNodes([node]); + Assert.Equal(node.id, pointer.Step(view, layout, new() { position = new(20f, 15f), isHovered = true, isPressed = true, isDown = true }).nodePressed); + _ = pointer.Step(view, layout, new() { position = new(70f, 45f), isDown = true }); + Assert.Equal(new GraphPosition(60f, 40f), pointer.previewPositions[node.id]); + GraphCanvasPointerResult completed = pointer.Step(view, layout, new() { position = new(70f, 45f), isReleased = true }); + Assert.Equal(new GraphPosition(60f, 40f), completed.movedNodes![node.id]); + Assert.False(pointer.isMoving); + Assert.Empty(pointer.previewPositions); + Assert.Equal(new GraphPosition(10f, 10f), node.position); + _ = pointer.Step(view, layout, new() { position = new(20f, 15f), isHovered = true, isPressed = true, isDown = true }); + _ = pointer.Step(view, layout, new() { position = new(90f, 90f), isDown = true }); + Assert.Null(pointer.Step(view, layout, new() { cancel = true }).movedNodes); + Assert.Empty(pointer.previewPositions); + Assert.Equal(new GraphPosition(60f, 40f), completed.movedNodes[node.id]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ConnectionsAreNeutralIntentsAndMissingEndpointsCannotBeConnected(bool missing) + { + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + GraphEndpoint output = new(new("flow.source"), new("next")); + GraphEndpoint input = new(new("flow.target"), new("previous")); + _ = layout.SetNodes([ + new(output.nodeId, default, new(200f, 100f), 24f, "Source", [new(output, GraphPortDirection.Output, new(200f, 40f), "Next")]), + new(input.nodeId, new(300f, 0f), new(200f, 100f), 24f, "Target", [new(input, GraphPortDirection.Input, new(0f, 40f), "Previous", isMissing: missing)]) + ]); + _ = pointer.Step(view, layout, new() { position = new(200f, 40f), isHovered = true, isPressed = true, isDown = true }); + Assert.Equal(output, state.pendingConnection); + GraphCanvasPointerResult result = pointer.Step(view, layout, new() { position = new(300f, 40f), isHovered = true, isReleased = true }); + Assert.Equal(missing ? null : output, result.output); + Assert.Equal(missing ? null : input, result.input); + Assert.Null(state.pendingConnection); + } + + [Fact] + public void ReadOnlyChangesCancelEditsButStillAllowBoxSelection() + { + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + GraphNodeId id = new("flow.read-only"); + _ = layout.SetNodes([new(id, new(20f, 20f), new(200f, 100f), 24f, "Read only", [])]); + _ = pointer.Step(view, layout, new() { position = new(30f, 25f), isHovered = true, isPressed = true, isDown = true }); + _ = pointer.Step(view, layout, new() { position = new(100f, 100f), isDown = true }); + Assert.Null(pointer.Step(view, layout, new() { position = new(100f, 100f), isReleased = true, readOnly = true }).movedNodes); + Assert.False(pointer.isMoving); + _ = pointer.Step(view, layout, new() { position = new(5f, 150f), isHovered = true, isPressed = true, isDown = true, readOnly = true }); + Assert.True(pointer.isBoxSelecting); + _ = pointer.Step(view, layout, new() { position = new(240f, 10f), isReleased = true, readOnly = true }); + Assert.Equal(id, Assert.Single(state.selectedNodes)); + Assert.False(pointer.isBoxSelecting); + Assert.Throws(() => pointer.Step(view, layout, new() { position = new(float.NaN, 0f) })); + } +} diff --git a/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPresentationTests.cs b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPresentationTests.cs new file mode 100644 index 000000000..e9c5dda4e --- /dev/null +++ b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasPresentationTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.Graph.ImGui; +using Xunit; + +namespace Inno.Editor.Graph.Tests; + +public sealed class GraphCanvasPresentationTests +{ + [Fact] + public void NonShaderNodesUseTheSameOrderedHitAndBoxGeometry() + { + var state = new GraphCanvasState(); + state.SetViewport(new(17f, -9f), 1.5f); + var view = new GraphCanvasView(state, new(100f, 200f), new(800f, 600f)); + GraphCanvasNode first = Node("workflow.start", new(0f, 0f)); + GraphCanvasNode second = Node("workflow.finish", new(50f, 0f)); + var layout = new GraphCanvasLayout(); + Assert.Equal(2, layout.SetNodes([first, second])); + Assert.Same(second, view.HitNode(layout, view.ToScreen(new(60f, 10f)))); + Assert.Equal(new[] { first.id, second.id }, view.BoxNodes(layout, view.ToScreen(new(-5f, -5f)), view.ToScreen(new(300f, 100f)))); + Assert.Equal(0, layout.SetNodes([second, first])); + Assert.Same(first, view.HitNode(layout, view.ToScreen(new(60f, 10f)))); + } + + [Fact] + public void PortHitGeometryPreservesMissingAndExplicitLabelBounds() + { + var state = new GraphCanvasState(); + var view = new GraphCanvasView(state, Vector2.Zero, new(800f, 600f)); + GraphNodeId id = new("workflow.output"); + var port = new GraphCanvasPort(new(id, new("next")), GraphPortDirection.Output, new(200f, 40f), "Continue", + isMissing: true, labelBounds: (new(100f, 28f), new(200f, 52f))); + var node = new GraphCanvasNode(id, new(10f, 10f), new(200f, 100f), 24f, "Flow step", [port]); + var layout = new GraphCanvasLayout(); + _ = layout.SetNodes([node]); + Assert.Equal(port, view.HitPort(layout, new(210f, 50f))); + Assert.Equal(port, view.HitPort(layout, new(150f, 50f))); + Assert.Null(view.HitPort(layout, new(150f, 90f))); + Assert.Throws(() => view.HitPort(layout, Vector2.Zero, -1f)); + } + + [Fact] + public void ChangingOneOfTwoThousandNodesPreservesOtherGeometry() + { + GraphCanvasNode[] nodes = Enumerable.Range(0, 2000) + .Select(index => Node("workflow." + index, new(index * 220f, 0f))).ToArray(); + var layout = new GraphCanvasLayout(); + Assert.Equal(2000, layout.SetNodes(nodes)); + GraphCanvasNode retained = nodes[999]; + nodes[1000] = Node(nodes[1000].id.value, new(100f, 200f)); + Assert.Equal(1, layout.SetNodes(nodes)); + Assert.True(layout.TryGetNode(retained.id, out GraphCanvasNode? current)); + Assert.Same(retained, current); + Assert.Equal(0, layout.SetNodes(nodes)); + } + + [Fact] + public void InvalidCandidatesDoNotReplaceTheCurrentSnapshot() + { + var layout = new GraphCanvasLayout(); + GraphCanvasNode node = Node("workflow.valid", default); + _ = layout.SetNodes([node]); + Assert.Throws(() => layout.SetNodes([node, node])); + Assert.Same(node, Assert.Single(layout.nodes)); + Assert.Throws(() => new GraphCanvasNode(node.id, default, new(float.NaN, 10f), 2f, "Invalid", [])); + Assert.Throws(() => new GraphCanvasNode(node.id, default, new(200f, 100f), 24f, "Invalid", + [new GraphCanvasPort(new(new("foreign"), new("port")), GraphPortDirection.Input, Vector2.Zero, "Foreign")])); + } + + private static GraphCanvasNode Node( + string id, + GraphPosition position + ) => new(new(id), position, new(200f, 100f), 24f, id, []); +} diff --git a/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasViewTests.cs b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasViewTests.cs new file mode 100644 index 000000000..2b2112261 --- /dev/null +++ b/tests/editor/Inno.Editor.Graph.Tests/GraphCanvasViewTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.Graph.ImGui; +using Xunit; + +namespace Inno.Editor.Graph.Tests; +public sealed class GraphCanvasViewTests +{ + [Fact] + public void FrameProjectionDoesNotChangeWhenLaterNavigationMutatesItsSource() + { + var state = new GraphCanvasState(); + var view = new GraphCanvasView(state, new(100f, 200f), new(800f, 600f)); + state.SetViewport(new(50f, 75f), 2f); + Assert.Equal(new Vector2(110f, 220f), view.ToScreen(new(10f, 20f))); + Assert.Equal(1f, view.zoom); + var next = new GraphCanvasView(state, new(100f, 200f), new(800f, 600f)); + Assert.Equal(new Vector2(170f, 315f), next.ToScreen(new(10f, 20f))); + Assert.Equal(2f, next.zoom); + } + + [Theory] + [InlineData(0.1f)] + [InlineData(1f)] + [InlineData(4f)] + public void ViewportRoundTripPreservesTheGraphPoint(float zoom) + { + var state = new GraphCanvasState(); + state.SetViewport(new(-172f, 38f), zoom); + var view = new GraphCanvasView(state, new(100f, 200f), new(800f, 600f)); + GraphPosition point = new(-21.25f, 90f); + GraphPosition actual = view.ToGraph(view.ToScreen(point)); + Assert.Equal(point.x, actual.x, 3); + Assert.Equal(point.y, actual.y, 3); + } + + [Fact] + public void ConnectionHitTestUsesTheDisplayedRouteIncludingEndpoints() + { + Assert.True(GraphCanvasView.HitConnection(new(0f, 0f), Vector2.Zero, new(200f, 100f))); + Assert.True(GraphCanvasView.HitConnection(new(100f, 50f), Vector2.Zero, new(200f, 100f))); + Assert.False(GraphCanvasView.HitConnection(new(100f, 120f), Vector2.Zero, new(200f, 100f))); + Assert.True(GraphCanvasView.HitConnection(new(200f, 100f), Vector2.Zero, new(200f, 100f))); + } + + [Fact] + public void InvalidViewportAndHitToleranceAreRejected() + { + Assert.Throws(() => new GraphCanvasView(new GraphCanvasState(), Vector2.Zero, new(float.NaN, 100f))); + Assert.Throws(() => GraphCanvasView.HitConnection(Vector2.Zero, Vector2.Zero, Vector2.One, -1f)); + } +} diff --git a/tests/editor/Inno.Editor.Graph.Tests/GraphDocumentControllerTests.cs b/tests/editor/Inno.Editor.Graph.Tests/GraphDocumentControllerTests.cs index 702c65340..b6c9d4bfb 100644 --- a/tests/editor/Inno.Editor.Graph.Tests/GraphDocumentControllerTests.cs +++ b/tests/editor/Inno.Editor.Graph.Tests/GraphDocumentControllerTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using Inno.Extensibility.Modules; using Inno.Extensibility.Reload; using Inno.Core.Graphs; @@ -62,6 +63,7 @@ public void Dispose() [Theory] [InlineData(false)] [InlineData(true)] + [Trait("Acceptance", "Lifecycle")] public void AssemblyPublicationPreservesNeutralGraphAndRollsBackLaterParticipantFailure(bool reject) { GraphEditorModule module = Assert.IsType(m_sink.module); @@ -73,16 +75,22 @@ public void AssemblyPublicationPreservesNeutralGraphAndRollsBackLaterParticipant var rejection = new RejectionParticipant(reject); using IDisposable registration = m_reloads.Register(rejection); string directory = Path.Combine(AppContext.BaseDirectory, "Modules"); - using (AssemblyReloadSession reload = m_modules.BeginReload([new DotNetModuleSource + for (int iteration = 0; iteration < 10; iteration++) { - artifactRootDirectory = Path.Combine(m_testRoot, "Assemblies"), - moduleName = "GraphRecovery", domain = AssemblyDomain.InnoPlugin, scope = AssemblyScope.Runtime, - mainAssemblyPath = Path.Combine(directory, "Inno.Extensibility.Modules.TestModule.dll"), - preloadAssemblyPaths = [Path.Combine(directory, "Reloadable.PrivateDependency.dll")] - }])) - { - if (reject) Assert.Throws(() => m_reloads.Execute(reload)); - else _ = m_reloads.Execute(reload); + using (AssemblyReloadSession reload = m_modules.BeginReload([new DotNetModuleSource + { + artifactRootDirectory = Path.Combine(m_testRoot, "Assemblies"), + moduleName = "GraphRecovery", domain = AssemblyDomain.InnoPlugin, scope = AssemblyScope.Runtime, + mainAssemblyPath = Path.Combine(directory, "Inno.Extensibility.Modules.TestModule.dll"), + preloadAssemblyPaths = [Path.Combine(directory, "Reloadable.PrivateDependency.dll")] + }])) + { + if (reject) Assert.Throws(() => m_reloads.Execute(reload)); + else _ = m_reloads.Execute(reload); + } + m_modules.generations.Wait(); + Assert.Equal(GenerationState.Ready, m_modules.generations.state); + Assert.Equal(before, GraphDocumentCodec.Encode(controller.document, m_serialization)); } m_modules.generations.Wait(); Assert.Equal(before, GraphDocumentCodec.Encode(controller.document, m_serialization)); @@ -95,6 +103,37 @@ public void AssemblyPublicationPreservesNeutralGraphAndRollsBackLaterParticipant Assert.Single(controller.document.nodes); } + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void OneHundredDocumentCyclesReleasePayloadsAndRejectRetiredControllers() + { + GraphEditorModule module = Assert.IsType(m_sink.module); + Guid identity = Guid.NewGuid(); + var retired = new List<(GraphDocumentController controller, WeakReference document)>(); + for (int iteration = 0; iteration < 100; iteration++) + retired.Add(OpenAndClose()); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + foreach (var entry in retired) + { + Assert.False(entry.controller.isAvailable); + Assert.False(entry.document.TryGetTarget(out _)); + Assert.Throws(() => entry.controller.AddNode("retired", default)); + } + Assert.False(module.TryOpenDocument(identity, m_runtime.interactions.history, out _)); + + [MethodImpl(MethodImplOptions.NoInlining)] + (GraphDocumentController, WeakReference) OpenAndClose() + { + var document = new GraphDocument(); + GraphDocumentController controller = module.OpenDocument(identity, document, m_runtime.interactions.history); + controller.AddNode("missing.example", new(7f, 9f)); + Assert.True(module.CloseDocument(identity)); + return (controller, new WeakReference(document)); + } + } + private sealed class RejectionParticipant(bool reject) : IEditorReloadParticipant, IGenerationChange { public IGenerationChange Capture(AssemblyReloadContext context) => this; diff --git a/tests/editor/Inno.Editor.Graph.Tests/Inno.Editor.Graph.Tests.csproj b/tests/editor/Inno.Editor.Graph.Tests/Inno.Editor.Graph.Tests.csproj index ec0f714d7..cff03d55f 100644 --- a/tests/editor/Inno.Editor.Graph.Tests/Inno.Editor.Graph.Tests.csproj +++ b/tests/editor/Inno.Editor.Graph.Tests/Inno.Editor.Graph.Tests.csproj @@ -10,6 +10,7 @@ + @@ -27,7 +28,6 @@ - diff --git a/tests/editor/Inno.Editor.ImGui.Tests/EditorUiSessionTests.cs b/tests/editor/Inno.Editor.ImGui.Tests/EditorUiSessionTests.cs new file mode 100644 index 000000000..1e30dca5e --- /dev/null +++ b/tests/editor/Inno.Editor.ImGui.Tests/EditorUiSessionTests.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using Inno.Editor.ImGui; +using Xunit; + +namespace Inno.Editor.ImGui.Tests; +public sealed class EditorUiSessionTests +{ + [Fact] + public void StyleAndControlsBelongToOneSession() + { + using var first = new EditorUiSession(); + using var second = new EditorUiSession(); + first.widgets.style.SetZoom(1.5f); + second.widgets.style.SetCompactMode(true); + Assert.NotSame(first.widgets, second.widgets); + Assert.Equal(1.5f, first.widgets.style.zoom); + Assert.Equal(1f, second.widgets.style.zoom); + Assert.False(first.widgets.style.isCompact); + Assert.True(second.widgets.style.isCompact); + } + + [Fact] + public void RejectedScopeRetirementPreservesTheActiveSession() + { + using var first = new EditorUiSession(); + using var second = new EditorUiSession(); + IDisposable outer = first.Enter(); + IDisposable inner = second.Enter(); + Assert.Throws(outer.Dispose); + Assert.Same(second, EditorUiSession.current); + Assert.Throws(first.Dispose); + inner.Dispose(); + Assert.Same(first, EditorUiSession.current); + outer.Dispose(); + Assert.Throws(() => EditorUiSession.current); + } + + [Fact] + public void RetiredSessionsRejectActivationAndControlUse() + { + var session = new EditorUiSession(); + session.Dispose(); + Assert.True(session.isDisposed); + Assert.Throws(() => session.Enter()); + Assert.Throws(() => session.widgets.CenteredButton("Retired")); + } + + [Fact] + public void ASessionCannotBeActivatedFromAnotherThread() + { + using var session = new EditorUiSession(); + Exception? failure = null; + var thread = new Thread(() => failure = Record.Exception(() => session.Enter())); + thread.Start(); + Assert.True(thread.Join(TimeSpan.FromSeconds(5))); + Assert.IsType(failure); + } +} diff --git a/tests/editor/Inno.Editor.ImGui.Tests/FontCoverageTests.cs b/tests/editor/Inno.Editor.ImGui.Tests/FontCoverageTests.cs new file mode 100644 index 000000000..fe0de65ea --- /dev/null +++ b/tests/editor/Inno.Editor.ImGui.Tests/FontCoverageTests.cs @@ -0,0 +1,228 @@ +using System; +using System.Globalization; +using System.IO; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Inno.Adapter.Platform.Sdl3; +using Inno.Adapter.Presentation.ImGui; +using Inno.Native.ImGui; +using Inno.Platform; +using Inno.Testing.Processes; +using Xunit; +using NativeImGui = Inno.Native.ImGui.ImGui; +using BackendFont = Inno.Adapter.Presentation.ImGui.ImGuiFont; +using BackendFontScope = Inno.Adapter.Presentation.ImGui.ImGuiFontScope; +using BackendFontStyle = Inno.Adapter.Presentation.ImGui.ImGuiFontStyle; + +namespace Inno.Editor.ImGui.Tests; + +public sealed unsafe class FontCoverageTests +{ + [Theory] + [InlineData(0.75f)] + [InlineData(1f)] + [InlineData(1.5f)] + [InlineData(2f)] + [Trait("Acceptance", "Visual")] + public void EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource(float zoom) + { + string contract = nameof(FontCoverageTests) + "." + nameof(EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource) + "." + zoom.ToString(CultureInfo.InvariantCulture); + if (NativeWindowTestHost.RunIfRequired(contract)) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Glyph coverage", width = 640, height = 480, visible = false }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + nint sharedData = 0; + context.RenderFrame(() => + { + NativeImGui.Raw.GetStyle()->FontScaleMain = zoom; + bool open = true; + _ = NativeImGui.Begin("Glyph coverage", ref open, 0); + try + { + foreach (BackendFontStyle style in new[] { BackendFontStyle.Regular, BackendFontStyle.Bold, BackendFontStyle.Italic, BackendFontStyle.Bold | BackendFontStyle.Italic }) + { + Assert.True(BackendFont.IsAvailable(style)); + using BackendFontScope scope = BackendFont.PushStyle(style); + ImFont* font = NativeImGui.Raw.GetFont(); + ImFontBaked* baked = NativeImGui.Raw.GetFontBaked(); + foreach (char value in "Aé资源漢字") + { + ImFontGlyph* glyph = NativeImGui.Raw.FindGlyphNoFallback(baked, value); + Assert.True(glyph is not null, $"Face {style} has no U+{(int)value:X4} glyph."); + Assert.Equal((uint)value, glyph->Codepoint); + Assert.Equal(value is 'A' or 'é' ? 0u : 3u, glyph->SourceIdx); + } + ImFontGlyph* icon = NativeImGui.Raw.FindGlyphNoFallback(baked, 0xf0e2); + Assert.True(icon is not null); + Assert.InRange(icon->SourceIdx, 1u, 2u); + Assert.Equal(4, font->Sources.Size); + ImFontConfig* supplemental = font->Sources.Data[3]; + Assert.Equal(16_437_364, supplemental->FontDataSize); + Assert.Equal(0, supplemental->FontDataOwnedByAtlas); + if (sharedData == 0) + sharedData = (nint)supplemental->FontData; + Assert.Equal(sharedData, (nint)supplemental->FontData); + NativeImGui.TextUnformatted("Aé资源漢字 / undo"); + } + } + finally + { + NativeImGui.End(); + } + }); + Assert.NotEqual((nint)0, sharedData); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void SuccessfulDisposalPermitsReplacementOnTheSameLiveWindow() + { + if (NativeWindowTestHost.RunIfRequired(nameof(FontCoverageTests) + "." + nameof(SuccessfulDisposalPermitsReplacementOnTheSameLiveWindow))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Replacement context", width = 320, height = 240, visible = false }); + var original = application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + _ = CaptureSource(original); + original.Dispose(); + using var replacement = application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + _ = CaptureSource(replacement); + Assert.Throws(() => original.RenderFrame(static () => { })); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void ApplicationShutdownRetiresBothRegisteredContexts() + { + if (NativeWindowTestHost.RunIfRequired(nameof(FontCoverageTests) + "." + nameof(ApplicationShutdownRetiresBothRegisteredContexts))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var firstWindow = application.CreateWindow(new PlatformWindowOptions { title = "First context", width = 320, height = 240, visible = false }); + using var secondWindow = application.CreateWindow(new PlatformWindowOptions { title = "Second context", width = 320, height = 240, visible = false }); + var first = application.CreateImGuiContext(firstWindow, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + var second = application.CreateImGuiContext(secondWindow, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + _ = CaptureSource(first); + _ = CaptureSource(second); + application.Dispose(); + Assert.Throws(() => first.RenderFrame(static () => { })); + Assert.Throws(() => second.RenderFrame(static () => { })); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void RetiringTenContextsReleasesTheirSourcesAndPreservesAnotherContext() + { + if (NativeWindowTestHost.RunIfRequired(nameof(FontCoverageTests) + "." + nameof(RetiringTenContextsReleasesTheirSourcesAndPreservesAnotherContext))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Retained font context", width = 320, height = 240, visible = false }); + using var retained = application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + nint source = CaptureSource(retained); + long baseline = GC.GetTotalMemory(forceFullCollection: true); + var retired = new List>(); + for (int cycle = 0; cycle < 10; cycle++) + { + retired.Add(CreateAndRetire(application, source)); + Assert.Equal(source, CaptureSource(retained)); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.All(retired, reference => Assert.False(reference.TryGetTarget(out _))); + Assert.InRange(GC.GetTotalMemory(forceFullCollection: true) - baseline, long.MinValue, 2L * 1024 * 1024); + Assert.Single(application.GetWindows()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RequiredBundledContentIsRejectedBeforeNativeUse(bool corrupt) + { + string owner = Path.Combine(Path.GetTempPath(), "InnoFontContent", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(owner); + try + { + foreach (string file in Directory.EnumerateFiles(AppContext.BaseDirectory, "*", SearchOption.AllDirectories)) + { + string target = Path.Combine(owner, Path.GetRelativePath(AppContext.BaseDirectory, file)); + Directory.CreateDirectory(Path.GetDirectoryName(target)!); + File.Copy(file, target); + } + string font = Path.Combine(owner, "BuiltIn", "Fonts", "Supplemental", "NotoSansCJKsc-Regular.otf"); + Assert.True(File.Exists(font)); + if (corrupt) + { + using FileStream file = File.OpenWrite(font); + file.WriteByte(0); + } + else + File.Delete(font); + NativeProcessTestHost.Run(Path.Combine(owner, Path.GetFileName(typeof(FontCoverageTests).Assembly.Location)), + nameof(FontCoverageTests) + ".RejectOwnedContent." + corrupt); + } + finally + { + Directory.Delete(owner, recursive: true); + } + } + + internal static void RejectOwnedContent(bool corrupt) + { + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Font content failure", width = 320, height = 240, visible = false }); + void Create() => application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()).Dispose(); + if (corrupt) + Assert.Throws(Create); + else + Assert.Throws(Create); + Assert.Single(application.GetWindows()); + Assert.False(BackendFont.IsAvailable(BackendFontStyle.Regular)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference CreateAndRetire( + Sdl3PlatformApplication application, + nint retainedSource + ) { + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Retired font context", width = 320, height = 240, visible = false }); + var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, new ImGuiInteractionOptions(false, 1), new NoOutputRenderer()); + Assert.NotEqual(retainedSource, CaptureSource(context)); + context.Dispose(); + return new(context); + } + + private static nint CaptureSource(PlatformImGuiContext context) + { + nint source = 0; + context.RenderFrame(() => + { + using BackendFontScope scope = BackendFont.PushStyle(BackendFontStyle.Regular); + Assert.True(NativeImGui.Raw.FindGlyphNoFallback(NativeImGui.Raw.GetFontBaked(), 0x6f22) is not null); + source = (nint)NativeImGui.Raw.GetFont()->Sources.Data[3]->FontData; + }); + Assert.NotEqual((nint)0, source); + return source; + } + + private sealed class NoOutputRenderer : IPlatformImGuiRenderer + { + public bool supportsViewports => false; + public bool supportsVertexOffsets => false; + public void RenderMain(IntPtr drawData) { } + public void SynchronizeMainOutput( + int pixelWidth, + int pixelHeight + ) { } + public void CreateViewport(PlatformImGuiViewportTarget target) => throw new NotSupportedException(); + public void ResizeViewport(PlatformImGuiViewportTarget target) => throw new NotSupportedException(); + public void RenderViewport( + PlatformImGuiViewportTarget target, + IntPtr drawData + ) => throw new NotSupportedException(); + public void PresentViewport(PlatformImGuiViewportTarget target) => throw new NotSupportedException(); + public Task RetireViewport(PlatformImGuiViewportTarget target) => Task.CompletedTask; + public void DrainViewportRetirements() { } + public void Dispose() { } + } +} diff --git a/tests/editor/Inno.Editor.ImGui.Tests/Inno.Editor.ImGui.Tests.csproj b/tests/editor/Inno.Editor.ImGui.Tests/Inno.Editor.ImGui.Tests.csproj index 99a0bbd0b..f184a2464 100644 --- a/tests/editor/Inno.Editor.ImGui.Tests/Inno.Editor.ImGui.Tests.csproj +++ b/tests/editor/Inno.Editor.ImGui.Tests/Inno.Editor.ImGui.Tests.csproj @@ -12,10 +12,11 @@ Inno.Editor.ImGui.Tests.NativeWindowTestHost Editor editor - windows-x64 + $(InnoNativeTarget) + @@ -35,5 +36,4 @@ - diff --git a/tests/editor/Inno.Editor.ImGui.Tests/NativeWindowTestHost.cs b/tests/editor/Inno.Editor.ImGui.Tests/NativeWindowTestHost.cs index a002d3dc5..c7ff9486b 100644 --- a/tests/editor/Inno.Editor.ImGui.Tests/NativeWindowTestHost.cs +++ b/tests/editor/Inno.Editor.ImGui.Tests/NativeWindowTestHost.cs @@ -8,6 +8,11 @@ internal static class NativeWindowTestHost { private static readonly IReadOnlyDictionary S_CONTRACTS = new Dictionary { + ["ViewportMetricsTests.FramePreparationRunsBeforeNativeCreationAndRejectsReentrantOperations"] = () => new ViewportMetricsTests().FramePreparationRunsBeforeNativeCreationAndRejectsReentrantOperations(), + ["ViewportMetricsTests.FailedPreparationDoesNotBeginOrPublishANativeFrame"] = () => new ViewportMetricsTests().FailedPreparationDoesNotBeginOrPublishANativeFrame(), + ["ViewportMetricsTests.AFailedDrawRetiresItsNativeFrameBeforeTheNextRequest"] = () => new ViewportMetricsTests().AFailedDrawRetiresItsNativeFrameBeforeTheNextRequest(), + ["ViewportMetricsTests.ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability.False"] = () => new ViewportMetricsTests().ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability(false), + ["ViewportMetricsTests.ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability.True"] = () => new ViewportMetricsTests().ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability(true), ["ViewportMetricsTests.FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback.False"] = () => new ViewportMetricsTests().FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback(false), ["ViewportMetricsTests.FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback.True"] = () => new ViewportMetricsTests().FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback(true), ["ViewportMetricsTests.MetricRefreshPreservesRequestedViewportGeometry"] = () => new ViewportMetricsTests().MetricRefreshPreservesRequestedViewportGeometry(), @@ -18,6 +23,15 @@ internal static class NativeWindowTestHost ["ViewportRetirementTests.DetachedWindowRemainsAliveUntilRenderingAcknowledgesRetirement"] = () => new ViewportRetirementTests().DetachedWindowRemainsAliveUntilRenderingAcknowledgesRetirement(), ["ViewportRetirementTests.IncompleteDrainRetainsContextOwnershipAndCanFinishAtTheNextSafetyPoint"] = () => new ViewportRetirementTests().IncompleteDrainRetainsContextOwnershipAndCanFinishAtTheNextSafetyPoint(), ["ViewportRetirementTests.CreationFailureIsReportedAtAManagedSafetyPointAfterCleanup"] = () => new ViewportRetirementTests().CreationFailureIsReportedAtAManagedSafetyPointAfterCleanup(), + ["FontCoverageTests.EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource.0.75"] = () => new FontCoverageTests().EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource(0.75f), + ["FontCoverageTests.EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource.1"] = () => new FontCoverageTests().EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource(1f), + ["FontCoverageTests.EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource.1.5"] = () => new FontCoverageTests().EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource(1.5f), + ["FontCoverageTests.EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource.2"] = () => new FontCoverageTests().EveryTextFaceHasRealCjkGlyphsAndSharesOneOwnedSource(2f), + ["FontCoverageTests.RejectOwnedContent.False"] = () => FontCoverageTests.RejectOwnedContent(false), + ["FontCoverageTests.RejectOwnedContent.True"] = () => FontCoverageTests.RejectOwnedContent(true), + ["FontCoverageTests.RetiringTenContextsReleasesTheirSourcesAndPreservesAnotherContext"] = () => new FontCoverageTests().RetiringTenContextsReleasesTheirSourcesAndPreservesAnotherContext(), + ["FontCoverageTests.SuccessfulDisposalPermitsReplacementOnTheSameLiveWindow"] = () => new FontCoverageTests().SuccessfulDisposalPermitsReplacementOnTheSameLiveWindow(), + ["FontCoverageTests.ApplicationShutdownRetiresBothRegisteredContexts"] = () => new FontCoverageTests().ApplicationShutdownRetiresBothRegisteredContexts(), }; private static bool m_executingContract; diff --git a/tests/editor/Inno.Editor.ImGui.Tests/ViewportMetricsTests.cs b/tests/editor/Inno.Editor.ImGui.Tests/ViewportMetricsTests.cs index 4c8c0bb4a..4fd4fa5d5 100644 --- a/tests/editor/Inno.Editor.ImGui.Tests/ViewportMetricsTests.cs +++ b/tests/editor/Inno.Editor.ImGui.Tests/ViewportMetricsTests.cs @@ -1,3 +1,4 @@ +using ImGuiWindowFlags = Inno.Editor.ImGui.ImGuiWindowFlags; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; @@ -15,11 +16,114 @@ using NativeImGui = Inno.Native.ImGui.ImGui; [assembly: CollectionBehavior(DisableTestParallelization = true)] - namespace Inno.Editor.ImGui.Tests; - public sealed unsafe class ViewportMetricsTests { + [Fact] + public void FramePreparationRunsBeforeNativeCreationAndRejectsReentrantOperations() + { + if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(FramePreparationRunsBeforeNativeCreationAndRejectsReentrantOperations))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions + { + title = "Prepared presentation frame", width = 320, height = 240, visible = false + }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, + new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); + using var ui = new EditorUiSession(); + context.SetIniFile(null); + int phase = 0; + context.RenderFrame(() => + { + Assert.Equal(1, phase); + Assert.Equal(1, NativeImGui.Raw.GetCurrentContext()->WithinFrameScope); + Assert.Equal(ui.widgets.style.fontScale, NativeImGui.Raw.GetStyle()->FontScaleMain); + phase = 2; + }, () => + { + using IDisposable scope = ui.Enter(); + Assert.Equal(0, NativeImGui.Raw.GetCurrentContext()->WithinFrameScope); + Assert.Equal(new Vector2(320f, 240f), Unsafe.BitCast(NativeImGui.Raw.GetIO()->DisplaySize)); + Assert.True(ui.widgets.style.SetZoom(1.25f)); + ui.widgets.SetupStyle(); + Assert.Throws(() => context.RenderFrame(static () => { })); + Assert.Throws(() => context.Dispose()); + phase = 1; + }); + Assert.Equal(2, phase); + Assert.Equal(0, NativeImGui.Raw.GetCurrentContext()->WithinFrameScope); + } + + [Fact] + public void FailedPreparationDoesNotBeginOrPublishANativeFrame() + { + if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(FailedPreparationDoesNotBeginOrPublishANativeFrame))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions + { + title = "Failed frame preparation", width = 320, height = 240, visible = false + }); + var renderer = new FixtureRenderer(); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, + new ImGuiInteractionOptions(false, 1), renderer); + context.SetIniFile(null); + bool drawn = false; + var failure = new InvalidOperationException("Frame preparation failed."); + Assert.Same(failure, Assert.Throws(() => context.RenderFrame(() => drawn = true, () => throw failure))); + Assert.False(drawn); + Assert.Equal(0, NativeImGui.Raw.GetCurrentContext()->WithinFrameScope); + context.RenderFrame(() => drawn = true); + Assert.True(drawn); + } + + [Fact] + public void AFailedDrawRetiresItsNativeFrameBeforeTheNextRequest() + { + if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(AFailedDrawRetiresItsNativeFrameBeforeTheNextRequest))) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions + { + title = "Failed presentation frame", width = 320, height = 240, visible = false + }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, + new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); + context.SetIniFile(null); + var failure = new InvalidOperationException("The frame extension failed."); + try + { + Assert.Same(failure, Assert.Throws(() => context.RenderFrame(() => throw failure))); + Assert.Equal(0, NativeImGui.Raw.GetCurrentContext()->WithinFrameScope); + context.RenderFrame(static () => { }); + } + finally + { + if (NativeImGui.Raw.GetCurrentContext()->WithinFrameScope != 0) + NativeImGui.Raw.EndFrame(); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability(bool supported) + { + if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(ContextAdvertisesOnlyTheSelectedRenderersVertexOffsetCapability) + "." + supported)) + return; + using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); + using var window = application.CreateWindow(new PlatformWindowOptions + { + title = "Renderer capability", width = 320, height = 240, visible = false + }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.None, + new ImGuiInteractionOptions(false, 1), new FixtureRenderer { supportsVertexOffsets = supported }); + context.SetIniFile(null); + context.RenderFrame(() => Assert.Equal(supported, + (NativeImGui.Raw.GetIO()->BackendFlags & (int)Inno.Native.ImGui.ImGuiBackendFlags.RendererHasVtxOffset) != 0)); + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -27,14 +131,12 @@ public void FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback(bool comm { if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback) + "." + commandKeys)) return; - using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); - using var window = application.CreateWindow(new PlatformWindowOptions - { title = "Viewport metrics", width = 320, height = 240, visible = false, highPixelDensity = true }); - using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, - new ImGuiInteractionOptions(commandKeys, 1), new FixtureRenderer()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Viewport metrics", width = 320, height = 240, visible = false, highPixelDensity = true }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, new ImGuiInteractionOptions(commandKeys, 1), new FixtureRenderer()); context.SetIniFile(null); - context.RenderFrame(() => { + context.RenderFrame(() => + { Assert.Equal(commandKeys, (NativeImGui.Raw.GetIO()->ConfigMacOsxBehaviors != 0)); Assert.True(NativeImGui.Raw.GetPlatformIO()->PlatformGetWindowFramebufferScale == default); int logicalWidth = 0; @@ -45,8 +147,10 @@ public void FrameRefreshesDrawableMetricsWithoutAPlatformScaleCallback(bool comm SDL.Raw.GetWindowSizeInPixels((SDLWindow*)window.sdlWindowHandle, &pixelWidth, &pixelHeight); Vector2 expected = new(pixelWidth / (float)logicalWidth, pixelHeight / (float)logicalHeight); Assert.Equal(expected, Unsafe.BitCast(NativeImGui.Raw.GetMainViewport()->FramebufferScale)); - Assert.Equal(SDL.Raw.GetWindowDisplayScale((SDLWindow*)window.sdlWindowHandle) - / SDL.Raw.GetWindowPixelDensity((SDLWindow*)window.sdlWindowHandle), NativeImGui.Raw.GetMainViewport()->DpiScale); + Assert.Equal( + SDL.Raw.GetWindowDisplayScale((SDLWindow*)window.sdlWindowHandle) / SDL.Raw.GetWindowPixelDensity((SDLWindow*)window.sdlWindowHandle), + NativeImGui.Raw.GetMainViewport()->DpiScale + ); Assert.Equal(new Vector2(logicalWidth, logicalHeight), Unsafe.BitCast(NativeImGui.Raw.GetIO()->DisplaySize)); }); SDL.Raw.SetWindowSize((SDLWindow*)window.sdlWindowHandle, 450, 300); @@ -58,14 +162,16 @@ public void MetricRefreshPreservesRequestedViewportGeometry() { if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(MetricRefreshPreservesRequestedViewportGeometry))) return; - using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); - using var window = application.CreateWindow(new PlatformWindowOptions - { title = "Viewport geometry owner", width = 320, height = 240, visible = false }); - using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, - new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Viewport geometry owner", width = 320, height = 240, visible = false }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); context.SetIniFile(null); - var viewport = new ImGuiViewport { ID = 4218, Size = Unsafe.BitCast(new Vector2(100, 90)), Pos = Unsafe.BitCast(new Vector2(30, 30)) }; + var viewport = new ImGuiViewport + { + ID = 4218, + Size = Unsafe.BitCast(new Vector2(100, 90)), + Pos = Unsafe.BitCast(new Vector2(30, 30)) + }; var io = NativeImGui.Raw.GetPlatformIO(); io->PlatformCreateWindow(&viewport); try @@ -75,7 +181,9 @@ public void MetricRefreshPreservesRequestedViewportGeometry() Vector2 requestedSize = new(140, 110); viewport.Pos = viewport.WorkPos = Unsafe.BitCast(requestedPosition); viewport.Size = viewport.WorkSize = Unsafe.BitCast(requestedSize); - context.RenderFrame(() => { }); + context.RenderFrame(() => + { + }); Assert.Equal(requestedPosition, Unsafe.BitCast(viewport.Pos)); Assert.Equal(requestedPosition, Unsafe.BitCast(viewport.WorkPos)); Assert.Equal(requestedSize, Unsafe.BitCast(viewport.Size)); @@ -95,12 +203,9 @@ public void DetachedWindowAppliesRequestsAfterPlatformEventReconciliation() { if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(DetachedWindowAppliesRequestsAfterPlatformEventReconciliation))) return; - using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); - using var window = application.CreateWindow(new PlatformWindowOptions - { title = "Viewport movement", width = 320, height = 240, visible = false }); - using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, - new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Viewport movement", width = 320, height = 240, visible = false }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); context.SetIniFile(null); var io = NativeImGui.Raw.GetIO(); io->ConfigViewportsNoAutoMerge = (true ? (byte)1 : (byte)0); @@ -108,7 +213,8 @@ public void DetachedWindowAppliesRequestsAfterPlatformEventReconciliation() { Vector2 requested = new(500 + frame * 7, 200 + frame * 3); uint viewportId = 0; - context.RenderFrame(() => { + context.RenderFrame(() => + { NativeImGui.SetNextWindowPos(requested); NativeImGui.SetNextWindowSize(new Vector2(200, 120)); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated("Detached movement", allowsNull: false); @@ -122,11 +228,14 @@ public void DetachedWindowAppliesRequestsAfterPlatformEventReconciliation() int y = 0; Assert.True(SDL.Raw.GetWindowPosition(SDL.Raw.GetWindowFromId(windowId), &x, &y) != 0); Assert.Equal(requested, new Vector2(x, y)); - // A real OS move acknowledgement owns reconciliation in the following frame. // Complete that frame before submitting the next application position request. - while (application.PollEvent(out _)) { } - context.RenderFrame(() => { + while (application.PollEvent(out _)) + { + } + + context.RenderFrame(() => + { using NativeStringLease nativeTestText1 = Utf8Codec.EncodeNullTerminated("Detached movement", allowsNull: false); NativeImGui.Raw.Begin((sbyte*)nativeTestText1.pointer, null, (int)(ImGuiWindowFlags.NoFocusOnAppearing)); Assert.Equal(requested, NativeImGui.GetWindowPos()); @@ -141,12 +250,9 @@ public void LogicalDpiTransitionRetainsThePreviousScaleUntilImGuiStartsItsFrame( { if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(LogicalDpiTransitionRetainsThePreviousScaleUntilImGuiStartsItsFrame))) return; - using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); - using var window = application.CreateWindow(new PlatformWindowOptions - { title = "Logical DPI transition", width = 320, height = 240, visible = false }); - using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, - new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Logical DPI transition", width = 320, height = 240, visible = false }); + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, new ImGuiInteractionOptions(false, 1), new FixtureRenderer()); context.SetIniFile(null); float previous = 0f; context.RenderFrame(() => previous = NativeImGui.Raw.GetMainViewport()->DpiScale); @@ -163,14 +269,16 @@ public void ViewportCallbacksRegisterOneSurfaceAndReleaseItBeforeNativeDestructi { if (NativeWindowTestHost.RunIfRequired(nameof(ViewportMetricsTests) + "." + nameof(ViewportCallbacksRegisterOneSurfaceAndReleaseItBeforeNativeDestruction))) return; - using var application = new Sdl3PlatformApplication(NativeHostFixture.Create()); - using var window = application.CreateWindow(new PlatformWindowOptions - { title = "Viewport ownership", width = 320, height = 240, visible = false }); + using var window = application.CreateWindow(new PlatformWindowOptions { title = "Viewport ownership", width = 320, height = 240, visible = false }); var renderer = new FixtureRenderer(); - using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, - new ImGuiInteractionOptions(false, 1), renderer); - var viewport = new ImGuiViewport { ID = 4217, Size = Unsafe.BitCast(new Vector2(100, 90)), Pos = Unsafe.BitCast(new Vector2(30, 30)) }; + using var context = application.CreateImGuiContext(window, ImGuiContextFlags.EnableViewports, new ImGuiInteractionOptions(false, 1), renderer); + var viewport = new ImGuiViewport + { + ID = 4217, + Size = Unsafe.BitCast(new Vector2(100, 90)), + Pos = Unsafe.BitCast(new Vector2(30, 30)) + }; var io = NativeImGui.Raw.GetPlatformIO(); io->PlatformCreateWindow(&viewport); try @@ -186,6 +294,7 @@ public void ViewportCallbacksRegisterOneSurfaceAndReleaseItBeforeNativeDestructi { io->PlatformDestroyWindow(&viewport); } + Assert.Single(application.GetWindows()); Assert.Null(renderer.target); Assert.True(viewport.PlatformUserData == null); @@ -195,24 +304,45 @@ private sealed class FixtureRenderer : IPlatformImGuiRenderer { internal PlatformImGuiViewportTarget? target; public bool supportsViewports => true; - public void RenderMain(IntPtr drawData) { } + public bool supportsVertexOffsets { get; init; } + + public void RenderMain(IntPtr drawData) + { + } + public void SynchronizeMainOutput( int pixelWidth, int pixelHeight - ) { } + ) { + } + public void CreateViewport(PlatformImGuiViewportTarget target) => this.target = target; - public void ResizeViewport(PlatformImGuiViewportTarget target) { } + public void ResizeViewport(PlatformImGuiViewportTarget target) + { + } + public void RenderViewport( PlatformImGuiViewportTarget target, IntPtr drawData - ) { } - public void PresentViewport(PlatformImGuiViewportTarget target) { } + ) { + } + + public void PresentViewport(PlatformImGuiViewportTarget target) + { + } + public Task RetireViewport(PlatformImGuiViewportTarget target) { this.target = null; return Task.CompletedTask; } - public void DrainViewportRetirements() { } - public void Dispose() { } + + public void DrainViewportRetirements() + { + } + + public void Dispose() + { + } } } diff --git a/tests/editor/Inno.Editor.ImGui.Tests/ViewportRetirementTests.cs b/tests/editor/Inno.Editor.ImGui.Tests/ViewportRetirementTests.cs index c58f4e631..b7fdb6efd 100644 --- a/tests/editor/Inno.Editor.ImGui.Tests/ViewportRetirementTests.cs +++ b/tests/editor/Inno.Editor.ImGui.Tests/ViewportRetirementTests.cs @@ -160,6 +160,7 @@ private sealed class PendingRenderer : IPlatformImGuiRenderer internal bool failCreation; public bool supportsViewports => true; + public bool supportsVertexOffsets => false; public void RenderMain(IntPtr drawData) { } public void SynchronizeMainOutput( int pixelWidth, diff --git a/tests/editor/Inno.Editor.ImGui.Tests/WidgetLayoutTests.cs b/tests/editor/Inno.Editor.ImGui.Tests/WidgetLayoutTests.cs index c595e2eb8..4a087a20f 100644 --- a/tests/editor/Inno.Editor.ImGui.Tests/WidgetLayoutTests.cs +++ b/tests/editor/Inno.Editor.ImGui.Tests/WidgetLayoutTests.cs @@ -1,9 +1,12 @@ +using ImGuiWindowFlags = Inno.Editor.ImGui.ImGuiWindowFlags; +using ImGuiMouseButton = Inno.Editor.ImGui.ImGuiMouseButton; +using ImGuiCond = Inno.Editor.ImGui.ImGuiCond; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; using System; using System.Numerics; - using Inno.Native.ImGui; using Xunit; using NativeImGui = Inno.Native.ImGui.ImGui; @@ -11,9 +14,500 @@ using EditorImGui = Inno.Editor.ImGui.ImGui; namespace Inno.Editor.ImGui.Tests; - public sealed unsafe class WidgetLayoutTests { + [Fact] + public void PackedColorConversionMatchesNativeSaturationRoundingAndChannelOrder() + { + using (NativeContext context = new(1f)) + { + context.Frame(() => + { + BeginWindow("Color format contract", new(400f, 300f)); + try + { + float[] boundaries = [float.NegativeInfinity, -1f, -0f, 0f, 0.5f, 1f, 2f, float.PositiveInfinity, float.NaN]; + foreach (float value in boundaries) + { + Verify(new(value, 0f, 0f, 0f)); + Verify(new(0f, value, 0f, 0f)); + Verify(new(0f, 0f, value, 0f)); + Verify(new(0f, 0f, 0f, value)); + } + for (int channel = 0; channel < 255; channel++) + { + float half = (channel + 0.5f) / 255f; + Verify(new(MathF.BitDecrement(half), half, MathF.BitIncrement(half), 1f)); + } + var random = new Random(0x2DCA); + for (int index = 0; index < 4096; index++) + Verify(new(random.NextSingle() * 3f - 1f, random.NextSingle() * 3f - 1f, + random.NextSingle() * 3f - 1f, random.NextSingle() * 3f - 1f)); + } + finally + { + NativeImGui.End(); + } + }); + } + + Assert.Throws(() => EditorImGui.ToPackedColor(Vector4.One)); + static void Verify(Vector4 value) => Assert.Equal(NativeImGui.ColorConvertFloat4ToU32(value), EditorImGui.ToPackedColor(value)); + } + + [Fact] + public void StandaloneMetricsApplyImmediatelyAndRetainedMetricsDoNotRootTheirRetiredSession() + { + var standalone = new EditorStyleMetrics(); + Assert.True(standalone.SetZoom(1.25f)); + Assert.True(standalone.SetCompactMode(true)); + Assert.Equal(1.25f, standalone.zoom); + Assert.True(standalone.isCompact); + EditorStyleMetrics retained = RetireStyleOwner(out WeakReference owner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(owner.IsAlive); + Assert.Throws(() => retained.SetZoom(1.1f)); + Assert.Throws(() => retained.SetCompactMode(true)); + } + + [Fact] + public void StyleRequestsDoNotChangeTheActiveNativeFrame() + { + using NativeContext context = new(1f); + var widgets = EditorUI.current.widgets; + for (int frame = 0; frame < 2; frame++) + { + context.Frame(() => + { + widgets.SetupStyle(); + BeginWindow("Stable style input", new(400f, 300f)); + try + { + if (frame == 0) + { + float padding = widgets.style.cardBodyPadding.X; + Assert.True(widgets.style.SetZoom(1.25f)); + Assert.True(widgets.style.SetCompactMode(true)); + Assert.Equal(1f, widgets.style.zoom); + Assert.False(widgets.style.isCompact); + widgets.SetupStyle(); + Assert.Equal(1f, widgets.style.zoom); + Assert.Equal(padding, widgets.style.cardBodyPadding.X); + } + else + { + Assert.Equal(1.25f, widgets.style.zoom); + Assert.True(widgets.style.isCompact); + } + widgets.PropertyLabel("Stable frame / 同帧尺度"); + } + finally + { + NativeImGui.End(); + } + }); + } + } + + [Fact] + public void AForeignNativeContextCannotPublishChangesIntoASuspendedOwnerFrame() + { + using NativeContext context = new(1f); + var widgets = EditorUI.current.widgets; + context.Frame(() => + { + widgets.SetupStyle(); + BeginWindow("Suspended style owner", new(400f, 300f)); + try + { + ImGuiNativeContext* own = NativeImGui.Raw.GetCurrentContext(); + ImGuiNativeContext* foreign = NativeImGui.Raw.CreateContext(null); + try + { + NativeImGui.Raw.SetCurrentContext(foreign); + Assert.True(widgets.style.SetZoom(1.25f)); + Assert.Equal(1f, widgets.style.zoom); + } + finally + { + NativeImGui.Raw.SetCurrentContext(own); + NativeImGui.Raw.DestroyContext(foreign); + NativeImGui.Raw.SetCurrentContext(own); + } + widgets.SetupStyle(); + Assert.Equal(1f, widgets.style.zoom); + } + finally + { + NativeImGui.End(); + } + }); + context.Frame(() => + { + widgets.SetupStyle(); + Assert.Equal(1.25f, widgets.style.zoom); + }); + } + + [Fact] + public void CopiedWindowGeometryRequiresAnActiveNativeFrame() + { + using NativeContext context = new(1f); + Assert.Throws(() => EditorImGui.GetClipBounds()); + Assert.Throws(() => EditorImGui.GetWindowBounds()); + context.Frame(() => + { + BeginWindow("Geometry owner", new(400f, 300f)); + try + { + var clip = EditorImGui.GetClipBounds(); + Assert.True(clip.maximum.X > clip.minimum.X && clip.maximum.Y > clip.minimum.Y); + Assert.True(EditorImGui.GetWindowBounds().maximumX > EditorImGui.GetWindowBounds().minimumX); + } + finally + { + NativeImGui.End(); + } + }); + Assert.Throws(() => EditorImGui.GetClipBounds()); + Assert.Throws(() => EditorImGui.GetWindowBounds()); + } + + [Fact] + public void FlattenedRowsRetainExpansionWithoutOwningNativeTreeScopes() + { + using NativeContext context = new(1f); + for (int frame = 0; frame < 3; frame++) + { + context.Frame(() => + { + BeginWindow("Flattened tree", new(400f, 300f)); + try + { + var widgets = EditorUI.current.widgets; + widgets.SetTreeNodeOpen("root", true); + ulong revision = widgets.treeExpansionRevision; + widgets.SetTreeNodeOpen("root", true); + Assert.Equal(revision, widgets.treeExpansionRevision); + int nativeDepth = NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth; + var root = widgets.TreeNode("root", _ => NativeImGui.TextUnformatted("Root"), new() { depth = 0 }); + Assert.True(root.isOpen); + Assert.True(widgets.IsTreeNodeOpen("root")); + Assert.Equal(nativeDepth, NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth); + var child = widgets.TreeNode("child", _ => NativeImGui.TextUnformatted("Deep child"), new() { depth = 7, isLeaf = true }); + Assert.True(child.contentMin.X > root.contentMin.X); + var failure = new InvalidOperationException("Rejected flat content"); + Assert.Same(failure, Assert.Throws(() => widgets.TreeNode("failed", _ => throw failure, new() { depth = 2 }))); + Assert.Equal(nativeDepth, NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth); + widgets.SetTreeNodeOpen("root", false); + Assert.True(widgets.treeExpansionRevision > revision); + Assert.False(widgets.IsTreeNodeOpen("root")); + } + finally + { + NativeImGui.End(); + } + }); + } + } + + [Fact] + public void TenThousandTreeRowsSubmitCustomContentOnlyWithinTheViewport() + { + using NativeContext context = new(1f); + for (int frame = 0; frame < 3; frame++) + { + context.Frame(() => + { + BeginWindow("Large tree", new(400f, 300f)); + int drawn = 0; + Action draw = _ => + { + drawn++; + NativeImGui.TextUnformatted("Visible content"); + }; + for (int row = 0; row < 10_000; row++) + _ = EditorUI.current.widgets.TreeNode("row-" + row, draw, new() { isLeaf = true }); + Assert.InRange(drawn, 1, 64); + if (frame == 1) + NativeImGui.SetScrollY(100_000f); + NativeImGui.End(); + }); + } + } + + [Fact] + public void FixedRowsRetainExplicitRowsAndBalanceTheirNativeOwner() + { + using NativeContext context = new(1f); + context.Frame(() => + { + BeginWindow("Clipped rows", new(400f, 300f)); + int submitted = 0; + bool preserved = false; + float height = NativeImGui.GetTextLineHeightWithSpacing(); + EditorUI.current.widgets.ClippedRows(10_000, height, 0, ( + state, + start, + end + ) => + { + submitted += end - start; + preserved |= start <= 9999 && end > 9999; + for (int row = start; row < end; row++) + NativeImGui.Dummy(new(100f, NativeImGui.GetTextLineHeight())); + }, [9999]); + Assert.InRange(submitted, 1, 64); + Assert.True(preserved); + NativeImGui.End(); + }); + } + + [Fact] + public void FixedRowFailureReleasesTheClipperBeforeTheNextFrame() + { + using NativeContext context = new(1f); + for (int frame = 0; frame < 2; frame++) + { + context.Frame(() => + { + BeginWindow("Clipper failure", new(400f, 300f)); + int before = NativeImGui.Raw.GetCurrentContext()->ClipperTempDataStacked; + var failure = new InvalidOperationException("Row extension rejected drawing."); + Assert.Same(failure, Assert.Throws(() => EditorUI.current.widgets.ClippedRows( + 10_000, NativeImGui.GetTextLineHeightWithSpacing(), 0, ( + state, + start, + end + ) => throw failure))); + Assert.Equal(before, NativeImGui.Raw.GetCurrentContext()->ClipperTempDataStacked); + NativeImGui.End(); + }); + } + } + + [Theory] + [InlineData(0.75f)] + [InlineData(1f)] + [InlineData(1.5f)] + public void TrendUsesOneBoundedRegionAndFiniteGeometry(float zoom) + { + using NativeContext context = new(zoom); + context.Frame(() => + { + BeginWindow("Trend bounds", new Vector2(400f, 400f)); + var widgets = EditorUI.current.widgets; + widgets.Trend("Finite extremes", new[] { float.MinValue, 0f, float.MaxValue }, offset: 1); + Assert.Equal(widgets.style.trendHeight, NativeImGui.GetItemRectSize().Y, 3); + Assert.True(NativeImGui.GetItemRectSize().X <= 400f); + var draw = NativeImGui.Raw.GetWindowDrawList(); + for (int index = 0; index < draw->VtxBuffer.Size; index++) + { + Vector2 point = Unsafe.BitCast(draw->VtxBuffer.Data[index].Pos); + Assert.True(float.IsFinite(point.X) && float.IsFinite(point.Y)); + } + widgets.Trend("Empty", ReadOnlySpan.Empty); + Assert.Throws(() => widgets.Trend("Offset", new[] { 1f }, offset: 1)); + Assert.Throws(() => widgets.Trend("Invalid", new[] { float.NaN })); + NativeImGui.End(); + }); + } + + [Fact] + public void ListNavigationUsesVisibleIndicesAndRespectsTextAndPopupOwnership() + { + string root = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "InnoListNavigation", Guid.NewGuid().ToString("N")); + System.IO.Directory.CreateDirectory(root); + try + { + var diagnostics = new Inno.Core.Diagnostics.DiagnosticHub(); + using IDisposable diagnosticScope = diagnostics.EnterScope(); + using var modules = new Inno.Extensibility.Modules.ModuleHost(new() + { + catalogSource = new Inno.Adapter.Modules.DotNet.DotNetAssemblyCatalogSource(typeof(WidgetLayoutTests).Assembly) + }); + using var types = new Inno.Extensibility.Types.TypeCatalog(modules, new Inno.Adapter.Modules.DotNet.ReflectionTypeCatalogSource()); + using var logs = new Inno.Core.Logging.LogRouter(); + using var runtime = new Inno.Editor.Interactions.EditorInteractionRuntime( + root, + types, + logs, + new Inno.Editor.Core.EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); + runtime.Start(); + using NativeContext context = new(1f); + NativeImGui.Raw.GetIO()->ConfigFlags |= (int)Inno.Native.ImGui.ImGuiConfigFlags.NavEnableKeyboard; + int focused = 4; + bool textInput = false; + bool popup = false; + string query = string.Empty; + Inno.Editor.ImGui.ImGuiWidget.ListNavigationResult observed = default; + Action draw = () => + { + BeginWindow("List navigation", new Vector2(500f, 400f)); + if (textInput) + { + EditorImGui.FocusNextItem(); + _ = EditorUI.current.widgets.SearchInput("keyboard-query", "Search", ref query, 1024); + } + if (popup) + { + NativeImGui.OpenPopup("Input owner"); + if (NativeImGui.BeginPopup("Input owner")) + { + NativeImGui.TextUnformatted("Exclusive popup input"); + NativeImGui.EndPopup(); + } + } + observed = EditorUI.current.widgets.NavigateList(runtime.interactions, 100, focused, pageSize: 10); + focused = observed.focusedIndex; + NativeImGui.End(); + }; + context.Frame(draw); + context.Frame(draw); + var io = NativeImGui.Raw.GetIO(); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.PageDown, 1); + context.Frame(draw); + Assert.Equal(14, focused); + Assert.True(observed.selectionRequested); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.PageDown, 0); + context.Frame(draw); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.End, 1); + context.Frame(draw); + Assert.Equal(99, focused); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.End, 0); + context.Frame(draw); + textInput = true; + context.Frame(draw); + context.Frame(draw); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.Home, 1); + context.Frame(draw); + Assert.Equal(99, focused); + Assert.False(observed.selectionRequested); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.Home, 0); + context.Frame(draw); + textInput = false; + popup = true; + context.Frame(draw); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.UpArrow, 1); + context.Frame(draw); + Assert.Equal(99, focused); + Assert.False(observed.selectionRequested); + NativeImGui.Raw.AddKeyEvent(io, Inno.Native.ImGui.ImGuiKey.UpArrow, 0); + context.Frame(draw); + } + finally + { + System.IO.Directory.Delete(root, recursive: true); + } + } + + [Theory] + [InlineData("card-leading")] + [InlineData("card-trailing")] + [InlineData("card-menu")] + [InlineData("tree-content")] + [InlineData("tree-overlay")] + public void ExtensionDrawingFailureRestoresTheEnclosingWindow(string failure) + { + using NativeContext context = new(1f); + for (int frame = 0; frame < 2; frame++) + { + context.Frame(() => + { + BeginWindow("Failure owner", new Vector2(400f, 300f)); + nint window = (nint)NativeImGui.Raw.GetCurrentWindow(); + int depth = NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth; + Vector2 padding = Unsafe.BitCast(NativeImGui.Raw.GetStyle()->FramePadding); + var widgets = EditorUI.current.widgets; + Action rejected = () => throw new InvalidOperationException("Rejected extension draw"); + Assert.Throws(() => + { + if (failure.StartsWith("card-", StringComparison.Ordinal)) + { + _ = widgets.CollapsingCard( + "failed-card", + "Extension", + drawLeadingControl: failure == "card-leading" ? rejected : null, + drawTrailingControl: failure == "card-trailing" ? rejected : null, + dimmed: true, + drawContextMenu: failure == "card-menu" ? rejected : null); + } + else + { + widgets.SetNextTreeNodeOpen(true); + _ = widgets.TreeNode("failed-tree", _ => + { + if (failure == "tree-content") + rejected(); + EditorImGui.Text("Extension"); + }, new() { drawViewportOverlay = failure == "tree-overlay" ? rejected : null }); + } + }); + Assert.Equal(window, (nint)NativeImGui.Raw.GetCurrentWindow()); + Assert.Equal(depth, NativeImGui.Raw.GetCurrentWindow()->DC.TreeDepth); + Assert.Equal(padding, Unsafe.BitCast(NativeImGui.Raw.GetStyle()->FramePadding)); + widgets.SetNextTreeNodeOpen(true); + var next = widgets.TreeNode("next-tree", _ => EditorImGui.Text("Next valid row"), new()); + Assert.True(next.isOpen); + NativeImGui.TreePop(); + EditorImGui.GetDrawList().Line(new(30f, 30f), new(60f, 60f), uint.MaxValue); + NativeImGui.End(); + }); + } + } + + [Fact] + public void DrawingBorrowRejectsAnotherWindowOrFrame() + { + using NativeContext context = new(1f); + Inno.Editor.ImGui.EditorDrawList borrow = default; + context.Frame(() => + { + BeginWindow("Drawing owner", new Vector2(400f, 300f)); + borrow = EditorImGui.GetDrawList(); + borrow.Line(new(30f, 30f), new(60f, 60f), uint.MaxValue); + _ = EditorImGui.BeginChild("Other owner", new Vector2(100f, 100f)); + Assert.Throws(() => borrow.Line(Vector2.Zero, Vector2.One, 0)); + EditorImGui.EndChild(); + NativeImGui.End(); + }); + context.Frame(() => + { + BeginWindow("Drawing owner", new Vector2(400f, 300f)); + Assert.Throws(() => borrow.Line(Vector2.Zero, Vector2.One, 0)); + NativeImGui.End(); + }); + Assert.Throws(() => default(Inno.Editor.ImGui.EditorDrawList).Line(Vector2.Zero, Vector2.One, 0)); + } + + [Fact] + public void BoundedTextControlsDoNotAllocateOneBufferAndEncoderPerSubmission() + { + using NativeContext context = new(1f); + string value = "Unicode: 漢字 😀"; + long allocated = 0; + Action draw = () => + { + BeginWindow("Text allocation", new Vector2(400f, 300f)); + long before = GC.GetAllocatedBytesForCurrentThread(); + for (int index = 0; index < 128; index++) + { + EditorImGui.PushId(index); + _ = EditorImGui.InputText("Value", ref value, 1024); + EditorImGui.PopId(); + } + + allocated = GC.GetAllocatedBytesForCurrentThread() - before; + NativeImGui.End(); + }; + context.Frame(draw); + context.Frame(draw); + Assert.Equal("Unicode: 漢字 😀", value); + Assert.True(allocated < 128L * 512, $"Unexpected steady-state allocation: {allocated} bytes for 128 fields."); + } + [Fact] public void TimelineWorkspace_UsesOneBoundedChildScrollOwner() { @@ -30,25 +524,28 @@ public void TimelineWorkspace_UsesOneBoundedChildScrollOwner() { if (visible) { - Widget.SectionLayout(() => + EditorUI.current.widgets.SectionLayout(() => { - if (Widget.SectionHeader("Frames")) + if (EditorUI.current.widgets.SectionHeader("Frames")) { for (int index = 0; index < 40; index++) EditorImGui.Text("Frame " + index); } }); } + childScroll = NativeImGui.GetScrollMaxY(); } finally { EditorImGui.EndChild(); } + parentScroll = NativeImGui.GetScrollMaxY(); NativeImGui.End(); }); } + Assert.True(childScroll > 200f); Assert.Equal(0f, parentScroll); } @@ -109,20 +606,17 @@ public void PropertyRows_WrapWithinTwoToThreeColumnsWithoutScrollbars(float zoom { BeginWindow("Property layout", new Vector2(320f, 270f)); fontHeight = NativeImGui.GetTextLineHeight(); - Widget.PropertyRow( - "persistence", - () => - { - labelWidth = NativeImGui.GetContentRegionAvail().X; - Widget.PropertyLabel("Persistent data directory for this exported application"); - labelSize = NativeImGui.GetItemRectSize(); - }, - () => - { - valueWidth = NativeImGui.GetContentRegionAvail().X; - string value = "Application data"; - Widget.SearchInput("directory", "Directory", ref value); - }); + EditorUI.current.widgets.PropertyRow("persistence", () => + { + labelWidth = NativeImGui.GetContentRegionAvail().X; + EditorUI.current.widgets.PropertyLabel("Persistent data directory for this exported application"); + labelSize = NativeImGui.GetItemRectSize(); + }, () => + { + valueWidth = NativeImGui.GetContentRegionAvail().X; + string value = "Application data"; + EditorUI.current.widgets.SearchInput("directory", "Directory", ref value); + }); scroll = new Vector2(NativeImGui.GetScrollMaxX(), NativeImGui.GetScrollMaxY()); NativeImGui.End(); }); @@ -159,7 +653,7 @@ float zoom { BeginWindow("Selector layout", new Vector2(400f, 360f)); NativeImGui.SetNextItemWidth(260f); - bool visible = Widget.BeginBoundedCombo("tags", "Untagged"); + bool visible = EditorUI.current.widgets.BeginBoundedCombo("tags", "Untagged"); if (visible) { opened = true; @@ -168,21 +662,20 @@ float zoom popupPosition = NativeImGui.GetWindowPos(); popupSize = NativeImGui.GetWindowSize(); scroll = new Vector2(NativeImGui.GetScrollMaxX(), NativeImGui.GetScrollMaxY()); - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } else { controlMinimum = NativeImGui.GetItemRectMin(); controlMaximum = NativeImGui.GetItemRectMax(); } + NativeImGui.End(); }; - context.Frame(draw); context.Click((controlMinimum + controlMaximum) * 0.5f, 0, draw); for (int frame = 0; frame < 6; frame++) context.Frame(draw); - Assert.True(opened); Assert.InRange(popupPosition.Y, controlMaximum.Y - 1f, controlMaximum.Y + 1f); Assert.True(popupPosition.X >= 20f); @@ -209,28 +702,27 @@ public void SelectorsNearWindowBottom_UseOnlyRemainingDownwardSpace(float vertic BeginWindow("Bottom selector", new Vector2(400f, 360f)); NativeImGui.SetCursorPosY(verticalOffset); NativeImGui.SetNextItemWidth(260f); - if (Widget.BeginBoundedCombo("objects", "Selected object")) + if (EditorUI.current.widgets.BeginBoundedCombo("objects", "Selected object")) { opened = true; for (int index = 0; index < 100; index++) NativeImGui.Selectable($"Object {index}"); popupPosition = NativeImGui.GetWindowPos(); popupSize = NativeImGui.GetWindowSize(); - Widget.EndBoundedCombo(); + EditorUI.current.widgets.EndBoundedCombo(); } else { controlMinimum = NativeImGui.GetItemRectMin(); controlMaximum = NativeImGui.GetItemRectMax(); } + NativeImGui.End(); }; - context.Frame(draw); context.Click((controlMinimum + controlMaximum) * 0.5f, 0, draw); for (int frame = 0; frame < 6; frame++) context.Frame(draw); - Assert.True(opened); Assert.InRange(popupPosition.Y, controlMaximum.Y - 1f, controlMaximum.Y + 1f); Assert.True(popupPosition.Y + popupSize.Y <= 381f); @@ -257,27 +749,26 @@ public void ContextMenu_SearchFillsItsActualContentWidthWithoutScrollbars(float NativeImGui.InvisibleButton("Scene", new Vector2(200f, 30f)); controlMinimum = NativeImGui.GetItemRectMin(); controlMaximum = NativeImGui.GetItemRectMax(); - if (Widget.BeginContextMenu("scene-actions")) + if (EditorUI.current.widgets.BeginContextMenu("scene-actions")) { opened = true; availableWidth = NativeImGui.GetContentRegionAvail().X; - Widget.SearchInput("commands", "Search commands...", ref query); + EditorUI.current.widgets.SearchInput("commands", "Search commands...", ref query); searchWidth = NativeImGui.GetItemRectSize().X; NativeImGui.MenuItem("Set Active Scene", string.Empty, false, true); NativeImGui.MenuItem("Create", string.Empty, false, true); NativeImGui.MenuItem("Rename", "F2", false, true); NativeImGui.MenuItem("Unload", "Delete", false, true); scroll = new Vector2(NativeImGui.GetScrollMaxX(), NativeImGui.GetScrollMaxY()); - Widget.EndContextMenu(); + EditorUI.current.widgets.EndContextMenu(); } + NativeImGui.End(); }; - context.Frame(draw); context.Click((controlMinimum + controlMaximum) * 0.5f, 1, draw); for (int frame = 0; frame < 6; frame++) context.Frame(draw); - Assert.True(opened); Assert.InRange(availableWidth - searchWidth, 0f, 1.1f); Assert.Equal(Vector2.Zero, scroll); @@ -294,21 +785,19 @@ public void OverlappingWindows_OnlyForegroundClickableTextReceivesPress() { BeginWindow("Background panel", new Vector2(400f, 300f)); NativeImGui.SetCursorScreenPos(new Vector2(100f, 100f)); - if (Widget.ClickableText("close", "X", new Vector2(40f, 40f))) + if (EditorUI.current.widgets.ClickableText("close", "X", new Vector2(40f, 40f))) backgroundPresses++; NativeImGui.End(); BeginWindow("Foreground shader", new Vector2(400f, 300f)); NativeImGui.SetCursorScreenPos(new Vector2(100f, 100f)); - if (Widget.ClickableText("action", "Edit", new Vector2(40f, 40f))) + if (EditorUI.current.widgets.ClickableText("action", "Edit", new Vector2(40f, 40f))) foregroundPresses++; hit = (NativeImGui.GetItemRectMin() + NativeImGui.GetItemRectMax()) * 0.5f; NativeImGui.End(); }; - context.Frame(draw); context.Frame(draw); context.Click(hit, 0, draw); - Assert.Equal(0, backgroundPresses); Assert.Equal(1, foregroundPresses); } @@ -328,19 +817,16 @@ float zoom Action draw = () => { BeginWindow("Tree content", new Vector2(400f, 300f)); - var result = Widget.TreeNode( - $"content-{isLeaf}-{zoom}", - _ => Widget.IconText("O", "Selected object", false), - new() - { - isLeaf = isLeaf, - drawViewportOverlay = () => - { - float rowY = NativeImGui.GetItemRectMin().Y; - NativeImGui.SetCursorScreenPos(new Vector2(360f, rowY)); - _ = Widget.ClickableIcon("visibility", "V", "Visibility"); - } - }); + var result = EditorUI.current.widgets.TreeNode($"content-{isLeaf}-{zoom}", _ => EditorUI.current.widgets.IconText( + "O", + "Selected object", + false + ), new() { isLeaf = isLeaf, drawViewportOverlay = () => + { + float rowY = NativeImGui.GetItemRectMin().Y; + NativeImGui.SetCursorScreenPos(new Vector2(360f, rowY)); + _ = EditorUI.current.widgets.ClickableIcon("visibility", "V", "Visibility"); + } }); hit = new Vector2(result.contentMin.X + 80f, (result.min.Y + result.max.Y) * 0.5f); if (result.isClicked) presses++; @@ -348,11 +834,9 @@ float zoom NativeImGui.TreePop(); NativeImGui.End(); }; - context.Frame(draw); context.Frame(draw); context.Click(hit, 0, draw, settleHover: false); - Assert.Equal(1, presses); } @@ -366,13 +850,8 @@ public void TreeRows_DisclosureClickExpandsWithoutSelectingContent() Action draw = () => { BeginWindow("Tree disclosure", new Vector2(400f, 300f)); - var result = Widget.TreeNode( - "disclosure", - _ => NativeImGui.TextUnformatted("Parent"), - new() { isLeaf = false }); - hit = new Vector2( - result.contentMin.X - NativeImGui.GetTreeNodeToLabelSpacing() * 0.5f, - (result.min.Y + result.max.Y) * 0.5f); + var result = EditorUI.current.widgets.TreeNode("disclosure", _ => NativeImGui.TextUnformatted("Parent"), new() { isLeaf = false }); + hit = new Vector2(result.contentMin.X - NativeImGui.GetTreeNodeToLabelSpacing() * 0.5f, (result.min.Y + result.max.Y) * 0.5f); opened = result.isOpen; if (result.isClicked) presses++; @@ -380,12 +859,10 @@ public void TreeRows_DisclosureClickExpandsWithoutSelectingContent() NativeImGui.TreePop(); NativeImGui.End(); }; - context.Frame(draw); context.Frame(draw); context.Click(hit, 0, draw); context.Frame(draw); - Assert.True(opened); Assert.Equal(0, presses); } @@ -402,30 +879,21 @@ public void TreeRows_OverlayConsumesItsOwnClick(bool settleHover) Action draw = () => { BeginWindow("Tree overlay", new Vector2(400f, 300f)); - var result = Widget.TreeNode( - $"overlay-{settleHover}", - _ => NativeImGui.TextUnformatted("Object"), - new() - { - isLeaf = true, - drawViewportOverlay = () => - { - float rowY = NativeImGui.GetItemRectMin().Y; - NativeImGui.SetCursorScreenPos(new Vector2(360f, rowY)); - if (NativeImGui.SmallButton("Eye")) - overlayPresses++; - overlayHit = (NativeImGui.GetItemRectMin() + NativeImGui.GetItemRectMax()) * 0.5f; - } - }); + var result = EditorUI.current.widgets.TreeNode($"overlay-{settleHover}", _ => NativeImGui.TextUnformatted("Object"), new() { isLeaf = true, drawViewportOverlay = () => + { + float rowY = NativeImGui.GetItemRectMin().Y; + NativeImGui.SetCursorScreenPos(new Vector2(360f, rowY)); + if (NativeImGui.SmallButton("Eye")) + overlayPresses++; + overlayHit = (NativeImGui.GetItemRectMin() + NativeImGui.GetItemRectMax()) * 0.5f; + } }); if (result.isClicked) rowPresses++; NativeImGui.End(); }; - context.Frame(draw); context.Frame(draw); context.Click(overlayHit, 0, draw, settleHover); - Assert.Equal(1, overlayPresses); Assert.Equal(0, rowPresses); } @@ -441,10 +909,7 @@ public void TreeRows_ContentDoubleClickExpandsOnce() Action draw = () => { BeginWindow("Tree double click", new Vector2(400f, 300f)); - var result = Widget.TreeNode( - "double-click", - _ => NativeImGui.TextUnformatted("Parent"), - new() { isLeaf = false }); + var result = EditorUI.current.widgets.TreeNode("double-click", _ => NativeImGui.TextUnformatted("Parent"), new() { isLeaf = false }); hit = new Vector2(result.contentMin.X + 80f, (result.min.Y + result.max.Y) * 0.5f); opened = result.isOpen; if (result.isClicked) @@ -455,18 +920,46 @@ public void TreeRows_ContentDoubleClickExpandsOnce() NativeImGui.TreePop(); NativeImGui.End(); }; - context.Frame(draw); context.Frame(draw); context.Click(hit, 0, draw); context.Click(hit, 0, draw); context.Frame(draw); - Assert.Equal(2, presses); Assert.Equal(1, doublePresses); Assert.True(opened); } + [Fact] + public void MixedValueRestoresOuterStateAfterNestedGroupsAndFailures() + { + using NativeContext context = new(1f); + context.Frame(() => + { + BeginWindow("Mixed scope", new Vector2(400f, 300f)); + var widgets = EditorUI.current.widgets; + Assert.False(widgets.isMixedValue); + widgets.MixedValue(true, () => + { + Assert.True(widgets.isMixedValue); + widgets.MixedValue(false, () => Assert.True(widgets.isMixedValue)); + Assert.True(widgets.isMixedValue); + }); + Assert.False(widgets.isMixedValue); + Assert.Throws(() => widgets.MixedValue(true, () => throw new InvalidOperationException("rejected draw"))); + Assert.False(widgets.isMixedValue); + NativeImGui.End(); + }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static EditorStyleMetrics RetireStyleOwner(out WeakReference owner) + { + using var session = new EditorUiSession(); + owner = new WeakReference(session); + return session.widgets.style; + } + private static void BeginWindow( string name, Vector2 size @@ -474,25 +967,30 @@ Vector2 size NativeImGui.SetNextWindowPos(new Vector2(20f, 20f), (int)(ImGuiCond.Always), default(Vector2)); NativeImGui.SetNextWindowSize(size, (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated(name, allowsNull: false); - NativeImGui.Raw.Begin((sbyte*)nativeTestText0.pointer, null, (int)(ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove - | ImGuiWindowFlags.NoSavedSettings)); + NativeImGui.Raw.Begin( + (sbyte*)nativeTestText0.pointer, + null, + (int)(ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings) + ); } private sealed class NativeContext : IDisposable { private readonly ImGuiNativeContext* m_context; - + private readonly Inno.Editor.ImGui.EditorUiSession m_ui = new(); + private readonly IDisposable m_uiScope; public NativeContext(float zoom) { m_context = NativeImGui.Raw.CreateContext(null); + m_uiScope = m_ui.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(1024f, 768f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; io->IniFilename = null; - Widget.style.SetZoom(zoom); - Widget.SetupStyle(); + EditorUI.current.widgets.style.SetZoom(zoom); + EditorUI.current.widgets.SetupStyle(); } public void Frame(Action draw) @@ -520,8 +1018,9 @@ public void Click( public void Dispose() { + m_uiScope.Dispose(); + m_ui.Dispose(); NativeImGui.Raw.DestroyContext(m_context); - Widget.style.SetZoom(1f); } } } diff --git a/tests/editor/Inno.Editor.Inspection.Tests/ConditionalInspectionTests.cs b/tests/editor/Inno.Editor.Inspection.Tests/ConditionalInspectionTests.cs index 92a0269ff..85d9cbbab 100644 --- a/tests/editor/Inno.Editor.Inspection.Tests/ConditionalInspectionTests.cs +++ b/tests/editor/Inno.Editor.Inspection.Tests/ConditionalInspectionTests.cs @@ -2,6 +2,8 @@ using Inno.Adapter.Modules.DotNet; using System; using System.IO; +using System.Linq; +using Inno.Editor.Annotations; using Inno.Core.Logging; using Inno.Core.Serialization; using Inno.Editor.Core; @@ -11,7 +13,6 @@ using Xunit; namespace Inno.Editor.Inspection.Tests; - public sealed class ConditionalInspectionTests : IDisposable { private readonly string m_root = Path.Combine(Path.GetTempPath(), "inno-inspection-" + Guid.NewGuid().ToString("N")); @@ -20,18 +21,16 @@ public sealed class ConditionalInspectionTests : IDisposable private readonly SerializationRegistry m_serialization; private readonly LogRouter m_logs = new(); private readonly EditorInteractionRuntime m_runtime; - public ConditionalInspectionTests() { Directory.CreateDirectory(m_root); - m_modules = new(new() - { - catalogSource = new DotNetAssemblyCatalogSource(typeof(ConditionalInspectionTests).Assembly) }); + m_modules = new(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ConditionalInspectionTests).Assembly) }); m_types = new(m_modules, new ReflectionTypeCatalogSource()); m_serialization = new(m_types, new ReflectionSerializationMetadataSource()); m_runtime = new(new EditorContext(m_root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_types, m_logs, [m_types, m_serialization]); m_runtime.Start(); } + [Fact] public void ConditionalInspectorPeersResolveByPredicateAndRejectActualAmbiguity() { @@ -43,14 +42,37 @@ public void ConditionalInspectorPeersResolveByPredicateAndRejectActualAmbiguity( Assert.IsType(first); Assert.True(inspectors.TryResolveExact(m_runtime.context, new ConditionalTarget(2, false), renderer, out var second, out _)); Assert.IsType(second); - InvalidOperationException error = Assert.Throws(() => - inspectors.TryResolve(m_runtime.context, new ConditionalTarget(2, true), renderer, out _, out _)); + InvalidOperationException error = Assert.Throws(() => inspectors.TryResolve(m_runtime.context, new ConditionalTarget(2, true), renderer, out _, out _)); Assert.Contains("both accept", error.Message); - Assert.Throws(() => - inspectors.TryResolveExact(m_runtime.context, new ConditionalTarget(2, true), renderer, out _, out _)); + Assert.Throws(() => inspectors.TryResolveExact(m_runtime.context, new ConditionalTarget(2, true), renderer, out _, out _)); + IInspectionDrawer CreateDrawer(Type type) => (IInspectionDrawer)Activator.CreateInstance(type, nonPublic: true)!; + } + + [Fact] + public void PropertyAvailabilityUsesCurrentOwnerConditionsWithoutRequiringNativeUi() + { + using var properties = new PropertyDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + using var attributes = new InspectorAttributeDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + var renderer = new SerializedPropertyRenderer(properties, attributes, m_runtime.interactions, new ProbePropertyEdits(), m_logs); + var target = new AvailabilityTarget(); + var values = m_serialization.GetProperties(target); + SerializedProperty conditional = values.Single(value => value.name == nameof(AvailabilityTarget.value)); + Assert.False(renderer.GetPropertyAvailability(target, conditional).isVisible); + target.visible = true; + Assert.True(renderer.GetPropertyAvailability(target, conditional).isVisible); + Assert.True(renderer.GetPropertyAvailability(target, values.Single(value => value.name == nameof(AvailabilityTarget.readOnlyValue))).isReadOnly); + } - IInspectionDrawer CreateDrawer(Type type) - => (IInspectionDrawer)Activator.CreateInstance(type, nonPublic: true)!; + [StableTypeId("eeeb7c59-2af3-4cc3-81d2-c853feef3874")] + public sealed class AvailabilityTarget : ISerializable + { + public bool visible { get; set; } + + [SerializableProperty, ShowIf(nameof(visible))] + public int value { get; set; } + + [SerializableProperty, InspectorReadOnly] + public int readOnlyValue { get; set; } } public sealed record ConditionalTarget( @@ -61,6 +83,7 @@ bool ambiguous public sealed class ConditionalFirst : InspectionDrawer { public override string icon => ""; + protected override bool CanInspect(ConditionalTarget target) => target.kind == 1; protected override (string name, Action? setter) BindName( InspectionDrawContext context, @@ -69,12 +92,15 @@ ConditionalTarget target protected override void Draw( InspectionDrawContext context, ConditionalTarget target - ) { } + ) { + } } + [InspectionDrawer(typeof(ConditionalTarget), conditional: true)] public sealed class ConditionalSecond : InspectionDrawer { public override string icon => ""; + protected override bool CanInspect(ConditionalTarget target) => target.kind == 2; protected override (string name, Action? setter) BindName( InspectionDrawContext context, @@ -83,12 +109,15 @@ ConditionalTarget target protected override void Draw( InspectionDrawContext context, ConditionalTarget target - ) { } + ) { + } } + [InspectionDrawer(typeof(ConditionalTarget), conditional: true)] public sealed class ConditionalOverlap : InspectionDrawer { public override string icon => ""; + protected override bool CanInspect(ConditionalTarget target) => target.ambiguous; protected override (string name, Action? setter) BindName( InspectionDrawContext context, @@ -97,20 +126,22 @@ ConditionalTarget target protected override void Draw( InspectionDrawContext context, ConditionalTarget target - ) { } + ) { + } } + private sealed class ProbePropertyEdits : IInspectionPropertyEditService { public bool ChangeProperty( object owner, string propertyName, Action mutation, - string historyName - ) - => throw new InvalidOperationException("Resolution must not edit a property."); + string historyName, + string? mergeKey, + string? componentName + ) => throw new InvalidOperationException("Resolution must not edit a property."); } - public void Dispose() { m_runtime.Dispose(); @@ -121,4 +152,3 @@ public void Dispose() Directory.Delete(m_root, true); } } - diff --git a/tests/editor/Inno.Editor.Inspection.Tests/Inno.Editor.Inspection.Tests.csproj b/tests/editor/Inno.Editor.Inspection.Tests/Inno.Editor.Inspection.Tests.csproj index 2b197c70d..75917ed19 100644 --- a/tests/editor/Inno.Editor.Inspection.Tests/Inno.Editor.Inspection.Tests.csproj +++ b/tests/editor/Inno.Editor.Inspection.Tests/Inno.Editor.Inspection.Tests.csproj @@ -7,9 +7,11 @@ false true Editor + $(DefineConstants);INNO_EDITOR + @@ -25,5 +27,8 @@ - + + + + diff --git a/tests/editor/Inno.Editor.Interactions.Tests/AssetHistoryTests.cs b/tests/editor/Inno.Editor.Interactions.Tests/AssetHistoryTests.cs index abc6a605e..91de7bcb7 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/AssetHistoryTests.cs +++ b/tests/editor/Inno.Editor.Interactions.Tests/AssetHistoryTests.cs @@ -3,7 +3,6 @@ using Inno.Adapter.Modules.DotNet; using System; using System.IO; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Core.Diagnostics; @@ -14,20 +13,18 @@ using Inno.Core.Serialization; using Inno.Core.Settings; using Inno.Editor.Core; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Plugins.Authoring; using Xunit; namespace Inno.Editor.Interactions.Tests; - public sealed class AssetHistoryTests : IDisposable { + private readonly Inno.Editor.ImGui.EditorUiSession m_ui = new(); private readonly IdentityAllocator m_identities = new(); private readonly IDisposable m_identityScope; - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoAssetHistoryTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoAssetHistoryTests", Guid.NewGuid().ToString("N")); private readonly ModuleHost m_modules; private readonly TypeCatalog m_types; private readonly SerializationRegistry m_serialization; @@ -37,65 +34,47 @@ public sealed class AssetHistoryTests : IDisposable private readonly PluginEnvironment m_plugins; private readonly ProjectSettingsStore m_settings; private readonly EditorInteractionRuntime m_runtime; - public AssetHistoryTests() { string assetRoot = Path.Combine(m_projectRoot, "Assets"); Directory.CreateDirectory(assetRoot); m_identityScope = m_identities.EnterScope(); - _ = typeof(AssetEditorModule); + _ = typeof(AssetEditingModule); _ = typeof(TextAsset); - m_modules = new ModuleHost(new ModuleHostOptions - { catalogSource = new DotNetAssemblyCatalogSource(typeof(AssetHistoryTests).Assembly) }); + m_modules = new ModuleHost(new ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(AssetHistoryTests).Assembly) }); m_types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); m_serialization = new SerializationRegistry(m_types, new ReflectionSerializationMetadataSource()); string libraryRoot = Path.Combine(m_projectRoot, "Library"); string pluginRoot = Path.Combine(m_projectRoot, "Plugins"); - var pluginSources = new PluginSourceService( - m_serialization, - pluginRoot, - libraryRoot); + var pluginSources = new PluginSourceService(m_serialization, pluginRoot, libraryRoot); PluginScanResult pluginScan = pluginSources.Scan(); - AssetPipelineOptions options = AssetPipelineOptions.Create( - assetRoot, - libraryRoot); + AssetPipelineOptions options = AssetPipelineOptions.Create(assetRoot, libraryRoot); options = options with { enableFileSystemWatcher = false }; - m_assets = new AssetPipeline( - m_modules, - m_types, - m_serialization, - m_identities, - m_diagnostics, - m_logs, - options); + m_assets = new AssetPipeline(m_modules, m_types, m_serialization, m_identities, m_diagnostics, m_logs, options); m_settings = new ProjectSettingsStore( new FileByteDocumentStore(Path.GetFullPath(Path.Combine(m_projectRoot, "Settings.Project.inno"))), m_types, m_serialization, new ProjectId("tests.editor"), - AssetSerializationContext.Create(m_assets)); - m_plugins = new PluginEnvironment( - m_assets, - m_settings, - m_serialization, - pluginRoot, - libraryRoot, - pluginScan, - m_modules.generations); + AssetSerializationContext.Create(m_assets) + ); + m_plugins = new PluginEnvironment(m_assets, m_settings, m_serialization, pluginRoot, libraryRoot, pluginScan, m_modules.generations); m_runtime = new EditorInteractionRuntime( new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_types, m_logs, - [m_types, m_serialization, m_identities, m_assets, m_plugins]); + [m_types, m_serialization, m_identities, m_assets, m_plugins, m_ui] + ); m_runtime.Start(); } public void Dispose() { m_runtime.Dispose(); + m_ui.Dispose(); m_plugins.Dispose(); m_assets.Dispose(); m_settings.Dispose(); @@ -108,15 +87,30 @@ public void Dispose() Directory.Delete(m_projectRoot, recursive: true); } + [Fact] + public void MultiSelectionDeleteUsesOneAtomicHistoryEntry() + { + m_assets.CreateDirectory(AssetPath.Project("First")); + m_assets.CreateDirectory(AssetPath.Project("First/Child")); + m_assets.CreateDirectory(AssetPath.Project("Second")); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("First"), out AssetFileEntry first)); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("First/Child"), out AssetFileEntry child)); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("Second"), out AssetFileEntry second)); + m_runtime.interactions.SetSelection([child, first, second], child); + Assert.True(m_runtime.interactions.For("panel/asset.file-browser", second).Execute("file-browser/delete")); + Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("First"), out _)); + Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("Second"), out _)); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("First/Child"), out _)); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("Second"), out _)); + } + [Fact] public void CreateFolderUndoAndRedoUseTheAssetDatabase() { - Assert.True(m_runtime.interactions - .For("panel/asset.file-browser", string.Empty) - .Execute("file-browser/create-folder")); + Assert.True(m_runtime.interactions.For("panel/asset.file-browser", string.Empty).Execute("file-browser/create-folder")); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("New Folder"), out AssetFileEntry created)); Assert.True(created.isDirectory); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("New Folder"), out _)); Assert.True(m_runtime.interactions.history.Redo().succeeded); @@ -130,18 +124,13 @@ public void DeleteFolderUndoRestoresIdentityMetadataAndEmptyDescendants() m_assets.CreateDirectory(AssetPath.Project("Folder/Empty")); Assert.True(m_assets.TryGetPersistentId(AssetPath.Project("Folder"), out Guid folderId)); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("Folder"), out AssetFileEntry folder)); - - Assert.True(m_runtime.interactions - .For("panel/asset.file-browser", folder) - .Execute("file-browser/delete")); + Assert.True(m_runtime.interactions.For("panel/asset.file-browser", folder).Execute("file-browser/delete")); Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("Folder"), out _)); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("Folder/Empty"), out AssetFileEntry restoredEmpty)); Assert.True(restoredEmpty.isDirectory); Assert.True(m_assets.TryGetPersistentId(AssetPath.Project("Folder"), out Guid restoredId)); Assert.Equal(folderId, restoredId); - Assert.True(m_runtime.interactions.history.Redo().succeeded); Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("Folder"), out _)); } @@ -151,13 +140,9 @@ public void AssetDeleteHistorySurvivesATypeCatalogRefresh() { m_assets.CreateDirectory(AssetPath.Project("ReloadSafe")); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("ReloadSafe"), out AssetFileEntry folder)); - Assert.True(m_runtime.interactions - .For("panel/asset.file-browser", folder) - .Execute("file-browser/delete")); - + Assert.True(m_runtime.interactions.For("panel/asset.file-browser", folder).Execute("file-browser/delete")); m_types.Rebuild(); _ = m_runtime.panelCount; - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("ReloadSafe"), out _)); } @@ -170,32 +155,22 @@ public void FileBrowserDropMovesFilesAndFoldersWithUndoRedo() m_assets.CreateDirectory(AssetPath.Project("From/Folder/Child")); m_assets.CreateDirectory(AssetPath.Project("To")); Assert.True(m_assets.Save(AssetPath.Project("From/Value.txt"), new TextAsset("value"))); - Assert.True(m_assets.TryGetFileSystemEntry( - AssetPath.Project("From/Value.txt"), - out AssetFileEntry file)); - - RuntimeIdentity fileToken = m_runtime.interactions - .For("panel/asset.file-browser", file) - .BeginDrag(new EditorDragData(file, "Value.txt")); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("From/Value.txt"), out AssetFileEntry file)); + RuntimeIdentity fileToken = m_runtime.interactions.For("panel/asset.file-browser", file).BeginDrag(new EditorDragData(file, "Value.txt")); EditorInteraction destination = m_runtime.interactions.For("panel/asset.file-browser", "To"); Assert.True(destination.QueryDrop(fileToken, EditorDropPlacement.Into).canDrop); Assert.True(destination.Drop(fileToken, EditorDropPlacement.Into).accepted); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("To/Value.txt"), out _)); Assert.False(m_assets.TryGetFileSystemEntry(AssetPath.Project("From/Value.txt"), out _)); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("From/Value.txt"), out _)); Assert.True(m_runtime.interactions.history.Redo().succeeded); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("To/Value.txt"), out _)); - Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("From/Folder"), out AssetFileEntry folder)); - RuntimeIdentity folderToken = m_runtime.interactions - .For("panel/asset.file-browser", folder) - .BeginDrag(new EditorDragData(folder, "Folder")); + RuntimeIdentity folderToken = m_runtime.interactions.For("panel/asset.file-browser", folder).BeginDrag(new EditorDragData(folder, "Folder")); Assert.True(destination.QueryDrop(folderToken, EditorDropPlacement.Into).canDrop); Assert.True(destination.Drop(folderToken, EditorDropPlacement.Into).accepted); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("To/Folder/Child"), out _)); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("From/Folder/Child"), out _)); } @@ -208,21 +183,9 @@ public void FileBrowserDropRejectsAFolderOwnDescendantAndNameCollisions() m_assets.CreateDirectory(AssetPath.Project("Target")); m_assets.CreateDirectory(AssetPath.Project("Target/Source")); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project("Source"), out AssetFileEntry source)); - - RuntimeIdentity descendantToken = m_runtime.interactions - .For("panel/asset.file-browser", source) - .BeginDrag(new EditorDragData(source, "Source")); - Assert.False(m_runtime.interactions - .For("panel/asset.file-browser", "Source/Child") - .QueryDrop(descendantToken, EditorDropPlacement.Into) - .canDrop); - - RuntimeIdentity collisionToken = m_runtime.interactions - .For("panel/asset.file-browser", source) - .BeginDrag(new EditorDragData(source, "Source")); - Assert.False(m_runtime.interactions - .For("panel/asset.file-browser", "Target") - .QueryDrop(collisionToken, EditorDropPlacement.Into) - .canDrop); + RuntimeIdentity descendantToken = m_runtime.interactions.For("panel/asset.file-browser", source).BeginDrag(new EditorDragData(source, "Source")); + Assert.False(m_runtime.interactions.For("panel/asset.file-browser", "Source/Child").QueryDrop(descendantToken, EditorDropPlacement.Into).canDrop); + RuntimeIdentity collisionToken = m_runtime.interactions.For("panel/asset.file-browser", source).BeginDrag(new EditorDragData(source, "Source")); + Assert.False(m_runtime.interactions.For("panel/asset.file-browser", "Target").QueryDrop(collisionToken, EditorDropPlacement.Into).canDrop); } } diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorHistoryTests.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorHistoryTests.cs index d8f17cee9..73c4c5b5e 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/EditorHistoryTests.cs +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorHistoryTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; - using Inno.Extensibility.Modules; using Inno.Extensibility.Types; using Inno.Core.Logging; @@ -11,18 +10,84 @@ using Xunit; namespace Inno.Editor.Interactions.Tests; - public sealed class EditorHistoryTests { [Fact] - public void ExecuteUndoAndRedoMoveNeutralChangeBetweenStacks() + public void SeparateGesturesNeverMergeTheSamePropertyAcrossCommitBoundaries() + { + using var fixture = new HistoryFixture(); + for (int step = 0; step < 2; step++) + { + using var gesture = new EditorEditGesture(fixture.history, "Drag Value"); + Assert.True(fixture.history.Execute("Update", NeutralValueHistoryHandler.CreateChange(1, step, step + 1, gesture.mergeKey)).succeeded); + gesture.Commit(); + } + + Assert.True(fixture.history.Undo().succeeded); + Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); + Assert.True(fixture.history.Undo().succeeded); + Assert.Equal(0, NeutralValueHistoryHandler.GetValue(1)); + } + + [Fact] + public void BlockedGestureCancellationRetainsOwnershipUntilRecovery() { using var fixture = new HistoryFixture(); + using var gesture = new EditorEditGesture(fixture.history, "Drag Value"); + Assert.True(fixture.history.Execute("Update", NeutralValueHistoryHandler.CreateChange(1, 0, 5, gesture.mergeKey)).succeeded); + NeutralValueHistoryHandler.Block(1, EditorHistoryDirection.Undo); + Assert.False(gesture.Cancel().succeeded); + Assert.True(gesture.isActive); + Assert.Equal(5, NeutralValueHistoryHandler.GetValue(1)); + NeutralValueHistoryHandler.Unblock(1, EditorHistoryDirection.Undo); + Assert.True(gesture.Cancel().succeeded); + Assert.False(gesture.isActive); + Assert.Equal(0, NeutralValueHistoryHandler.GetValue(1)); + Assert.False(fixture.history.canUndo); + } - EditorHistoryResult applied = fixture.history.Execute( - "Change Value", - NeutralValueHistoryHandler.CreateChange(slot: 1, before: 0, after: 10)); + [Theory] + [InlineData("commit")] + [InlineData("rollback")] + [InlineData("dispose")] + public void RejectedOutOfOrderCompletionPreservesBothTransactions(string completion) + { + using var fixture = new HistoryFixture(); + using EditorHistoryTransaction outer = fixture.history.BeginTransaction("Outer"); + Assert.True(fixture.history.Execute("First", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + using EditorHistoryTransaction inner = fixture.history.BeginTransaction("Inner"); + Assert.True(fixture.history.Execute("Second", NeutralValueHistoryHandler.CreateChange(2, 0, 2)).succeeded); + Assert.Throws(() => + { + switch (completion) + { + case "commit": + outer.Commit(); + break; + case "rollback": + _ = outer.Rollback(); + break; + default: + outer.Dispose(); + break; + } + }); + Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); + Assert.Equal(2, NeutralValueHistoryHandler.GetValue(2)); + Assert.True(inner.Rollback().succeeded); + Assert.Equal(0, NeutralValueHistoryHandler.GetValue(2)); + outer.Commit(); + Assert.Equal("Outer", fixture.history.undoName); + Assert.True(fixture.history.Undo().succeeded); + Assert.Equal(0, NeutralValueHistoryHandler.GetValue(1)); + Assert.False(fixture.history.canUndo); + } + [Fact] + public void ExecuteUndoAndRedoMoveNeutralChangeBetweenStacks() + { + using var fixture = new HistoryFixture(); + EditorHistoryResult applied = fixture.history.Execute("Change Value", NeutralValueHistoryHandler.CreateChange(slot: 1, before: 0, after: 10)); Assert.True(applied.succeeded); Assert.Equal(10, NeutralValueHistoryHandler.GetValue(1)); Assert.Equal("Change Value", fixture.history.undoName); @@ -37,23 +102,18 @@ public void ExecuteUndoAndRedoMoveNeutralChangeBetweenStacks() public void FailedTransitionRemainsAvailableForRetry() { using var fixture = new HistoryFixture(); - Assert.True(fixture.history.Execute( - "Conditional", - NeutralValueHistoryHandler.CreateChange(slot: 1, before: 0, after: 1)).succeeded); - + Assert.True(fixture.history.Execute("Conditional", NeutralValueHistoryHandler.CreateChange(slot: 1, before: 0, after: 1)).succeeded); NeutralValueHistoryHandler.Block(slot: 1, EditorHistoryDirection.Undo); Assert.False(fixture.history.Undo().succeeded); Assert.True(fixture.history.canUndo); Assert.Equal("Conditional", fixture.history.undoName); Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); - NeutralValueHistoryHandler.Unblock(slot: 1, EditorHistoryDirection.Undo); Assert.True(fixture.history.Undo().succeeded); NeutralValueHistoryHandler.Block(slot: 1, EditorHistoryDirection.Redo); Assert.False(fixture.history.Redo().succeeded); Assert.True(fixture.history.canRedo); Assert.Equal(0, NeutralValueHistoryHandler.GetValue(1)); - NeutralValueHistoryHandler.Unblock(slot: 1, EditorHistoryDirection.Redo); Assert.True(fixture.history.Redo().succeeded); Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); @@ -64,14 +124,9 @@ public void AdjacentChangesWithTheSameStableMergeKeyCoalesce() { using var fixture = new HistoryFixture(); NeutralValueHistoryHandler.SetValue(slot: 1, value: 2); - fixture.history.RecordApplied( - "Change Value", - NeutralValueHistoryHandler.CreateChange(1, 1, 2, mergeKey: "value/1")); + fixture.history.RecordApplied("Change Value", NeutralValueHistoryHandler.CreateChange(1, 1, 2, mergeKey: "value/1")); NeutralValueHistoryHandler.SetValue(slot: 1, value: 3); - fixture.history.RecordApplied( - "Change Value", - NeutralValueHistoryHandler.CreateChange(1, 2, 3, mergeKey: "value/1")); - + fixture.history.RecordApplied("Change Value", NeutralValueHistoryHandler.CreateChange(1, 2, 3, mergeKey: "value/1")); Assert.True(fixture.history.Undo().succeeded); Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); Assert.False(fixture.history.canUndo); @@ -85,12 +140,8 @@ public void TransactionCommitsAndRevertsAtomically() using var fixture = new HistoryFixture(); using (EditorHistoryTransaction transaction = fixture.history.BeginTransaction("Batch")) { - Assert.True(fixture.history.Execute( - "First", - NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); - Assert.True(fixture.history.Execute( - "Second", - NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("First", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("Second", NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); transaction.Commit(); } @@ -107,16 +158,10 @@ public void TransactionCommitsAndRevertsAtomically() public void RecordingANewMutationReleasesTheRedoBranch() { using var fixture = new HistoryFixture(); - Assert.True(fixture.history.Execute( - "First", - NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("First", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); Assert.True(fixture.history.Undo().succeeded); Assert.True(fixture.history.canRedo); - - Assert.True(fixture.history.Execute( - "Replacement", - NeutralValueHistoryHandler.CreateChange(1, 0, 2)).succeeded); - + Assert.True(fixture.history.Execute("Replacement", NeutralValueHistoryHandler.CreateChange(1, 0, 2)).succeeded); Assert.False(fixture.history.canRedo); Assert.Equal(2, NeutralValueHistoryHandler.GetValue(1)); } @@ -125,18 +170,13 @@ public void RecordingANewMutationReleasesTheRedoBranch() public void IsolatedBranchIsReleasedAndRestoresTheEditingStacks() { using var fixture = new HistoryFixture(); - Assert.True(fixture.history.Execute( - "Edit Value", - NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("Edit Value", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); Assert.True(fixture.history.Undo().succeeded); - using (fixture.runtime.interactions.BeginHistoryIsolation()) { Assert.False(fixture.history.canUndo); Assert.False(fixture.history.canRedo); - Assert.True(fixture.history.Execute( - "Runtime Value", - NeutralValueHistoryHandler.CreateChange(1, 0, 2)).succeeded); + Assert.True(fixture.history.Execute("Runtime Value", NeutralValueHistoryHandler.CreateChange(1, 0, 2)).succeeded); Assert.Equal("Runtime Value", fixture.history.undoName); } @@ -152,16 +192,10 @@ public void FailedTransactionRollbackCanBeRetriedWithoutPartialState() { using var fixture = new HistoryFixture(); EditorHistoryTransaction transaction = fixture.history.BeginTransaction("Batch"); - Assert.True(fixture.history.Execute( - "First", - NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); - Assert.True(fixture.history.Execute( - "Second", - NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("First", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("Second", NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); NeutralValueHistoryHandler.Block(slot: 1, EditorHistoryDirection.Undo); - EditorHistoryResult firstAttempt = transaction.Rollback(); - Assert.False(firstAttempt.succeeded); Assert.True(firstAttempt.statePreserved); Assert.Equal(1, NeutralValueHistoryHandler.GetValue(1)); @@ -178,17 +212,11 @@ public void CompensationFailureFaultsHistoryAndRejectsFurtherTransactions() { using var fixture = new HistoryFixture(); EditorHistoryTransaction transaction = fixture.history.BeginTransaction("Batch"); - Assert.True(fixture.history.Execute( - "First", - NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); - Assert.True(fixture.history.Execute( - "Second", - NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("First", NeutralValueHistoryHandler.CreateChange(1, 0, 1)).succeeded); + Assert.True(fixture.history.Execute("Second", NeutralValueHistoryHandler.CreateChange(2, 0, 1)).succeeded); NeutralValueHistoryHandler.Block(slot: 1, EditorHistoryDirection.Undo); NeutralValueHistoryHandler.Block(slot: 2, EditorHistoryDirection.Redo); - EditorHistoryResult result = transaction.Rollback(); - Assert.False(result.succeeded); Assert.False(result.statePreserved); Assert.True(fixture.history.isFaulted); @@ -202,12 +230,7 @@ public void CompensationFailureFaultsHistoryAndRejectsFurtherTransactions() public void MissingHandlerCreatesAnExplicitAvailabilityBarrier() { using var fixture = new HistoryFixture(); - fixture.history.RecordApplied( - "Unavailable Change", - new EditorHistoryChange( - "tests/missing-history-handler", - EditorHistoryPayload.FromBytes([1, 2, 3]))); - + fixture.history.RecordApplied("Unavailable Change", new EditorHistoryChange("tests/missing-history-handler", EditorHistoryPayload.FromBytes([1, 2, 3]))); Assert.False(fixture.history.canUndo); Assert.Contains("tests/missing-history-handler", fixture.history.undoUnavailableReason); Assert.False(fixture.history.Undo().succeeded); @@ -223,12 +246,7 @@ public void LargePayloadUsesTheBoundedSessionDiskStore() BitConverter.GetBytes(9).CopyTo(payload, sizeof(int)); BitConverter.GetBytes(32).CopyTo(payload, sizeof(int) * 2); NeutralValueHistoryHandler.SetValue(slot: 1, value: 32); - fixture.history.RecordApplied( - "Large Neutral Change", - new EditorHistoryChange( - NeutralValueHistoryHandler.KIND, - EditorHistoryPayload.FromBytes(payload))); - + fixture.history.RecordApplied("Large Neutral Change", new EditorHistoryChange(NeutralValueHistoryHandler.KIND, EditorHistoryPayload.FromBytes(payload))); Assert.Equal(0, fixture.history.residentBytes); Assert.Equal(payload.LongLength, fixture.history.diskBytes); Assert.True(fixture.history.Undo().succeeded); @@ -240,13 +258,9 @@ public void NeutralHistorySurvivesATypeCatalogGenerationChange() { using var fixture = new HistoryFixture(); NeutralValueHistoryHandler.SetValue(slot: 1, value: 14); - fixture.history.RecordApplied( - "Change Neutral Value", - NeutralValueHistoryHandler.CreateChange(1, 3, 14)); - + fixture.history.RecordApplied("Change Neutral Value", NeutralValueHistoryHandler.CreateChange(1, 3, 14)); fixture.types.Rebuild(); _ = fixture.runtime.panelCount; - Assert.True(fixture.history.Undo().succeeded); Assert.Equal(3, NeutralValueHistoryHandler.GetValue(1)); Assert.True(fixture.history.Redo().succeeded); @@ -255,32 +269,26 @@ public void NeutralHistorySurvivesATypeCatalogGenerationChange() private sealed class HistoryFixture : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoEditorHistoryTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoEditorHistoryTests", Guid.NewGuid().ToString("N")); private readonly ModuleHost m_modules; private readonly LogRouter m_logs = new(); - internal HistoryFixture() { Directory.CreateDirectory(Path.Combine(m_projectRoot, "Assets")); NeutralValueHistoryHandler.Reset(); - m_modules = new ModuleHost(new ModuleHostOptions - { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorHistoryTests).Assembly) }); + m_modules = new ModuleHost(new ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorHistoryTests).Assembly) }); types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); runtime = new EditorInteractionRuntime( new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), types, m_logs, - [types]); + [types] + ); runtime.Start(); } internal TypeCatalog types { get; } - internal EditorInteractionRuntime runtime { get; } - internal IEditorHistory history => runtime.interactions.history; public void Dispose() @@ -299,10 +307,8 @@ public void Dispose() public sealed class NeutralValueHistoryHandler : EditorHistoryHandler { public const string KIND = "tests/neutral-values"; - private static readonly HashSet<(int Slot, EditorHistoryDirection Direction)> S_BLOCKED = []; private static readonly Dictionary S_VALUES = []; - public static EditorHistoryChange CreateChange( int slot, int before, @@ -316,27 +322,19 @@ public static EditorHistoryChange CreateChange( return new EditorHistoryChange(KIND, EditorHistoryPayload.FromBytes(bytes), mergeKey); } - public static int GetValue(int slot) - => S_VALUES.TryGetValue(slot, out int value) ? value : 0; - + public static int GetValue(int slot) => S_VALUES.TryGetValue(slot, out int value) ? value : 0; public static void SetValue( int slot, int value - ) - => S_VALUES[slot] = value; - + ) => S_VALUES[slot] = value; public static void Block( int slot, EditorHistoryDirection direction - ) - => S_BLOCKED.Add((slot, direction)); - + ) => S_BLOCKED.Add((slot, direction)); public static void Unblock( int slot, EditorHistoryDirection direction - ) - => S_BLOCKED.Remove((slot, direction)); - + ) => S_BLOCKED.Remove((slot, direction)); public static void Reset() { S_BLOCKED.Clear(); @@ -347,11 +345,7 @@ protected override EditorHistoryAvailability Query( EditorHistoryContext context, EditorHistoryChange change, EditorHistoryDirection direction - ) - => change.payload.length >= sizeof(int) * 3 - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable("The neutral value payload is truncated."); - + ) => change.payload.length >= sizeof(int) * 3 ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable("The neutral value payload is truncated."); protected override EditorHistoryResult Apply( EditorHistoryContext context, EditorHistoryChange change, @@ -361,9 +355,7 @@ EditorHistoryDirection direction int slot = BitConverter.ToInt32(bytes, 0); if (S_BLOCKED.Contains((slot, direction))) return EditorHistoryResult.Failure($"Slot {slot} is blocked for {direction}."); - int offset = direction == EditorHistoryDirection.Undo - ? sizeof(int) - : sizeof(int) * 2; + int offset = direction == EditorHistoryDirection.Undo ? sizeof(int) : sizeof(int) * 2; S_VALUES[slot] = BitConverter.ToInt32(bytes, offset); return EditorHistoryResult.Success(); } @@ -382,11 +374,7 @@ out EditorHistoryChange? merged return false; } - merged = CreateChange( - slot, - BitConverter.ToInt32(olderBytes, sizeof(int)), - BitConverter.ToInt32(newerBytes, sizeof(int) * 2), - older.mergeKey); + merged = CreateChange(slot, BitConverter.ToInt32(olderBytes, sizeof(int)), BitConverter.ToInt32(newerBytes, sizeof(int) * 2), older.mergeKey); return true; } } diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Commands.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Commands.cs new file mode 100644 index 000000000..e27c65125 --- /dev/null +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Commands.cs @@ -0,0 +1,117 @@ +using System; +using System.Linq; +using Inno.Core.Input; +using Inno.Editor.Interactions; +using Xunit; + +namespace Inno.Editor.Interactions.Tests; + +public sealed partial class EditorRuntimeTests +{ + [Fact] + public void StaticMenuTemplatesKeepAvailabilityLabelsAndReasonsFresh() + { + var target = new MenuStateTarget(); + var interaction = m_runtime.interactions.For("tests/static-menu", target); + for (int index = 0; index < 3; index++) + { + target.label = "Current " + index; + target.enabled = index != 1; + target.isChecked = index == 2; + EditorMenuItem leaf = Assert.Single(Assert.Single(interaction.BuildMenu().items).children); + Assert.Equal(target.label, leaf.label); + Assert.Equal(target.enabled, leaf.status.isEnabled); + Assert.Equal(target.isChecked, leaf.status.isChecked); + Assert.Equal(target.enabled ? null : "Current owner is read-only.", leaf.status.unavailableReason); + } + target.visible = false; + Assert.Empty(interaction.BuildMenu().items); + m_types.Rebuild(); + target.visible = true; + target.label = "After registry refresh"; + Assert.Equal(target.label, Assert.Single(Assert.Single(interaction.BuildMenu().items).children).label); + GC.KeepAlive(target); + } + + [Fact] + public void HiddenHigherOrderPlacementDoesNotEraseTheVisibleAction() + { + var target = new MenuStateTarget { visible = false }; + var interaction = m_runtime.interactions.For("tests/shared-menu", target); + Assert.Equal("tests.menu-fallback", Assert.Single(interaction.BuildMenu().items).actionId); + target.visible = true; + Assert.Equal("tests.menu-conditional", Assert.Single(interaction.BuildMenu().items).actionId); + target.visible = false; + Assert.Equal("tests.menu-fallback", Assert.Single(interaction.BuildMenu().items).actionId); + GC.KeepAlive(target); + } + + [Fact] + public void FailedDynamicSourceCannotPublishItsPartialMenu() + { + Assert.Empty(m_runtime.interactions.For("tests/failed-menu").BuildMenu().items); + Assert.NotEmpty(m_runtime.interactions.For("tests/menu").BuildMenu().items); + } + + [Fact] + public void ShortcutIndexRefreshPreservesDispatchAndRejectsRepeats() + { + m_runtime.interactions.For("tests/multiple-shortcuts").Focus(); + m_runtime.HandleKeyPressed(new(0, KeyCode.F3)); + Assert.Equal(1, MultiShortcutAction.executeCount); + m_types.Rebuild(); + m_runtime.HandleKeyPressed(new(0, KeyCode.F4)); + Assert.Equal(2, MultiShortcutAction.executeCount); + m_runtime.HandleKeyPressed(new(0, KeyCode.F4, repeat: true)); + Assert.Equal(2, MultiShortcutAction.executeCount); + } +} + +public sealed class MenuStateTarget +{ + public bool visible = true; + public bool enabled = true; + public bool isChecked; + public string label = "Current"; +} + +[EditorAction("tests.menu-state", "tests/static-menu")] +[EditorMenu("tests/static-menu", "Tools/State")] +public sealed class MenuStateAction : EditorAction +{ + protected override EditorActionState Query(EditorActionContext context) + => context.target is MenuStateTarget target + ? new(target.visible, target.enabled, target.isChecked, target.label, "Current owner is read-only.") + : EditorActionState.hidden; + + protected override void Execute(EditorActionContext context) { } +} + +[EditorAction("tests.menu-fallback", "tests/shared-menu")] +[EditorMenu("tests/shared-menu", "Shared", order: 100)] +public sealed class MenuFallbackAction : EditorAction +{ + protected override void Execute(EditorActionContext context) { } +} + +[EditorAction("tests.menu-conditional", "tests/shared-menu")] +[EditorMenu("tests/shared-menu", "Shared", order: 200)] +public sealed class MenuConditionalAction : EditorAction +{ + protected override EditorActionState Query(EditorActionContext context) + => context.target is MenuStateTarget { visible: true } ? EditorActionState.enabled : EditorActionState.hidden; + + protected override void Execute(EditorActionContext context) { } +} + +[EditorMenuSource("tests/failed-menu")] +public sealed class RejectedMenuSource : EditorMenuSource +{ + public override void Build( + EditorMenuContext context, + EditorMenuBuilder builder + ) { + builder.Add("Partial", "tests.menu"); + throw new InvalidOperationException("Candidate menu source failed."); + } +} diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Performance.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Performance.cs new file mode 100644 index 000000000..b104bac43 --- /dev/null +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Performance.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using System.Text.Json; +using Inno.Core.Events; +using Inno.Core.Input; +using Inno.Editor.Core; +using Xunit; + +namespace Inno.Editor.Interactions.Tests; + +public sealed partial class EditorRuntimeTests +{ + [Fact] + [Trait("Category", "Performance")] + public void PublicCommandAndStatisticsPathsProduceRepeatableSamples() + { + var interaction = m_runtime.interactions.For("tests/menu"); + Measure("menu-open", () => Assert.NotEmpty(interaction.BuildMenu().items)); + m_runtime.interactions.For("tests/multiple-shortcuts").Focus(); + int beforeDispatch = MultiShortcutAction.executeCount; + Measure("shortcut-dispatch", () => m_runtime.HandleKeyPressed(new KeyPressedEvent(0, KeyCode.F3, KeyModifier.None))); + Assert.Equal(beforeDispatch + 10_200, MultiShortcutAction.executeCount); + Measure("shortcut-unmatched", () => m_runtime.HandleKeyPressed(new KeyPressedEvent(0, KeyCode.F12, KeyModifier.None))); + Assert.Equal(beforeDispatch + 10_200, MultiShortcutAction.executeCount); + var statistics = new EditorStatistics(); + for (int index = 0; index < 100; index++) + { + statistics.Publish(new EditorStatistic( + new("metric/" + index), + new("group/" + index / 10), + "Group " + index / 10, + "Metric " + index, + index.ToString(), + order: index)); + } + Measure("statistics-unchanged", () => Assert.Equal(100, statistics.GetSnapshot().Count)); + } + + private static void Measure( + string name, + Action operation + ) { + const int iterations = 2_000; + using Process process = Process.GetCurrentProcess(); + for (int warmup = 0; warmup < 200; warmup++) + operation(); + for (int sample = 0; sample < 5; sample++) + { + TimeSpan cpu = process.TotalProcessorTime; + int gen0 = GC.CollectionCount(0); + int gen1 = GC.CollectionCount(1); + int gen2 = GC.CollectionCount(2); + long before = GC.GetAllocatedBytesForCurrentThread(); + long start = Stopwatch.GetTimestamp(); + for (int iteration = 0; iteration < iterations; iteration++) + operation(); + double milliseconds = Stopwatch.GetElapsedTime(start).TotalMilliseconds; + long bytes = GC.GetAllocatedBytesForCurrentThread() - before; + Console.WriteLine(JsonSerializer.Serialize(new + { + workload = name, sample, iterations, milliseconds, bytes, + cpuMilliseconds = (process.TotalProcessorTime - cpu).TotalMilliseconds, + gen0 = GC.CollectionCount(0) - gen0, + gen1 = GC.CollectionCount(1) - gen1, + gen2 = GC.CollectionCount(2) - gen2 + })); + } + } +} diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Selection.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Selection.cs new file mode 100644 index 000000000..f48c05563 --- /dev/null +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Selection.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Inno.Core.Identity; +using Inno.Editor.Core; +using Inno.Editor.Interactions; +using Xunit; + +namespace Inno.Editor.Interactions.Tests; +public sealed partial class EditorRuntimeTests +{ + [Fact] + public void MissingIdentityRejectsTheEntireVisibleRangeWithoutChangingSelection() + { + var first = new SelectionQueueTarget(); + var last = new SelectionQueueTarget(); + Assert.True(m_identities.Register(first)); + Assert.True(m_identities.Register(last)); + m_runtime.interactions.SetSelection(first); + EditorSelectionSnapshot original = m_runtime.interactions.selection.Capture(); + RuntimeIdentity firstId = first.identity.runtimeIdentity!.Value; + RuntimeIdentity missing = new(firstId.domainId, int.MaxValue); + Assert.Throws(() => m_runtime.interactions.SelectRange( + [firstId, missing, last.identity.runtimeIdentity!.Value], last)); + Assert.Same(original, m_runtime.interactions.selection.Capture()); + Assert.Same(first, m_runtime.interactions.selection.selectedTarget); + } + + [Fact] + public void LargeIdentityRangesKeepOrderedPrimaryAndIndexedMembership() + { + var targets = new SelectionQueueTarget[10_000]; + var order = new RuntimeIdentity[targets.Length]; + for (int index = 0; index < targets.Length; index++) + { + targets[index] = new SelectionQueueTarget(); + Assert.True(m_identities.Register(targets[index])); + order[index] = targets[index].identity.runtimeIdentity!.Value; + } + m_runtime.interactions.SetSelection(targets[0]); + m_runtime.interactions.SelectRange(order, targets[^1]); + Assert.Equal(targets.Length, m_runtime.interactions.selection.count); + Assert.Same(targets[^1], m_runtime.interactions.selection.selectedTarget); + foreach (SelectionQueueTarget target in targets) + Assert.True(m_runtime.interactions.selection.Contains(target)); + m_runtime.interactions.SetSelection(targets[0]); + Assert.False(m_runtime.interactions.selection.Contains(targets[^1])); + } + + [Fact] + public void BoxedTargetsDoNotKeepReferencedObjectsAliveThroughSelectionFocusOrSnapshots() + { + WeakReference reference = SelectBoxedReferenceTarget(); + EditorSelectionSnapshot snapshot = m_runtime.interactions.selection.Capture(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(reference.IsAlive); + Assert.Null(m_runtime.interactions.selection.selectedTarget); + Assert.Null(m_runtime.interactions.focusedTarget); + Assert.Equal(0, snapshot.CopyTargets(m_runtime.interactions, new List())); + GC.KeepAlive(snapshot); + } + + [Fact] + public void NeutralScalarSelectionRemainsAvailableWithoutAnotherOwner() + { + Guid value = Guid.NewGuid(); + SelectScalarTarget(value); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.Equal(value, Assert.IsType(m_runtime.interactions.selection.selectedTarget)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void SelectScalarTarget(Guid value) => m_runtime.interactions.SetSelection(value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private WeakReference SelectBoxedReferenceTarget() + { + var payload = new object(); + object target = new KeyValuePair("payload", payload); + m_runtime.interactions.SetSelection(target); + m_runtime.interactions.For("tests/boxed-target", target).Focus(); + return new WeakReference(payload); + } + + [Fact] + public void RejectedDomainRetirementOnAnotherThreadPreservesTheRoutingLease() + { + var allocator = new IdentityAllocator(); + var target = new SelectionQueueTarget(); + Assert.True(allocator.Register(target)); + using IDisposable registration = m_runtime.interactions.RegisterIdentityDomain(allocator); + Exception? failure = null; + var worker = new System.Threading.Thread(() => failure = Record.Exception(registration.Dispose)); + worker.Start(); + Assert.True(worker.Join(TimeSpan.FromSeconds(5))); + Assert.IsType(failure); + Assert.True(m_runtime.interactions.TryResolveIdentity(target.identity.runtimeIdentity!.Value, out IdentityObject? resolved)); + Assert.Same(target, resolved); + } + + [Fact] + public void TemporaryIdentityDomainsResolveSelectionAndDragOnlyWhileTheirRoutingLeaseIsAlive() + { + var allocator = new IdentityAllocator(); + var target = new SelectionQueueTarget(); + Assert.True(allocator.Register(target)); + RuntimeIdentity identity = target.identity.runtimeIdentity!.Value; + Assert.False(m_runtime.interactions.TryResolveIdentity(identity, out _)); + IDisposable registration = m_runtime.interactions.RegisterIdentityDomain(allocator); + Assert.Throws(() => m_runtime.interactions.RegisterIdentityDomain(allocator)); + Assert.True(m_runtime.interactions.TryResolveIdentity(identity, out IdentityObject? resolved)); + Assert.Same(target, resolved); + m_runtime.interactions.SetSelection(target); + Assert.Same(target, m_runtime.interactions.selection.selectedTarget); + RuntimeIdentity drag = m_runtime.interactions.For("tests/temporary", target).BeginDrag(new EditorDragData(target, "temporary")); + Assert.True(m_runtime.interactions.TryGetDragSource(drag, out _)); + registration.Dispose(); + registration.Dispose(); + Assert.False(m_runtime.interactions.TryResolveIdentity(identity, out _)); + Assert.False(m_runtime.interactions.TryGetDragSource(drag, out _)); + Assert.Null(m_runtime.interactions.selection.selectedTarget); + Assert.Equal(1, m_runtime.interactions.selection.count); + Assert.NotNull(allocator.Get(identity)); + using IDisposable rejoined = m_runtime.interactions.RegisterIdentityDomain(allocator); + registration.Dispose(); + Assert.Same(target, m_runtime.interactions.selection.selectedTarget); + Assert.False(m_runtime.interactions.TryGetActiveDragIdentity(out _)); + } + + [Fact] + public void SelectionFinishesPendingEditsBeforeChangingAndPreservesStateWhenPreparationRejects() + { + var first = new SelectionQueueTarget(); + var second = new SelectionQueueTarget(); + Assert.True(m_identities.Register(first)); + Assert.True(m_identities.Register(second)); + m_runtime.interactions.SetSelection(first); + using var hub = m_runtime.interactions.events.CreateHub(); + using var gesture = new EditorEditGesture(m_runtime.interactions.history, "Pending Input"); + using (hub.Listen(_ => + { + Assert.Same(first, m_runtime.interactions.selection.selectedTarget); + gesture.Commit(); + })) + { + m_runtime.interactions.SetSelection(second); + } + Assert.False(gesture.isActive); + ulong revision = m_runtime.interactions.selection.version; + using var rejecting = hub.Listen(_ => throw new InvalidOperationException("Pending edit cannot finish.")); + Assert.Throws(() => m_runtime.interactions.SetSelection(first)); + Assert.Throws(() => m_runtime.interactions.SetSelection([first], first)); + Assert.Same(second, m_runtime.interactions.selection.selectedTarget); + Assert.Equal(revision, m_runtime.interactions.selection.version); + } + + [Fact] + public void OrderedSelectionDeduplicatesIdentitiesAndRetainsItsSnapshotUntilItChanges() + { + var first = new SelectionQueueTarget(); + var second = new SelectionQueueTarget(); + Assert.True(m_identities.Register(first)); + Assert.True(m_identities.Register(second)); + m_runtime.interactions.SetSelection([first, second, first], first); + Assert.Equal(2, m_runtime.interactions.selection.count); + Assert.Same(second, m_runtime.interactions.selection.selectedTarget); + Assert.Same(first, m_runtime.interactions.selection.anchor); + EditorSelectionSnapshot snapshot = m_runtime.interactions.selection.Capture(); + Assert.Equal(first.identity.runtimeIdentity, snapshot.identities[0]); + Assert.Equal(second.identity.runtimeIdentity, snapshot.primary); + ulong revision = m_runtime.interactions.selection.version; + m_runtime.interactions.SetSelection([first, second], first); + Assert.Equal(revision, m_runtime.interactions.selection.version); + Assert.Same(snapshot, m_runtime.interactions.selection.Capture()); + } + + [Fact] + public void RangeSelectionUsesVisibleOrderAndTogglePreservesTheAnchor() + { + var first = new SelectionQueueTarget(); + var second = new SelectionQueueTarget(); + var third = new SelectionQueueTarget(); + Assert.True(m_identities.Register(first)); + Assert.True(m_identities.Register(second)); + Assert.True(m_identities.Register(third)); + m_runtime.interactions.SetSelection(first); + m_runtime.interactions.SelectRange([first.identity.runtimeIdentity!.Value, second.identity.runtimeIdentity!.Value, third.identity.runtimeIdentity!.Value], third); + Assert.Equal(3, m_runtime.interactions.selection.count); + m_runtime.interactions.ToggleSelection(second); + Assert.False(m_runtime.interactions.selection.Contains(second)); + Assert.Same(first, m_runtime.interactions.selection.anchor); + Assert.Same(third, m_runtime.interactions.selection.selectedTarget); + m_runtime.interactions.SelectRange([third.identity.runtimeIdentity!.Value, second.identity.runtimeIdentity!.Value, first.identity.runtimeIdentity!.Value], second); + var targets = new List(); + Assert.Equal(2, m_runtime.interactions.selection.CopyTargets(targets)); + Assert.Equal([first, second], targets); + } + + [Fact] + public void SelectionNeverConflatesPersistentIdentitiesFromDifferentDomains() + { + Guid persistent = Guid.NewGuid(); + var first = new SelectionQueueTarget(); + var second = new SelectionQueueTarget(); + Assert.True(m_identities.Register(first, persistent)); + Assert.True(m_secondaryIdentities.Register(second, persistent)); + m_runtime.interactions.SetSelection([first, second], first); + Assert.Equal(2, m_runtime.interactions.selection.count); + Assert.True(m_runtime.interactions.selection.Contains(first)); + Assert.True(m_runtime.interactions.selection.Contains(second)); + Assert.NotEqual(first.identity.runtimeIdentity, second.identity.runtimeIdentity); + } + + [Fact] + public void RejectedSelectionDoesNotModifyItsPreviousMembershipOrRevision() + { + var registered = new SelectionQueueTarget(); + Assert.True(m_identities.Register(registered)); + m_runtime.interactions.SetSelection(registered); + ulong revision = m_runtime.interactions.selection.version; + var unregistered = new SelectionQueueTarget(); + Assert.Throws(() => m_runtime.interactions.SetSelection([registered, unregistered], registered)); + Assert.Equal(revision, m_runtime.interactions.selection.version); + Assert.Same(registered, m_runtime.interactions.selection.selectedTarget); + } + + [Fact] + public void QueuedActionsRejectRetiredTargetsInsteadOfExecutingTheirReplacement() + { + Guid persistent = Guid.NewGuid(); + var previous = new SelectionQueueTarget(); + Assert.True(m_identities.Register(previous, persistent)); + m_runtime.interactions.For("tests/identity-queue", previous).Enqueue("tests/identity-queue"); + Assert.True(m_identities.Unregister(previous)); + var replacement = new SelectionQueueTarget(); + Assert.True(m_identities.Register(replacement, persistent)); + m_runtime.Update(new EditorFrame()); + Assert.Equal(0, previous.executions); + Assert.Equal(0, replacement.executions); + m_runtime.interactions.For("tests/identity-queue", replacement).Enqueue("tests/identity-queue"); + m_runtime.Update(new EditorFrame()); + Assert.Equal(1, replacement.executions); + } + + [Fact] + public void QueuedSelectionAndFocusDoNotKeepRetiredIdentityObjectsAlive() + { + WeakReference reference = QueueAndRetireSelectionTarget(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(reference.IsAlive); + Assert.Null(m_runtime.interactions.selection.selectedTarget); + Assert.Null(m_runtime.interactions.focusedTarget); + m_runtime.Update(new EditorFrame()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private WeakReference QueueAndRetireSelectionTarget() + { + var target = new SelectionQueueTarget(); + Assert.True(m_identities.Register(target)); + m_runtime.interactions.SetSelection(target); + m_runtime.interactions.For("tests/identity-queue", target).Focus(); + m_runtime.interactions.For("tests/identity-queue", target).Enqueue("tests/identity-queue"); + Assert.True(m_identities.Unregister(target)); + return new WeakReference(target); + } + + public sealed class SelectionQueueTarget : IdentityObject + { + public int executions { get; set; } + } + + [EditorAction("tests/identity-queue")] + public sealed class IdentityQueuedAction : EditorAction + { + protected override void Execute(EditorActionContext context) => context.target.executions++; + } +} diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Statistics.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Statistics.cs new file mode 100644 index 000000000..e3ca8b7f4 --- /dev/null +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.Statistics.cs @@ -0,0 +1,58 @@ +using System.Linq; +using Inno.Editor.Core; +using Xunit; + +namespace Inno.Editor.Interactions.Tests; + +public sealed partial class EditorRuntimeTests +{ + [Fact] + public void UnchangedStatisticsReuseAnImmutableSnapshot() + { + var statistics = new EditorStatistics(); + statistics.Publish(new EditorStatistic(new("frame"), new("timing"), "Timing", "Frame", "16 ms")); + var first = statistics.GetSnapshot(); + Assert.Same(first, statistics.GetSnapshot()); + statistics.Publish(new EditorStatistic(new("frame"), new("timing"), "Timing", "Frame", "16 ms")); + Assert.Same(first, statistics.GetSnapshot()); + statistics.Publish(new EditorStatistic(new("frame"), new("timing"), "Timing", "Frame", "8 ms")); + var changed = statistics.GetSnapshot(); + Assert.NotSame(first, changed); + Assert.Equal("16 ms", Assert.Single(first).value); + Assert.Equal("8 ms", Assert.Single(changed).value); + } + + [Fact] + public void StatisticFrameHandoffRetainsLateProvidersAndRemovesExpiredContributions() + { + EditorStatistics statistics = m_runtime.context.statistics; + statistics.Publish(new EditorStatistic(new("first"), new("sampling"), "Sampling", "First", "1")); + var first = statistics.GetSnapshot(); + m_runtime.Update(new EditorFrame(0.016f, 1f, isFocused: true)); + Assert.Same(first, statistics.GetSnapshot()); + statistics.Publish(new EditorStatistic(new("second"), new("sampling"), "Sampling", "Second", "2")); + var combined = statistics.GetSnapshot(); + Assert.Equal(2, combined.Count(value => value.groupId == new EditorStatisticGroupId("sampling"))); + m_runtime.Update(new EditorFrame(0.016f, 2f, isFocused: true)); + Assert.Equal("second", Assert.Single(statistics.GetSnapshot().Where(value => value.groupId == new EditorStatisticGroupId("sampling"))).id.value); + Assert.Equal(2, combined.Count); + m_runtime.Update(new EditorFrame(0.016f, 3f, isFocused: true)); + Assert.DoesNotContain(statistics.GetSnapshot(), value => value.groupId == new EditorStatisticGroupId("sampling")); + } + + [Fact] + public void StatisticGroupingAndOrderChangesInvalidateTheSnapshot() + { + var statistics = new EditorStatistics(); + statistics.Publish(new EditorStatistic(new("a"), new("old"), "Old", "First", "1", groupOrder: 1)); + statistics.Publish(new EditorStatistic(new("b"), new("second"), "Second", "Second", "2", groupOrder: 2)); + var original = statistics.GetSnapshot(); + statistics.Publish(new EditorStatistic(new("b"), new("new"), "New", "Renamed", "2", groupOrder: 0)); + var changed = statistics.GetSnapshot(); + Assert.NotSame(original, changed); + Assert.Equal("b", changed[0].id.value); + Assert.Equal("New", changed[0].groupName); + Assert.Equal("Renamed", changed[0].label); + Assert.Equal("a", original[0].id.value); + } +} diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.cs index a7e755359..7ab18aa73 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.cs +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorRuntimeTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Reflection; - using Inno.Extensibility.Modules; using Inno.Extensibility.Reload; using Inno.Core.Diagnostics; @@ -23,13 +22,9 @@ using Xunit; namespace Inno.Editor.Interactions.Tests; - -public sealed class EditorRuntimeTests : IDisposable +public sealed partial class EditorRuntimeTests : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoEditorRuntimeTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoEditorRuntimeTests", Guid.NewGuid().ToString("N")); private readonly DiagnosticHub m_diagnostics = new(); private readonly LogRouter m_logs = new(); private readonly IdentityAllocator m_identities = new(); @@ -38,15 +33,12 @@ public sealed class EditorRuntimeTests : IDisposable private readonly ModuleHost m_modules; private readonly TypeCatalog m_types; private readonly EditorInteractionRuntime m_runtime; - public EditorRuntimeTests() { Directory.CreateDirectory(Path.Combine(m_projectRoot, "Assets")); m_diagnosticScope = m_diagnostics.EnterScope(); - m_modules = new ModuleHost(new ModuleHostOptions - { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorRuntimeTests).Assembly) }); + m_modules = new ModuleHost(new ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorRuntimeTests).Assembly) }); m_types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); - TestModule.startCount = 0; TestModule.stopCount = 0; TestModule.stateValue = 0; @@ -81,7 +73,6 @@ public EditorRuntimeTests() ZThrowingDisposeModule.disposeCount = 0; ZThrowingDisposeModule.throwOnDispose = false; ShutdownOrder.events.Clear(); - m_runtime = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super"))); m_runtime.Start(); } @@ -100,9 +91,7 @@ public void Dispose() private EditorInteractionRuntime CreateRuntime( EditorContext context, params object[] hostServices - ) - => new(context, m_types, m_logs, [m_types, m_identities, m_secondaryIdentities, .. hostServices]); - + ) => new(context, m_types, m_logs, [m_types, m_identities, m_secondaryIdentities, ..hostServices]); [Fact] public void FeatureLookupReturnsOnlyStartedNonQuarantinedModules() { @@ -125,9 +114,7 @@ public void HistoryAndExtensionStateContractsDoNotExposeStandaloneWorkspaceTypes Assert.Null(core.GetType("Inno.Editor.Core.EditorWorkspaceStateWriter")); Assert.Null(typeof(EditorHistoryChange).GetProperty("version")); ConstructorInfo constructor = Assert.Single(typeof(EditorHistoryHandlerAttribute).GetConstructors()); - Assert.Collection( - constructor.GetParameters(), - static parameter => Assert.Equal(typeof(string), parameter.ParameterType)); + Assert.Collection(constructor.GetParameters(), static parameter => Assert.Equal(typeof(string), parameter.ParameterType)); } [Fact] @@ -135,7 +122,6 @@ public void EditorDensityDefaultsToComfortableAndCanSwitchIdempotently() { var style = new EditorStyleMetrics(); float comfortablePadding = style.framePadding.Y; - Assert.False(style.isCompact); Assert.True(style.SetCompactMode(true)); Assert.True(style.isCompact); @@ -151,12 +137,8 @@ public void InteractionRuntimeInjectsOneStableAssignableHostService() { var service = new TestHostService(); HostServicePanel.current = null; - using EditorInteractionRuntime runtime = CreateRuntime( - new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), - service); - + using EditorInteractionRuntime runtime = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), service); runtime.Start(); - Assert.Contains(runtime.panels, static panel => panel.id == "tests.host-service"); Assert.Same(service, HostServicePanel.current); } @@ -166,9 +148,7 @@ public void InteractionRuntimeRejectsExtensionWhenHostServiceIsUnavailable() { HostServicePanel.current = null; using EditorInteractionRuntime runtime = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super"))); - runtime.Start(); - Assert.DoesNotContain(runtime.panels, static panel => panel.id == "tests.host-service"); Assert.Null(HostServicePanel.current); } @@ -185,8 +165,7 @@ public void InteractionIdentifiersArePlainValidatedStringsWithoutWrapperTypes() Assert.Equal(typeof(string), typeof(EditorInteractions).GetProperty("focusedArea")!.PropertyType); Assert.Equal(typeof(string), typeof(EditorInteraction).GetProperty("area")!.PropertyType); Assert.Equal(typeof(string), typeof(EditorPanelExtension).GetProperty("id")!.PropertyType); - MethodInfo forMethod = Assert.Single(typeof(EditorInteractions).GetMethods() - .Where(static method => method.Name == "For")); + MethodInfo forMethod = Assert.Single(typeof(EditorInteractions).GetMethods().Where(static method => method.Name == "For")); Assert.Equal(typeof(string), forMethod.GetParameters()[0].ParameterType); Assert.Throws(() => m_runtime.interactions.For(string.Empty)); Assert.Throws(() => m_runtime.interactions.TogglePanel(" ")); @@ -197,16 +176,13 @@ public void InteractionIdentifiersArePlainValidatedStringsWithoutWrapperTypes() [Fact] public void PanelOpenAndCloseOperationsAreIdempotentAndRequestFocus() { - EditorPanelExtension panel = Assert.Single( - m_runtime.panels.Where(static candidate => candidate.id == "tests.panel")); + EditorPanelExtension panel = Assert.Single(m_runtime.panels.Where(static candidate => candidate.id == "tests.panel")); Assert.False(panel.isOpen); - Assert.True(m_runtime.interactions.OpenPanel("tests.panel")); Assert.True(m_runtime.interactions.OpenPanel("tests.panel")); Assert.True(panel.isOpen); Assert.True(panel.TakeFocusRequest()); Assert.False(panel.TakeFocusRequest()); - Assert.True(m_runtime.interactions.ClosePanel("tests.panel")); Assert.True(m_runtime.interactions.ClosePanel("tests.panel")); Assert.False(panel.isOpen); @@ -221,11 +197,9 @@ public void DocumentHostOpensOneInstanceAndRequiresExplicitDirtyCloseDecision() var provider = new TestDocumentProvider(); using IDisposable lease = documents.RegisterProvider(provider); Guid assetId = Guid.NewGuid(); - EditorDocumentContext opened = documents.Open("./Assets/Hero.ispriteatlas2d", assetId); EditorDocumentContext focused = documents.Open("Assets/OtherName.ispriteatlas2d", assetId); documents.SetDirty(opened.documentId); - Assert.Same(opened, focused); Assert.Equal("Assets/OtherName.ispriteatlas2d", opened.assetPath); Assert.Equal("OtherName.ispriteatlas2d", opened.title); @@ -245,17 +219,13 @@ public void DocumentPathReassignmentRetiresOnlyCleanStaleOwner() var provider = new TestDocumentProvider(); using IDisposable lease = documents.RegisterProvider(provider); const string path = "Assets/Shared.ispriteatlas2d"; - EditorDocumentContext first = documents.Open(path, Guid.NewGuid()); EditorDocumentContext replacement = documents.Open(path, Guid.NewGuid()); - Assert.NotSame(first, replacement); Assert.Single(documents.documents); Assert.Equal(1, provider.closeCount); - documents.SetDirty(replacement.documentId); - InvalidOperationException failure = Assert.Throws( - () => documents.Open(path, Guid.NewGuid())); + InvalidOperationException failure = Assert.Throws(() => documents.Open(path, Guid.NewGuid())); Assert.Contains("unsaved changes", failure.Message, StringComparison.Ordinal); Assert.Same(replacement, Assert.Single(documents.documents)); } @@ -269,15 +239,11 @@ public void DocumentStateSurvivesProviderGenerationReplacement() EditorDocumentContext opened = documents.Open("Assets/World.itilemap2d", Guid.NewGuid()); opened.SetViewParameter("zoom", "2.5"); documents.SetDirty(opened.documentId); - firstLease.Dispose(); - Assert.False(opened.isProviderAvailable); Assert.False(documents.Save(opened.documentId)); - var replacement = new TestDocumentProvider(); using IDisposable replacementLease = documents.RegisterProvider(replacement); - Assert.True(opened.isProviderAvailable); Assert.Equal(1, replacement.openCount); EditorDocumentContext restored = Assert.Single(documents.documents); @@ -300,24 +266,11 @@ public void ViewportToolCapturesOnePointerAndCommitsOneGestureTransaction() new Inno.Core.Mathematics.Vector2(2f, 3f), coordinates.ScreenToWorld(new Inno.Core.Mathematics.Vector2(2f, 3f)), 0, - KeyModifier.None); - + KeyModifier.None + ); Assert.True(session.HandlePointer(down)); - Assert.False(session.HandlePointer(new EditorViewportPointerEvent( - 8, - EditorViewportPointerPhase.Move, - default, - default, - 0, - KeyModifier.None))); - Assert.True(session.HandlePointer(new EditorViewportPointerEvent( - 7, - EditorViewportPointerPhase.Up, - down.screenPosition, - down.worldPosition, - 0, - KeyModifier.None))); - + Assert.False(session.HandlePointer(new EditorViewportPointerEvent(8, EditorViewportPointerPhase.Move, default, default, 0, KeyModifier.None))); + Assert.True(session.HandlePointer(new EditorViewportPointerEvent(7, EditorViewportPointerPhase.Up, down.screenPosition, down.worldPosition, 0, KeyModifier.None))); Assert.Equal(1, tool.downCount); Assert.Equal(1, tool.upCount); Assert.True(history.canUndo); @@ -341,24 +294,11 @@ public void StatisticsUseStableReplacementAndOneFrameOrderHandoff() EditorStatistics statistics = m_runtime.context.statistics; var id = new EditorStatisticId("tests.statistics.frame-time"); var groupId = new EditorStatisticGroupId("tests.statistics"); - statistics.Publish(new EditorStatistic( - id, - groupId, - "Tests", - "Frame Time", - "10 ms")); - statistics.Publish(new EditorStatistic( - id, - groupId, - "Tests", - "Frame Time", - "8 ms")); - + statistics.Publish(new EditorStatistic(id, groupId, "Tests", "Frame Time", "10 ms")); + statistics.Publish(new EditorStatistic(id, groupId, "Tests", "Frame Time", "8 ms")); Assert.Equal("8 ms", Assert.Single(statistics.GetSnapshot()).value); - m_runtime.Update(new EditorFrame(0.016f, 0.016f, isFocused: true)); Assert.Equal("8 ms", Assert.Single(statistics.GetSnapshot()).value); - m_runtime.Update(new EditorFrame(0.016f, 0.032f, isFocused: true)); Assert.Empty(statistics.GetSnapshot()); } @@ -367,14 +307,10 @@ public void StatisticsUseStableReplacementAndOneFrameOrderHandoff() public void BlockingModuleDefersOnlyModulesOrderedAfterIt() { UpdateBarrierModule.block = true; - m_runtime.Update(new EditorFrame(0.016f, 0.016f, isFocused: true)); - Assert.Equal(0, FollowingUpdateModule.updateCount); UpdateBarrierModule.block = false; - m_runtime.Update(new EditorFrame(0.016f, 0.032f, isFocused: true)); - Assert.Equal(1, FollowingUpdateModule.updateCount); } @@ -382,10 +318,8 @@ public void BlockingModuleDefersOnlyModulesOrderedAfterIt() public void ThrowingModuleBarrierIsQuarantinedWithoutStoppingFollowingModules() { UpdateBarrierModule.throwWhenRead = true; - m_runtime.Update(new EditorFrame(0.016f, 0.016f, isFocused: true)); m_runtime.Update(new EditorFrame(0.016f, 0.032f, isFocused: true)); - Assert.Equal(1, UpdateBarrierModule.barrierReadCount); Assert.Equal(2, FollowingUpdateModule.updateCount); } @@ -394,14 +328,8 @@ public void ThrowingModuleBarrierIsQuarantinedWithoutStoppingFollowingModules() public void ActionResolutionPrefersExactAreaAndTarget() { var target = new DerivedTarget(); - - EditorActionState exact = m_runtime.interactions - .For("tests/special", target) - .Query("tests.resolve"); - EditorActionState fallback = m_runtime.interactions - .For("tests/other", target) - .Query("tests.resolve"); - + EditorActionState exact = m_runtime.interactions.For("tests/special", target).Query("tests.resolve"); + EditorActionState fallback = m_runtime.interactions.For("tests/other", target).Query("tests.resolve"); Assert.Equal("area", exact.displayName); Assert.Equal("base", fallback.displayName); } @@ -413,10 +341,7 @@ public void ActionTypeMetadataIsCapturedOnceAndNeverReadOnTheRoutingPath() MetadataAction.throwWhenRead = true; try { - EditorInteraction interaction = m_runtime.interactions.For( - "tests/action-metadata", - new BaseTarget()); - + EditorInteraction interaction = m_runtime.interactions.For("tests/action-metadata", new BaseTarget()); Assert.True(interaction.Query("tests.action-metadata").isEnabled); Assert.True(interaction.Execute("tests.action-metadata")); Assert.True(interaction.Query("tests.action-metadata").isEnabled); @@ -434,9 +359,7 @@ public void QueuedActionExecutesAtRuntimeSafePoint() { m_runtime.interactions.For("tests/other").Enqueue("tests.deferred"); Assert.Equal(0, DeferredAction.executeCount); - m_runtime.Update(new EditorFrame(0.016f, 1f, isFocused: true)); - Assert.Equal(1, DeferredAction.executeCount); } @@ -444,17 +367,13 @@ public void QueuedActionExecutesAtRuntimeSafePoint() public void BuiltInUndoAndRedoActionsExposeHistoryThroughMenusAndShortcuts() { NeutralHistoryHandler.value = 1; - m_runtime.interactions.history.RecordApplied( - "Change Test Value", - NeutralHistoryHandler.CreateChange(before: 0, after: 1)); + m_runtime.interactions.history.RecordApplied("Change Test Value", NeutralHistoryHandler.CreateChange(before: 0, after: 1)); EditorInteraction global = m_runtime.interactions.For("editor/global"); - EditorActionState undo = global.Query("editor/undo"); Assert.True(undo.isEnabled); Assert.Equal("Undo Change Test Value", undo.displayName); Assert.True(global.Execute("editor/undo")); Assert.Equal(0, NeutralHistoryHandler.value); - EditorActionState redo = global.Query("editor/redo"); Assert.True(redo.isEnabled); Assert.Equal("Redo Change Test Value", redo.displayName); @@ -466,13 +385,9 @@ public void BuiltInUndoAndRedoActionsExposeHistoryThroughMenusAndShortcuts() public void NeutralHistorySurvivesATypeCatalogGenerationChange() { NeutralHistoryHandler.value = 14; - m_runtime.interactions.history.RecordApplied( - "Change Neutral Value", - NeutralHistoryHandler.CreateChange(before: 3, after: 14)); - + m_runtime.interactions.history.RecordApplied("Change Neutral Value", NeutralHistoryHandler.CreateChange(before: 3, after: 14)); m_types.Rebuild(); _ = m_runtime.panelCount; - Assert.True(m_runtime.interactions.history.canUndo); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(3, NeutralHistoryHandler.value); @@ -483,12 +398,7 @@ public void NeutralHistorySurvivesATypeCatalogGenerationChange() [Fact] public void MissingHistoryHandlerCreatesAnExplicitBarrier() { - m_runtime.interactions.history.RecordApplied( - "Unavailable Change", - new EditorHistoryChange( - "tests/missing-history-handler", - EditorHistoryPayload.FromBytes([1, 2, 3]))); - + m_runtime.interactions.history.RecordApplied("Unavailable Change", new EditorHistoryChange("tests/missing-history-handler", EditorHistoryPayload.FromBytes([1, 2, 3]))); Assert.False(m_runtime.interactions.history.canUndo); Assert.Contains("tests/missing-history-handler", m_runtime.interactions.history.undoUnavailableReason); Assert.False(m_runtime.interactions.history.Undo().succeeded); @@ -502,12 +412,7 @@ public void LargeNeutralPayloadSpillsToTheBoundedSessionDiskStore() byte[] bytes = new byte[128 * 1024]; BitConverter.GetBytes(9).CopyTo(bytes, 0); BitConverter.GetBytes(32).CopyTo(bytes, sizeof(int)); - m_runtime.interactions.history.RecordApplied( - "Large Neutral Change", - new EditorHistoryChange( - NeutralHistoryHandler.KIND, - EditorHistoryPayload.FromBytes(bytes))); - + m_runtime.interactions.history.RecordApplied("Large Neutral Change", new EditorHistoryChange(NeutralHistoryHandler.KIND, EditorHistoryPayload.FromBytes(bytes))); Assert.Equal(0, m_runtime.interactions.history.residentBytes); Assert.Equal(bytes.LongLength, m_runtime.interactions.history.diskBytes); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -518,14 +423,11 @@ public void LargeNeutralPayloadSpillsToTheBoundedSessionDiskStore() public void AttributeMenusSupportArbitraryDepthAndDynamicEntries() { EditorMenuModel menu = m_runtime.interactions.For("tests/menu").BuildMenu(); - EditorMenuItem tools = Assert.Single(menu.items); Assert.Equal("Tools", tools.label); EditorMenuItem create = Assert.Single(tools.children); Assert.Equal("Create", create.label); - Assert.Equal( - ["Asset", "Libraries", "Generated"], - create.children.Select(static item => item.label)); + Assert.Equal(["Asset", "Libraries", "Generated"], create.children.Select(static item => item.label)); EditorMenuItem libraries = Assert.Single(create.children.Where(static item => item.label == "Libraries")); Assert.True(libraries.separatorBefore); Assert.False(tools.separatorBefore); @@ -537,47 +439,36 @@ public void AttributeMenusSupportArbitraryDepthAndDynamicEntries() public void ToolbarResolvesContextualIconTooltipStateAndDispatch() { EditorInteraction interaction = m_runtime.interactions.For("tests/toolbar"); - EditorToolbarItem initial = Assert.Single(interaction.BuildToolbar().items); Assert.Equal("tests.toolbar", initial.actionId); Assert.Equal(EditorToolbarIcon.Play, initial.icon); Assert.Equal("Start Runtime", initial.tooltip); Assert.False(initial.status.isChecked); - ToolbarAction.isChecked = true; EditorToolbarItem active = Assert.Single(interaction.BuildToolbar().items); Assert.Equal(EditorToolbarIcon.Stop, active.icon); Assert.Equal("Stop Runtime", active.tooltip); Assert.True(active.status.isChecked); - interaction.Enqueue(active.actionId); m_runtime.Update(new EditorFrame(0.016f, 1f, isFocused: true)); Assert.Equal(1, ToolbarAction.executeCount); } [Fact] - public void MainMenuPlacesGeneratedPanelTogglesUnderPanel() + public void WorkbenchPlacesPanelTogglesUnderWindow() { - EditorMenuModel menu = m_runtime.interactions - .For("editor/main-menu") - .BuildMenu(); - - EditorMenuItem panel = Assert.Single(menu.items.Where(static item => item.label == "Panel")); + EditorMenuModel menu = m_runtime.interactions.For("editor/main-menu").BuildMenu(); + EditorMenuItem panel = Assert.Single(menu.items.Where(static item => item.label == "Window")); EditorMenuItem testing = Assert.Single(panel.children.Where(static item => item.label == "Testing")); EditorMenuItem test = Assert.Single(testing.children.Where(static item => item.label == "Test")); Assert.False(test.status.isChecked); EditorMenuItem? view = menu.items.SingleOrDefault(static item => item.label == "View"); Assert.True(view is null || view.children.All(static item => item.label != "Test")); - Assert.True(m_runtime.interactions.TogglePanel("tests.panel")); - EditorMenuModel openMenu = m_runtime.interactions - .For("editor/main-menu") - .BuildMenu(); - EditorMenuItem openPanel = Assert.Single(openMenu.items.Where(static item => item.label == "Panel")); - EditorMenuItem openTesting = Assert.Single( - openPanel.children.Where(static item => item.label == "Testing")); - EditorMenuItem openTest = Assert.Single( - openTesting.children.Where(static item => item.label == "Test")); + EditorMenuModel openMenu = m_runtime.interactions.For("editor/main-menu").BuildMenu(); + EditorMenuItem openPanel = Assert.Single(openMenu.items.Where(static item => item.label == "Window")); + EditorMenuItem openTesting = Assert.Single(openPanel.children.Where(static item => item.label == "Testing")); + EditorMenuItem openTest = Assert.Single(openTesting.children.Where(static item => item.label == "Test")); Assert.True(openTest.status.isChecked); } @@ -585,25 +476,20 @@ public void MainMenuPlacesGeneratedPanelTogglesUnderPanel() public void EveryShortcutDeclaredByTheSameActionCanDispatch() { m_runtime.interactions.For("tests/multiple-shortcuts").Focus(); - m_runtime.HandleKeyPressed(new KeyPressedEvent(0, KeyCode.F3, KeyModifier.None)); m_runtime.HandleKeyPressed(new KeyPressedEvent(0, KeyCode.F4, KeyModifier.None)); - Assert.Equal(2, MultiShortcutAction.executeCount); } [Fact] public void ProductKeyboardPolicyControlsDiscoveryLabelsAndShortcutDispatch() { - using var runtime = CreateRuntime(new EditorContext(m_projectRoot, - new EditorKeyboardPolicy(KeyModifier.Super, "Cmd"))); + using var runtime = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(KeyModifier.Super, "Cmd"))); runtime.Start(); NeutralHistoryHandler.value = 1; - runtime.interactions.history.RecordApplied("Change Test Value", - NeutralHistoryHandler.CreateChange(before: 0, after: 1)); + runtime.interactions.history.RecordApplied("Change Test Value", NeutralHistoryHandler.CreateChange(before: 0, after: 1)); EditorInteraction global = runtime.interactions.For("editor/global"); global.Focus(); - Assert.True(global.TryGetShortcut("editor/undo", out HotKeyGesture gesture)); Assert.Equal(KeyModifier.Super, gesture.modifiers); Assert.Equal("Cmd+Z", gesture.ToString()); @@ -618,9 +504,7 @@ public void ProductKeyboardPolicyControlsDiscoveryLabelsAndShortcutDispatch() public void ActionExecutionFailureCancelsStateActivatedBeforeTheException() { EditorInteraction interaction = m_runtime.interactions.For("tests/throw-after-activate"); - Assert.False(interaction.Execute("tests.throw-after-activate")); - Assert.False(interaction.IsActive("tests.throw-after-activate")); Assert.Equal(1, ThrowAfterActivateAction.cancelCount); } @@ -630,20 +514,13 @@ public void ActionOwnsValidatedMultiFrameState() { var target = new InteractionTarget(); EditorInteraction interaction = m_runtime.interactions.For("tests/other", target); - Assert.True(interaction.Execute("tests.interaction")); Assert.True(interaction.IsActive("tests.interaction")); - - Assert.True(interaction.Present( - "tests.interaction", - new InteractionPresentation(string.Empty, submit: true))); + Assert.True(interaction.Present("tests.interaction", new InteractionPresentation(string.Empty, submit: true))); Assert.True(interaction.IsActive("tests.interaction")); Assert.Equal("A name is required.", target.validationMessage); Assert.Null(target.committedValue); - - Assert.True(interaction.Present( - "tests.interaction", - new InteractionPresentation("Renamed", submit: true))); + Assert.True(interaction.Present("tests.interaction", new InteractionPresentation("Renamed", submit: true))); Assert.Equal("Renamed", target.committedValue); Assert.False(interaction.IsActive("tests.interaction")); Assert.False(interaction.Present("tests.interaction")); @@ -656,9 +533,7 @@ public void ChangingSelectionFinishesThePreviousTargetsActivePresentation() EditorInteraction interaction = m_runtime.interactions.For("tests/other", target); Assert.True(interaction.Select()); Assert.True(interaction.Execute("tests.commit-on-presentation-lost")); - Assert.True(m_runtime.interactions.For("tests/other", new DerivedTarget()).Select()); - Assert.Equal("Committed on focus loss", target.committedValue); Assert.False(interaction.IsActive("tests.commit-on-presentation-lost")); } @@ -668,15 +543,12 @@ public void SelectionAndFocusUseTheLightweightAreaHandle() { var target = new DerivedTarget(); EditorInteraction interaction = m_runtime.interactions.For("tests/other", target); - interaction.Focus(); Assert.Equal("tests/other", m_runtime.interactions.focusedArea); Assert.Same(target, m_runtime.interactions.focusedTarget); - Assert.True(interaction.Select()); Assert.Same(target, m_runtime.interactions.selection.selectedTarget); Assert.True(interaction.isSelected); - Assert.True(m_runtime.interactions.For("tests/other").Select()); Assert.Null(m_runtime.interactions.selection.selectedTarget); Assert.Null(typeof(EditorSelectionState).GetMethod("Select")); @@ -691,30 +563,19 @@ public void IdentityResolutionUsesTheOwningDomain() Assert.True(m_secondaryIdentities.Register(secondary)); RuntimeIdentity primaryIdentity = primary.identity.runtimeIdentity!.Value; RuntimeIdentity secondaryIdentity = secondary.identity.runtimeIdentity!.Value; - Assert.True(m_runtime.interactions.TryResolveIdentity(primaryIdentity, out IdentityObject? primaryResult)); Assert.Same(primary, primaryResult); Assert.True(m_runtime.interactions.TryResolveIdentity(secondaryIdentity, out IdentityObject? secondaryResult)); Assert.Same(secondary, secondaryResult); - Assert.True(m_runtime.interactions.TryResolveIdentity( - secondaryIdentity.domainId, - secondary.identity.persistentId, - out IdentityObject? persistentResult)); + Assert.True(m_runtime.interactions.TryResolveIdentity(secondaryIdentity.domainId, secondary.identity.persistentId, out IdentityObject? persistentResult)); Assert.Same(secondary, persistentResult); - var crossDomainAlias = new RuntimeIdentity(secondaryIdentity.domainId, primaryIdentity.runtimeId); - Assert.NotSame(primary, m_runtime.interactions.TryResolveIdentity(crossDomainAlias, out IdentityObject? alias) - ? alias - : null); - + Assert.NotSame(primary, m_runtime.interactions.TryResolveIdentity(crossDomainAlias, out IdentityObject? alias) ? alias : null); Guid persistentId = secondary.identity.persistentId; Assert.True(m_secondaryIdentities.Unregister(secondary)); var replacement = new DragSource(); Assert.True(m_secondaryIdentities.Register(replacement, persistentId)); - Assert.True(m_runtime.interactions.TryResolveIdentity( - secondaryIdentity.domainId, - persistentId, - out IdentityObject? replacementResult)); + Assert.True(m_runtime.interactions.TryResolveIdentity(secondaryIdentity.domainId, persistentId, out IdentityObject? replacementResult)); Assert.Same(replacement, replacementResult); } @@ -725,14 +586,9 @@ public void TypedDropRoutesAndCancelsItsManagedSession() DragSource source = Register(new DragSource()); var target = new DropTarget(); var data = new EditorDragData(source, "source"); - RuntimeIdentity supersededIdentity = m_runtime.interactions - .For("tests/other", supersededSource) - .BeginDrag(new EditorDragData(supersededSource, "superseded")); - RuntimeIdentity identity = m_runtime.interactions - .For("tests/other", source) - .BeginDrag(data); + RuntimeIdentity supersededIdentity = m_runtime.interactions.For("tests/other", supersededSource).BeginDrag(new EditorDragData(supersededSource, "superseded")); + RuntimeIdentity identity = m_runtime.interactions.For("tests/other", source).BeginDrag(data); EditorInteraction dropTarget = m_runtime.interactions.For("tests/drop", target); - Assert.NotEqual(supersededIdentity, identity); Assert.False(m_runtime.interactions.TryGetDragSource(supersededIdentity, out _)); Assert.True(dropTarget.QueryDrop(identity, EditorDropPlacement.Into).canDrop); @@ -747,10 +603,8 @@ public void TypedDropRoutesAndCancelsItsManagedSession() public void UnregisteredDragSourceCancelsTheManagedSession() { DragSource source = Register(new DragSource()); - RuntimeIdentity identity = m_runtime.interactions.For("tests/drag", source).BeginDrag( - new EditorDragData(source, "source")); + RuntimeIdentity identity = m_runtime.interactions.For("tests/drag", source).BeginDrag(new EditorDragData(source, "source")); Assert.True(m_identities.Unregister(source)); - Assert.False(m_runtime.interactions.TryGetDragSource(identity, out _)); Assert.False(m_runtime.interactions.TryGetDragSource(identity, out _)); } @@ -766,9 +620,7 @@ private TIdentity Register(TIdentity value) public void ThrowingPanelPresentationPropertyQuarantinesOnlyThatPanel() { TestPanel.throwFromWindowPadding = true; - EditorPanelExtension panel = Assert.Single( - m_runtime.panels.Where(static value => value.id == "tests.panel")); - + EditorPanelExtension panel = Assert.Single(m_runtime.panels.Where(static value => value.id == "tests.panel")); Assert.False(panel.TryGetWindowPresentation(out _, out _, out _)); Assert.False(panel.isOpen); } @@ -777,17 +629,13 @@ public void ThrowingPanelPresentationPropertyQuarantinesOnlyThatPanel() public void ShutdownDetachesPanelsBeforeStoppingModulesThenDisposesInteractionsAndExtensions() { ZThrowingDisposeModule.throwOnDispose = true; - AggregateException failure = Assert.Throws(m_runtime.Dispose); Assert.Contains("Injected extension disposal failure", failure.ToString()); Assert.Equal(GenerationState.Faulted, m_modules.generations.state); Assert.Throws(() => m_modules.generations.EnsureReady("reload after failed extension retirement")); - Assert.True(ADisposeObserverModule.historyAvailableDuringStop); Assert.True(ADisposeObserverModule.historyDisposedDuringDispose); - Assert.True( - ShutdownOrder.events.IndexOf("panel.detach") < - ShutdownOrder.events.IndexOf("module.stop")); + Assert.True(ShutdownOrder.events.IndexOf("panel.detach") < ShutdownOrder.events.IndexOf("module.stop")); Assert.Equal(1, ADisposeObserverModule.disposeCount); Assert.Equal(1, ZThrowingDisposeModule.disposeCount); } @@ -805,7 +653,10 @@ public void FailedModuleStartIsStoppedBeforeCandidateDisposal() Assert.Equal(stops + 1, TestModule.stopCount); Assert.Equal(GenerationState.Ready, m_modules.generations.state); } - finally { TestModule.throwOnStart = false; } + finally + { + TestModule.throwOnStart = false; + } } [Fact] @@ -824,7 +675,10 @@ public void FailedPanelAttachmentIsCompensatedBeforeItIsQuarantined() runtime.Dispose(); Assert.Equal(detaches + 1, TestPanel.detachCount); } - finally { TestPanel.throwOnAttach = false; } + finally + { + TestPanel.throwOnAttach = false; + } } [Theory] @@ -864,8 +718,10 @@ public void PendingPanelAndModuleDrainBeforeHistoryAndExtensionResourcesAreDispo Assert.True(ADisposeObserverModule.historyDisposedDuringDispose); Assert.Equal(1, ADisposeObserverModule.disposeCount); Assert.Equal(GenerationState.Ready, m_modules.generations.state); - Assert.Equal(new[] { "panel.detach", "panel.detach", "panel.detach", "module.stop", "module.stop", "module.stop" }, - ShutdownOrder.events.Where(static value => value is "panel.detach" or "module.stop")); + Assert.Equal( + new[] { "panel.detach", "panel.detach", "panel.detach", "module.stop", "module.stop", "module.stop" }, + ShutdownOrder.events.Where(static value => value is "panel.detach" or "module.stop") + ); } [Fact] @@ -873,10 +729,8 @@ public void RebuildRetainsHostExtensionsInsteadOfRestartingModules() { int starts = TestModule.startCount; int attaches = TestPanel.attachCount; - m_types.Rebuild(); _ = m_runtime.panelCount; - Assert.Equal(starts, TestModule.startCount); Assert.Equal(attaches, TestPanel.attachCount); } @@ -885,24 +739,19 @@ public void RebuildRetainsHostExtensionsInsteadOfRestartingModules() public void ModuleStateAndPanelVisibilityRestoreForTheSameProject() { TestModule.stateValue = 42; - EditorPanelExtension panel = Assert.Single( - m_runtime.panels.Where(static value => value.id == "tests.panel")); + EditorPanelExtension panel = Assert.Single(m_runtime.panels.Where(static value => value.id == "tests.panel")); panel.isOpen = true; m_runtime.Update(new EditorFrame(0.016f, 3f, isFocused: true)); m_runtime.Dispose(); - string settingsPath = Path.Combine(m_projectRoot, "editor.ini"); Assert.True(File.Exists(settingsPath)); string document = File.ReadAllText(settingsPath); Assert.Contains("[InnoEditor][Module.tests.state]", document); Assert.DoesNotContain("[InnoEditor][Module.tests.update-barrier]", document); - using EditorInteractionRuntime restored = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super"))); restored.Start(); - Assert.Equal(42, TestModule.restoredStateValue); - Assert.True(Assert.Single( - restored.panels.Where(static value => value.id == "tests.panel")).isOpen); + Assert.True(Assert.Single(restored.panels.Where(static value => value.id == "tests.panel")).isOpen); } [Fact] @@ -910,16 +759,11 @@ public void MalformedModuleStateValueUsesTheExtensionFallback() { m_runtime.Dispose(); var context = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); - context.SetLayoutSection("Module.tests.state", new Dictionary - { - ["value"] = "not-json" - }); + context.SetLayoutSection("Module.tests.state", new Dictionary { ["value"] = "not-json" }); context.SaveLayout(); TestModule.restoredStateValue = -1; - using EditorInteractionRuntime restored = CreateRuntime(context); restored.Start(); - Assert.Equal(0, TestModule.restoredStateValue); } @@ -932,17 +776,11 @@ public void ExtensionStateCaptureFailure_PublishesCurrentDiagnosticUntilRetrySuc { TestModule.captureFailure = true; m_runtime.Update(new EditorFrame(0.016f, 3f, isFocused: true)); - - DiagnosticReport report = Assert.Single(sink.reports.Values.Where(static value => - value.source.displayName == "Editor State Capture")); + DiagnosticReport report = Assert.Single(sink.reports.Values.Where(static value => value.source.displayName == "Editor State Capture")); Assert.Equal("EDITOR-STATE-CAPTURE", Assert.Single(report.diagnostics).code); - TestModule.captureFailure = false; m_runtime.Update(new EditorFrame(0.016f, 6f, isFocused: true)); - - Assert.DoesNotContain( - sink.reports.Values, - static value => value.source.displayName == "Editor State Capture"); + Assert.DoesNotContain(sink.reports.Values, static value => value.source.displayName == "Editor State Capture"); } finally { @@ -957,18 +795,11 @@ public void UnifiedEditorIniPreservesLayoutAndExtensionStateSectionsTogether() const string layout = "[Window][Hierarchy]\nPos=10,20\nSize=300,400"; var context = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); context.SetImGuiLayout(layout); - context.SetLayoutSection("Module.tests", new Dictionary - { - ["openScenes"] = "[\"Scenes/Test.iscene\"]" - }); - + context.SetLayoutSection("Module.tests", new Dictionary { ["openScenes"] = "[\"Scenes/Test.iscene\"]" }); Assert.True(context.SaveLayoutIfChanged()); var restored = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); - Assert.Equal(layout, restored.imguiLayout); - Assert.True(restored.TryGetLayoutSection( - "Module.tests", - out IReadOnlyDictionary values)); + Assert.True(restored.TryGetLayoutSection("Module.tests", out IReadOnlyDictionary values)); Assert.Equal("[\"Scenes/Test.iscene\"]", values["openScenes"]); string document = File.ReadAllText(restored.layoutPath); Assert.Contains("[Window][Hierarchy]", document); @@ -982,22 +813,17 @@ public void StartupRegistryRefreshCannotOverwriteModuleStateBeforeRestore() { m_runtime.Dispose(); var context = new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")); - context.SetLayoutSection("Module.tests.state", new Dictionary - { - ["value"] = "91" - }); + context.SetLayoutSection("Module.tests.state", new Dictionary { ["value"] = "91" }); context.SaveLayout(); TestModule.startCount = 0; TestModule.restoredStateValue = 0; TestModule.rebuildDuringRestore = true; - using EditorInteractionRuntime restored = CreateRuntime(new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super"))); restored.Start(); Assert.Equal(91, TestModule.restoredStateValue); TestModule.stateValue = TestModule.restoredStateValue; restored.Update(new EditorFrame(0.016f, 0.016f, isFocused: true)); restored.SaveState(); - string document = File.ReadAllText(Path.Combine(m_projectRoot, "editor.ini")); Assert.Contains("[InnoEditor][Module.tests.state]", document); Assert.Contains("value=91", document); @@ -1008,19 +834,14 @@ private sealed class TestDiagnosticSink : IDiagnosticSink { internal Dictionary reports { get; } = new(StringComparer.Ordinal); - public void Replace(DiagnosticReport report) - => reports[report.source.id] = report; - - public void Clear(DiagnosticSource source) - => reports.Remove(source.id); + public void Replace(DiagnosticReport report) => reports[report.source.id] = report; + public void Clear(DiagnosticSource source) => reports.Remove(source.id); } - } public class BaseTarget; public sealed class DerivedTarget : BaseTarget; public sealed class DragSource : IdentityObject; - public sealed class DropTarget { public bool wasDropped { get; set; } @@ -1037,14 +858,11 @@ public sealed record InteractionPresentation( bool submit, bool cancel = false ); - [EditorHistoryHandler(NeutralHistoryHandler.KIND)] public sealed class NeutralHistoryHandler : EditorHistoryHandler { public const string KIND = "tests/neutral-value"; - public static int value; - public static EditorHistoryChange CreateChange( int before, int after @@ -1059,47 +877,36 @@ protected override EditorHistoryAvailability Query( EditorHistoryContext context, EditorHistoryChange change, EditorHistoryDirection direction - ) - => change.payload.length >= sizeof(int) * 2 - ? EditorHistoryAvailability.Available() - : EditorHistoryAvailability.Unavailable("The neutral value payload is truncated."); - + ) => change.payload.length >= sizeof(int) * 2 ? EditorHistoryAvailability.Available() : EditorHistoryAvailability.Unavailable("The neutral value payload is truncated."); protected override EditorHistoryResult Apply( EditorHistoryContext context, EditorHistoryChange change, EditorHistoryDirection direction ) { byte[] bytes = change.payload.ReadBytes(); - value = BitConverter.ToInt32( - bytes, - direction == EditorHistoryDirection.Undo ? 0 : sizeof(int)); + value = BitConverter.ToInt32(bytes, direction == EditorHistoryDirection.Undo ? 0 : sizeof(int)); return EditorHistoryResult.Success(); } } public interface ITestHostService; - public sealed class TestHostService : ITestHostService; - [EditorPanel("tests.host-service", "Host Service")] public sealed class HostServicePanel : EditorPanel { public static ITestHostService? current; - public HostServicePanel(ITestHostService service) { current = service; } - protected override void OnDraw(EditorContext context) - => _ = context; + protected override void OnDraw(EditorContext context) => _ = context; } [EditorModule("tests.state")] public sealed class TestModule : EditorModule { private readonly TypeCatalog m_types; - public static int startCount; public static int stopCount; public static int stateValue; @@ -1109,7 +916,6 @@ public sealed class TestModule : EditorModule public static bool throwOnStart; public static bool throwOnStop; public static int pendingStops; - public TestModule(TypeCatalog types) { m_types = types; @@ -1157,15 +963,12 @@ public sealed class UpdateBarrierModule : EditorModule public static bool block; public static bool throwWhenRead; public static int barrierReadCount; - public override bool blocksFollowingUpdates { get { barrierReadCount++; - return throwWhenRead - ? throw new InvalidOperationException("Injected module barrier failure.") - : block; + return throwWhenRead ? throw new InvalidOperationException("Injected module barrier failure.") : block; } } } @@ -1174,7 +977,6 @@ public override bool blocksFollowingUpdates public sealed class FollowingUpdateModule : EditorModule { public static int updateCount; - protected override void OnUpdate(EditorContext context) => updateCount++; } @@ -1188,10 +990,7 @@ public sealed class TestPanel(TestModule module) : EditorPanel public static bool throwOnAttach; public static bool throwOnDetach; public static int pendingDetaches; - - public override bool useWindowPadding => throwFromWindowPadding - ? throw new InvalidOperationException("Injected panel presentation failure.") - : true; + public override bool useWindowPadding => throwFromWindowPadding ? throw new InvalidOperationException("Injected panel presentation failure.") : true; protected override void OnAttach(EditorContext context) { @@ -1231,23 +1030,19 @@ public sealed class MultiPresentationSetting : ISerializable [ProjectSettingPath("Project/Tests/Multi/Primary")] public sealed class PrimaryMultiPresentationEditor : ProjectSettingEditor { - protected override void OnDraw(MultiPresentationSetting setting) - => _ = setting; + protected override void OnDraw(MultiPresentationSetting setting) => _ = setting; } [ProjectSettingPath("Project/Tests/Multi/Secondary")] public sealed class SecondaryMultiPresentationEditor : ProjectSettingEditor { - protected override void OnDraw(MultiPresentationSetting setting) - => _ = setting; + protected override void OnDraw(MultiPresentationSetting setting) => _ = setting; } [EditorAction("tests.resolve")] public sealed class BaseResolveAction : EditorAction { - protected override EditorActionState Query(EditorActionContext context) - => new(true, true, displayName: "base"); - + protected override EditorActionState Query(EditorActionContext context) => new(true, true, displayName: "base"); protected override void Execute(EditorActionContext context) { } @@ -1256,9 +1051,7 @@ protected override void Execute(EditorActionContext context) [EditorAction("tests.resolve", "tests/special", priority: 100)] public sealed class AreaResolveAction : EditorAction { - protected override EditorActionState Query(EditorActionContext context) - => new(true, true, displayName: "area"); - + protected override EditorActionState Query(EditorActionContext context) => new(true, true, displayName: "area"); protected override void Execute(EditorActionContext context) { } @@ -1268,7 +1061,6 @@ protected override void Execute(EditorActionContext context) public sealed class DeferredAction : EditorAction { public static int executeCount; - protected override void Execute(EditorActionContext context) => executeCount++; } @@ -1277,15 +1069,12 @@ public sealed class MetadataAction : EditorAction { public static int targetTypeReadCount; public static bool throwWhenRead; - public override Type? targetType { get { targetTypeReadCount++; - return throwWhenRead - ? throw new InvalidOperationException("Action metadata was read at runtime.") - : typeof(BaseTarget); + return throwWhenRead ? throw new InvalidOperationException("Action metadata was read at runtime.") : typeof(BaseTarget); } } @@ -1300,24 +1089,16 @@ protected override void Execute(EditorActionContext context) public sealed class MultiShortcutAction : EditorAction { public static int executeCount; - protected override void Execute(EditorActionContext context) => executeCount++; } [EditorAction("tests.toolbar", "tests/toolbar")] -[EditorToolbarItem( - "tests/toolbar", - EditorToolbarIcon.Play, - "Start Runtime", - activeIcon: EditorToolbarIcon.Stop)] +[EditorToolbarItem("tests/toolbar", EditorToolbarIcon.Play, "Start Runtime", activeIcon: EditorToolbarIcon.Stop)] public sealed class ToolbarAction : EditorAction { public static bool isChecked; public static int executeCount; - - protected override EditorActionState Query(EditorActionContext context) - => new(true, true, isChecked, isChecked ? "Stop Runtime" : "Start Runtime"); - + protected override EditorActionState Query(EditorActionContext context) => new(true, true, isChecked, isChecked ? "Stop Runtime" : "Start Runtime"); protected override void Execute(EditorActionContext context) => executeCount++; } @@ -1325,7 +1106,6 @@ protected override EditorActionState Query(EditorActionContext context) public sealed class ThrowAfterActivateAction : EditorAction { public static int cancelCount; - protected override void Execute(EditorActionContext context) { Activate(context); @@ -1341,7 +1121,6 @@ public sealed class ADisposeObserverModule(EditorInteractions interactions) : Ed public static bool historyAvailableDuringStop; public static bool historyDisposedDuringDispose; public static int disposeCount; - protected override void OnStop(EditorContext context) { if (interactions.history.isFaulted) @@ -1369,7 +1148,6 @@ public sealed class ZThrowingDisposeModule : EditorModule { public static bool throwOnDispose; public static int disposeCount; - protected override void OnDispose() { disposeCount++; @@ -1384,11 +1162,9 @@ internal static class ShutdownOrder } [EditorAction("tests.interaction")] -public sealed class InteractionAction : - EditorPresentationAction +public sealed class InteractionAction : EditorPresentationAction { private string m_value = string.Empty; - protected override void Execute(EditorActionContext context) { m_value = "Initial"; @@ -1396,8 +1172,7 @@ protected override void Execute(EditorActionContext context) Activate(context); } - protected override bool Present( - EditorActionContext context) + protected override bool Present(EditorActionContext context) { InteractionPresentation presentation = context.argument; if (presentation.cancel) @@ -1428,7 +1203,6 @@ protected override bool Present( public sealed class CommitOnPresentationLostAction : EditorAction { private InteractionTarget? m_target; - protected override void Execute(EditorActionContext context) { m_target = context.target; @@ -1471,9 +1245,7 @@ EditorMenuBuilder builder [EditorDrop("tests/drop")] public sealed class TestDrop : EditorDrop { - protected override EditorDropStatus Query(EditorDropContext context) - => EditorDropStatus.Accept(); - + protected override EditorDropStatus Query(EditorDropContext context) => EditorDropStatus.Accept(); protected override EditorDropResult Drop(EditorDropContext context) { context.target.wasDropped = true; @@ -1486,15 +1258,17 @@ public sealed class TestDocumentProvider : EditorDocumentProvider public int openCount { get; private set; } public int saveCount { get; private set; } public int closeCount { get; private set; } - public override string id => "tests.documents"; - public override bool CanOpen(string assetPath) - => assetPath.EndsWith(".ispriteatlas2d", StringComparison.Ordinal) - || assetPath.EndsWith(".itilemap2d", StringComparison.Ordinal); - + public override bool CanOpen(string assetPath) => assetPath.EndsWith( + ".ispriteatlas2d", + StringComparison.Ordinal + ) + || assetPath.EndsWith( + ".itilemap2d", + StringComparison.Ordinal + ); public override void Open(EditorDocumentContext context) => openCount++; - public override bool Save(EditorDocumentContext context) { saveCount++; @@ -1506,20 +1280,15 @@ public override bool Save(EditorDocumentContext context) public sealed class TestViewportCoordinates : IEditorViewportCoordinateConverter { - public Inno.Core.Mathematics.Vector2 ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition) - => screenPosition * 2f; - - public Inno.Core.Mathematics.Vector2 WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition) - => worldPosition / 2f; + public Inno.Core.Mathematics.Vector2 ScreenToWorld(Inno.Core.Mathematics.Vector2 screenPosition) => screenPosition * 2f; + public Inno.Core.Mathematics.Vector2 WorldToScreen(Inno.Core.Mathematics.Vector2 worldPosition) => worldPosition / 2f; } public sealed class TestViewportTool(IEditorHistory history) : EditorViewportTool { public int downCount { get; private set; } public int upCount { get; private set; } - public override string id => "tests.viewport.paint"; - public override EditorViewportCursor cursor => EditorViewportCursor.Crosshair; public override void OnPointerDown( @@ -1530,9 +1299,7 @@ EditorViewportPointerEvent pointer context.CapturePointer(pointer.pointerId); context.BeginHistoryGesture("Paint Tile"); NeutralHistoryHandler.value = 1; - history.RecordApplied( - "Paint Tile Cell", - NeutralHistoryHandler.CreateChange(before: 0, after: 1)); + history.RecordApplied("Paint Tile Cell", NeutralHistoryHandler.CreateChange(before: 0, after: 1)); } public override void OnPointerUp( diff --git a/tests/editor/Inno.Editor.Interactions.Tests/EditorSettingsTests.cs b/tests/editor/Inno.Editor.Interactions.Tests/EditorSettingsTests.cs index 1dfa0a061..8de8e5c4e 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/EditorSettingsTests.cs +++ b/tests/editor/Inno.Editor.Interactions.Tests/EditorSettingsTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; - using Inno.Extensibility.Modules; using Inno.Extensibility.Types; using Inno.Core.Diagnostics; @@ -18,13 +17,9 @@ using Xunit; namespace Inno.Editor.Interactions.Tests; - public sealed class EditorSettingsTests : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoEditorSettingsTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoEditorSettingsTests", Guid.NewGuid().ToString("N")); private readonly EditorInteractionRuntime m_runtime; private readonly EditorSettings m_settings; private readonly IEditorConsole m_console; @@ -34,12 +29,10 @@ public sealed class EditorSettingsTests : IDisposable private readonly DiagnosticHub m_diagnostics = new(); private readonly FakePlayMode m_playMode = new(); private readonly LogRouter m_logs = new(); - public EditorSettingsTests() { Directory.CreateDirectory(m_projectRoot); - m_modules = new ModuleHost(new ModuleHostOptions - { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorSettingsTests).Assembly) }); + m_modules = new ModuleHost(new ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorSettingsTests).Assembly) }); _ = typeof(EditorSettings); _ = System.Reflection.Assembly.Load("Inno.Editor.Panel.Logging"); m_types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); @@ -50,12 +43,11 @@ public EditorSettingsTests() new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_types, m_logs, - [m_types, m_serialization, m_diagnostics, m_playMode]); + [m_types, m_serialization, m_diagnostics, m_playMode] + ); m_runtime.Start(); - m_settings = SettingsCaptureModule.current - ?? throw new InvalidOperationException("The Settings module was not discovered."); - m_console = SettingsCaptureModule.console - ?? throw new InvalidOperationException("The Console module was not discovered."); + m_settings = SettingsCaptureModule.current ?? throw new InvalidOperationException("The Settings module was not discovered."); + m_console = SettingsCaptureModule.console ?? throw new InvalidOperationException("The Console module was not discovered."); } public void Dispose() @@ -76,20 +68,14 @@ public void ApplyPersistsObjectsAtTheProjectRootAndPublishesOnlyTheService() { int changedCount = 0; EditorSettings? published = null; - m_settings.changed += settings => + using IDisposable subscription = m_settings.ListenChanges(settings => { changedCount++; published = settings; - }; + }); var value = new EditorSettingObject(); value.SetAsInt32("value", 10); - - Assert.True(m_settings.Apply( - new Dictionary(StringComparer.Ordinal) - { - ["Editor/Tests/Values/Project Count"] = value - })); - + Assert.True(m_settings.Apply(new Dictionary(StringComparer.Ordinal) { ["Editor/Tests/Values/Project Count"] = value })); string path = Path.Combine(m_projectRoot, "Settings.Editor.inno"); Assert.True(File.Exists(path)); Assert.False(Directory.Exists(Path.Combine(m_projectRoot, "Settings"))); @@ -102,16 +88,11 @@ public void ApplyPersistsObjectsAtTheProjectRootAndPublishesOnlyTheService() public void ChangedSubscriberFailureDoesNotChangeHistoryResultOrSkipLaterSubscribers() { int delivered = 0; - m_settings.changed += _ => throw new InvalidOperationException("subscriber"); - m_settings.changed += _ => delivered++; + using IDisposable failed = m_settings.ListenChanges(_ => throw new InvalidOperationException("subscriber")); + using IDisposable deliveredSubscription = m_settings.ListenChanges(_ => delivered++); var value = new EditorSettingObject(); value.SetAsInt32("value", 11); - - Assert.True(m_settings.Apply( - new Dictionary(StringComparer.Ordinal) - { - ["Editor/Tests/Values/Project Count"] = value - })); + Assert.True(m_settings.Apply(new Dictionary(StringComparer.Ordinal) { ["Editor/Tests/Values/Project Count"] = value })); Assert.Equal(1, delivered); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(2, delivered); @@ -126,7 +107,6 @@ public void GetReturnsIsolatedObjectsAndRejectsPagesOrMissingPaths() { EditorSettingObject first = m_settings.Get("Editor/Tests/Values/Project Count"); first.SetAsInt32("value", 99); - Assert.Equal(3, m_settings.Get("Editor/Tests/Values/Project Count").GetAsInt32("value")); Assert.Throws(() => m_settings.Get("Editor/Tests")); Assert.Throws(() => m_settings.Get("Editor/Tests/Missing")); @@ -135,25 +115,12 @@ public void GetReturnsIsolatedObjectsAndRejectsPagesOrMissingPaths() [Fact] public void ResetRemovesThePathOverrideAndRestoresTheDefinitionObject() { - EditorSetting definition = Assert.Single( - m_settings.definitions, - static setting => setting.path == "Editor/Tests/Values/Project Count"); + EditorSetting definition = Assert.Single(m_settings.definitions, static setting => setting.path == "Editor/Tests/Values/Project Count"); var value = new EditorSettingObject(); value.SetAsInt32("value", 8); - Assert.True(m_settings.Apply( - new Dictionary - { - ["Editor/Tests/Values/Project Count"] = value - })); + Assert.True(m_settings.Apply(new Dictionary { ["Editor/Tests/Values/Project Count"] = value })); Assert.False(definition.IsDefault(m_settings.Get("Editor/Tests/Values/Project Count"))); - - Assert.True(m_settings.Apply( - new Dictionary(), - new HashSet(StringComparer.Ordinal) - { - "Editor/Tests/Values/Project Count" - })); - + Assert.True(m_settings.Apply(new Dictionary(), new HashSet(StringComparer.Ordinal) { "Editor/Tests/Values/Project Count" })); Assert.Equal(3, m_settings.Get("Editor/Tests/Values/Project Count").GetAsInt32("value")); Assert.True(definition.IsDefault(m_settings.Get("Editor/Tests/Values/Project Count"))); Assert.NotSame(definition.defaultValue, definition.defaultValue); @@ -167,13 +134,7 @@ public void OneApplyCreatesOneSharedHistoryEntryForAllSettings() count.SetAsInt32("value", 7); var toggle = new EditorSettingObject(); toggle.SetAsBoolean("value", false); - - Assert.True(m_settings.Apply( - new Dictionary(StringComparer.Ordinal) - { - ["Editor/Tests/Values/Project Count"] = count, - ["Editor/Tests/Values/Project Toggle"] = toggle - })); + Assert.True(m_settings.Apply(new Dictionary(StringComparer.Ordinal) { ["Editor/Tests/Values/Project Count"] = count, ["Editor/Tests/Values/Project Toggle"] = toggle })); Assert.Equal("Apply Settings", m_runtime.interactions.history.undoName); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(3, m_settings.Get("Editor/Tests/Values/Project Count").GetAsInt32("value")); @@ -189,16 +150,10 @@ public void ClearOnPlaySettingDefaultsToTrueAndImmediatelyControlsTheConsole() const string path = "Editor/Diagnostics/Console/Clear on Play"; Assert.True(m_settings.Get(path).GetAsBoolean("value")); Assert.True(m_console.clearOnPlay); - var disabled = new EditorSettingObject(); disabled.SetAsBoolean("value", false); - Assert.True(m_settings.Apply( - new Dictionary(StringComparer.Ordinal) - { - [path] = disabled - })); + Assert.True(m_settings.Apply(new Dictionary(StringComparer.Ordinal) { [path] = disabled })); Assert.False(m_console.clearOnPlay); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_console.clearOnPlay); } @@ -206,17 +161,16 @@ public void ClearOnPlaySettingDefaultsToTrueAndImmediatelyControlsTheConsole() [Fact] public void CatalogKeepsRawStringPathsAndAlphabeticalSections() { - EditorSetting page = Assert.Single( - m_settings.definitions, - static definition => definition.path == "Editor/Tests"); + EditorSetting page = Assert.Single(m_settings.definitions, static definition => definition.path == "Editor/Tests"); Assert.False(page.hasValue); Assert.Equal("Test-only settings overview.", page.description); Assert.Equal( new[] { "Alpha", "Numbers" }, - m_settings.definitions - .Where(static definition => definition.pagePath == "Editor/Tests/Values") - .OrderBy(static definition => definition.section, StringComparer.Ordinal) - .Select(static definition => definition.section)); + m_settings.definitions.Where(static definition => definition.pagePath == "Editor/Tests/Values").OrderBy( + static definition => definition.section, + StringComparer.Ordinal + ).Select(static definition => definition.section) + ); } [Fact] @@ -231,21 +185,15 @@ public void SettingObjectRoundTripsSupportedPrimitivesAndArrays() value.SetAsSingle("single", 1.25f); value.SetAsDouble("double", 2.5d); value.SetAsString("string", "value"); - value.SetAsBooleanArray("booleans", [true, false]); + value.SetAsBooleanArray("booleans", [true, false ]); value.SetAsInt32Array("int32s", [-1, 2]); value.SetAsUInt32Array("uint32s", [1u, 2u]); value.SetAsSingleArray("singles", [1.5f, 2.5f]); value.SetAsDoubleArray("doubles", [3.5d, 4.5d]); value.SetAsStringArray("strings", ["first", null, "third"]); - - Assert.True(m_settings.Apply( - new Dictionary(StringComparer.Ordinal) - { - ["Editor/Tests/Objects/Primitives"] = value - })); + Assert.True(m_settings.Apply(new Dictionary(StringComparer.Ordinal) { ["Editor/Tests/Objects/Primitives"] = value })); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.True(m_runtime.interactions.history.Redo().succeeded); - EditorSettingObject restored = m_settings.Get("Editor/Tests/Objects/Primitives"); Assert.True(restored.GetAsBoolean("boolean")); Assert.Equal(-32, restored.GetAsInt32("int32")); @@ -260,30 +208,22 @@ public void SettingObjectRoundTripsSupportedPrimitivesAndArrays() Assert.Equal(new[] { 1u, 2u }, restored.GetAsUInt32Array("uint32s")); Assert.Equal(new[] { 1.5f, 2.5f }, restored.GetAsSingleArray("singles")); Assert.Equal(new[] { 3.5d, 4.5d }, restored.GetAsDoubleArray("doubles")); - Assert.Equal( - new string?[] { "first", null, "third" }, - restored.GetAsStringArray("strings")); - - string?[] strings = restored.GetAsStringArray("strings"); + Assert.Equal(new string? [] { "first", null, "third" }, restored.GetAsStringArray("strings")); + string? [] strings = restored.GetAsStringArray("strings"); strings[0] = "mutated"; Assert.Equal("first", restored.GetAsStringArray("strings")[0]); - Assert.Throws( - () => restored.SetAsSingle("invalid", float.PositiveInfinity)); + Assert.Throws(() => restored.SetAsSingle("invalid", float.PositiveInfinity)); Assert.Throws(() => restored.GetAsString(string.Empty)); } [Fact] public void DrawTracksDifferenceFromEachStagedObjectsFirstDraw() { - EditorSetting definition = Assert.Single( - m_settings.definitions, - static setting => setting.path == "Editor/Tests/Objects/Draw Mutation"); + EditorSetting definition = Assert.Single(m_settings.definitions, static setting => setting.path == "Editor/Tests/Objects/Draw Mutation"); var value = new EditorSettingObject(); - Assert.True(definition.Draw(value)); Assert.Equal(42, value.GetAsInt32("value")); Assert.True(definition.Draw(value)); - var alreadyCommitted = new EditorSettingObject(); alreadyCommitted.SetAsInt32("value", 42); Assert.False(definition.Draw(alreadyCommitted)); @@ -297,7 +237,6 @@ IEditorConsole editorConsole { internal static EditorSettings? current; internal static IEditorConsole? console; - protected override void OnStart(EditorContext context) { current = settings; @@ -308,25 +247,14 @@ protected override void OnStart(EditorContext context) private sealed class FakePlayMode : IEditorPlayMode { public EditorPlayModeState state => EditorPlayModeState.Editing; - public bool isPlaying => false; - public string? lastFailure => null; - public LogSessionId activeSessionId => LogSessionId.none; - public event Action? stateChanged - { - add - { - } - remove - { - } - } + private readonly Inno.Core.Events.EventDispatcher m_stateDispatcher = new(); + public IDisposable ListenStateChanges(Action listener) => m_stateDispatcher.CreateHub(); public bool EnterPlayMode() => false; - public bool ExitPlayMode() => false; } @@ -340,9 +268,7 @@ private sealed class TestSettingsPage : EditorSetting private sealed class TestCountSetting : EditorSetting { public override EditorSettingObject defaultValue => CreateDefault(); - public override string section => "Numbers"; - public override string description => "Exercises path-addressed project persistence."; protected override void OnDraw(EditorSettingObject setting) @@ -361,7 +287,6 @@ private static EditorSettingObject CreateDefault() private sealed class TestToggleSetting : EditorSetting { public override EditorSettingObject defaultValue => CreateDefault(); - public override string section => "Alpha"; protected override void OnDraw(EditorSettingObject setting) @@ -391,7 +316,6 @@ private sealed class TestDrawMutationSetting : EditorSetting { public override EditorSettingObject defaultValue => new(); - protected override void OnDraw(EditorSettingObject setting) - => setting.SetAsInt32("value", 42); + protected override void OnDraw(EditorSettingObject setting) => setting.SetAsInt32("value", 42); } } diff --git a/tests/editor/Inno.Editor.Interactions.Tests/Inno.Editor.Interactions.Tests.csproj b/tests/editor/Inno.Editor.Interactions.Tests/Inno.Editor.Interactions.Tests.csproj index 9e76fd0e6..9203b7889 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/Inno.Editor.Interactions.Tests.csproj +++ b/tests/editor/Inno.Editor.Interactions.Tests/Inno.Editor.Interactions.Tests.csproj @@ -10,6 +10,7 @@ + @@ -26,7 +27,7 @@ - + @@ -53,7 +54,6 @@ - diff --git a/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.Selection.cs b/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.Selection.cs new file mode 100644 index 000000000..f544b02d9 --- /dev/null +++ b/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.Selection.cs @@ -0,0 +1,131 @@ +using System; +using Inno.Core.Identity; +using Inno.Core.Serialization; +using Inno.Extensibility.Types; +using Inno.Scene; +using Inno.Scene.Components; +using Xunit; + +namespace Inno.Editor.Interactions.Tests; +public sealed partial class SceneHistoryTests +{ + [Fact] + public void BatchPropertyEditPreservesIndependentBeforeValuesInOneUndo() + { + GameScene scene = CreateScene(); + var first = scene.CreateObject("First").AddComponent(); + var second = scene.CreateObject("Second").AddComponent(); + first.value = 12; + second.value = 34; + Assert.False(m_edits.HaveSamePropertyValues([first, second], "value")); + Assert.True(m_edits.ChangeProperties([first, second], "value", () => first.value = 42, "Edit Selection")); + Assert.Equal(42, first.value); + Assert.Equal(42, second.value); + Assert.True(m_edits.HaveSamePropertyValues([first, second], "value")); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + Assert.Equal(12, first.value); + Assert.Equal(34, second.value); + Assert.True(m_runtime.interactions.history.Redo().succeeded); + Assert.Equal(42, first.value); + Assert.Equal(42, second.value); + } + + [Fact] + public void BatchAxisEditPreservesUneditedComponentsIncludingEqualPrimarySamples() + { + GameScene scene = CreateScene(); + var first = scene.CreateObject("First").transform; + var second = scene.CreateObject("Second").transform; + first.localPosition = new(1, 2, 3); + second.localPosition = new(4, 5, 6); + Assert.True(m_edits.ChangeProperties([first, second], "localPosition", () => first.localPosition = new(9, 2, 3), "Edit X", componentName: "x")); + Assert.Equal(new Inno.Core.Mathematics.Vector3(9, 5, 6), second.localPosition); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + Assert.Equal(new Inno.Core.Mathematics.Vector3(4, 5, 6), second.localPosition); + Assert.True(m_edits.ChangeProperties([first, second], "localPosition", () => { }, "Use Primary X", componentName: "x")); + Assert.Equal(new Inno.Core.Mathematics.Vector3(1, 5, 6), second.localPosition); + Assert.Throws(() => m_edits.ChangeProperties([first, second], "localPosition", () => first.localPosition = default, "Rejected Component", componentName: "notAComponent")); + Assert.Equal(new Inno.Core.Mathematics.Vector3(1, 2, 3), first.localPosition); + } + + [Fact] + public void LaterBatchSetterFailureRestoresAllEarlierTargets() + { + GameScene scene = CreateScene(); + var first = scene.CreateObject("First").AddComponent(); + var second = scene.CreateObject("Second").AddComponent(); + first.value = 12; + second.value = 34; + second.rejectAnswer = true; + Assert.ThrowsAny(() => m_edits.ChangeProperties([first, second], "value", () => first.value = 42, "Rejected Selection")); + Assert.Equal(12, first.value); + Assert.Equal(34, second.value); + Assert.False(m_runtime.interactions.history.canUndo); + } + + [Fact] + public void BatchDeleteNormalizesSelectedAncestorsAndRestoresEverySubtree() + { + GameScene scene = CreateScene(); + GameObject root = scene.CreateObject("Root"); + GameObject child = scene.CreateObject("Child"); + GameObject other = scene.CreateObject("Other"); + child.transform.SetParent(root.transform); + Guid rootId = root.identity.persistentId; + Guid childId = child.identity.persistentId; + Guid otherId = other.identity.persistentId; + m_edits.DeleteGameObjects([child, root, other]); + Assert.Null(IdentityAllocator.current.Get(rootId)); + Assert.Null(IdentityAllocator.current.Get(childId)); + Assert.Null(IdentityAllocator.current.Get(otherId)); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + GameObject restoredRoot = Assert.IsType(IdentityAllocator.current.Get(rootId)); + GameObject restoredChild = Assert.IsType(IdentityAllocator.current.Get(childId)); + Assert.Same(restoredRoot.transform, restoredChild.transform.parent); + Assert.NotNull(IdentityAllocator.current.Get(otherId)); + Assert.True(m_runtime.interactions.history.Redo().succeeded); + Assert.Null(IdentityAllocator.current.Get(rootId)); + } + + [Fact] + public void BatchMovePreservesRootOrderAndRestoresIndependentPlacements() + { + GameScene scene = CreateScene(); + GameObject first = scene.CreateObject("First"); + GameObject second = scene.CreateObject("Second"); + GameObject destination = scene.CreateObject("Destination"); + GameObject child = scene.CreateObject("Child"); + child.transform.SetParent(first.transform); + m_edits.MoveGameObjects([first, child, second], scene, destination); + Assert.Same(destination.transform, first.transform.parent); + Assert.Same(destination.transform, second.transform.parent); + Assert.Same(first.transform, child.transform.parent); + Assert.Equal(0, first.transform.siblingIndex); + Assert.Equal(1, second.transform.siblingIndex); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + Assert.Null(first.transform.parent); + Assert.Null(second.transform.parent); + Assert.Same(first.transform, child.transform.parent); + Assert.True(m_runtime.interactions.history.Redo().succeeded); + Assert.Same(destination.transform, second.transform.parent); + Assert.Throws(() => m_edits.MoveGameObjects([destination], scene, child)); + Assert.Same(destination.transform, first.transform.parent); + } +} + +[StableTypeId("9ea6d04d-4ed3-48f3-ac3a-b09372a575ab")] +internal sealed class BatchValidationComponent : GameComponent +{ + [SerializableProperty] + public int value { get; set; } + + [SerializableProperty] + public bool rejectAnswer { get; set; } + + [OnSerializableRestored] + private void Validate() + { + if (rejectAnswer && value == 42) + throw new InvalidOperationException("The current owner rejected this batch value."); + } +} diff --git a/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.cs b/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.cs index 2a69fda83..fa569eba9 100644 --- a/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.cs +++ b/tests/editor/Inno.Editor.Interactions.Tests/SceneHistoryTests.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Extensibility.Modules; @@ -22,13 +21,9 @@ using Xunit; namespace Inno.Editor.Interactions.Tests; - -public sealed class SceneHistoryTests : IDisposable +public sealed partial class SceneHistoryTests : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoSceneHistoryTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoSceneHistoryTests", Guid.NewGuid().ToString("N")); private readonly EngineHost m_host; private readonly RuntimeSession m_session; private readonly IDisposable m_executionScope; @@ -36,26 +31,16 @@ public sealed class SceneHistoryTests : IDisposable private readonly EditorInteractionRuntime m_runtime; private readonly SceneEdits m_edits; private readonly EditorReloadCoordinator m_reloads = new(); - public SceneHistoryTests() { Directory.CreateDirectory(Path.Combine(m_projectRoot, "Assets")); SceneHistoryProbe.Reset(); - m_host = new EngineHostBuilder() - .UseMetadataSources(new DotNetAssemblyCatalogSource(typeof(SceneHistoryTests).Assembly), - new ReflectionTypeCatalogSource(), new ReflectionSerializationMetadataSource()) - .Build(); - m_session = m_host.CreateSession(new RuntimeSessionOptions - { - kind = RuntimeSessionKind.Edit, - applicationId = "inno.tests.scene-history", - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine( - m_projectRoot, - "Library", - "PersistentData", - "inno.tests.scene-history"), "Logs")), - jobExecutionMode = RuntimeJobExecutionMode.SingleThread - }); + m_host = new EngineHostBuilder().UseMetadataSources( + new DotNetAssemblyCatalogSource(typeof(SceneHistoryTests).Assembly), + new ReflectionTypeCatalogSource(), + new ReflectionSerializationMetadataSource() + ).Build(); + m_session = m_host.CreateSession(new RuntimeSessionOptions { kind = RuntimeSessionKind.Edit, applicationId = "inno.tests.scene-history", createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(m_projectRoot, "Library", "PersistentData", "inno.tests.scene-history"), "Logs")), jobExecutionMode = RuntimeJobExecutionMode.SingleThread }); m_executionScope = m_session.EnterExecutionScope(); m_assets = new AssetPipeline( m_host.modules, @@ -64,23 +49,14 @@ public SceneHistoryTests() new IdentityAllocator(), m_host.diagnostics, m_host.logs, - AssetPipelineOptions.Create( - Path.Combine(m_projectRoot, "Assets"), - Path.Combine(m_projectRoot, "Library")) with - { - enableFileSystemWatcher = false - }); + AssetPipelineOptions.Create(Path.Combine(m_projectRoot, "Assets"), Path.Combine(m_projectRoot, "Library"))with { enableFileSystemWatcher = false } + ); m_runtime = new EditorInteractionRuntime( new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_host.types, m_host.logs, - [ - m_host.types, - m_host.serialization, - m_session, - m_assets, - m_reloads - ]); + [m_host.types, m_host.serialization, m_session, m_assets, m_reloads] + ); m_runtime.Start(); _ = m_runtime.panelCount; m_edits = Assert.IsType(SceneHistoryProbe.edits); @@ -107,22 +83,14 @@ public void PropertyUndoRestoresOnlyThePropertyOnTheExistingSceneGraph() Guid sceneId = scene.identity.persistentId; Guid objectId = gameObject.identity.persistentId; Guid componentId = component.identity.persistentId; - - Assert.True(m_edits.ChangeProperty( - component, - nameof(HistoryComponent.value), - () => component.value = 25, - "Change Value", - "tests:component-value")); + Assert.True(m_edits.ChangeProperty(component, nameof(HistoryComponent.value), () => component.value = 25, "Change Value", "tests:component-value")); Assert.Equal(25, component.value); - EditorHistoryResult undo = m_runtime.interactions.history.Undo(); Assert.True(undo.succeeded, undo.message); Assert.Equal(10, component.value); Assert.Same(scene, IdentityAllocator.current.Get(sceneId)); Assert.Same(gameObject, IdentityAllocator.current.Get(objectId)); Assert.Same(component, IdentityAllocator.current.Get(componentId)); - Assert.True(m_runtime.interactions.history.Redo().succeeded); Assert.Equal(25, component.value); } @@ -133,20 +101,13 @@ public void PropertyUndoRestoresEverySiblingChangedByThePrimarySetter() var component = CreateScene().CreateObject("Coupled").AddComponent(); Assert.True(component.primary); Assert.Equal(CoupledHistoryMode.Base, component.mode); - - Assert.True(m_edits.ChangeProperty( - component, - nameof(CoupledHistoryComponent.mode), - () => component.mode = CoupledHistoryMode.Overlay, - "Change Coupled Mode")); + Assert.True(m_edits.ChangeProperty(component, nameof(CoupledHistoryComponent.mode), () => component.mode = CoupledHistoryMode.Overlay, "Change Coupled Mode")); Assert.Equal(CoupledHistoryMode.Overlay, component.mode); Assert.False(component.primary); - EditorHistoryResult undo = m_runtime.interactions.history.Undo(); Assert.True(undo.succeeded, undo.message); Assert.Equal(CoupledHistoryMode.Base, component.mode); Assert.True(component.primary); - EditorHistoryResult redo = m_runtime.interactions.history.Redo(); Assert.True(redo.succeeded, redo.message); Assert.Equal(CoupledHistoryMode.Overlay, component.mode); @@ -159,28 +120,13 @@ public void TransformManipulationTransactionUndoesAllLocalValuesAtomically() GameObject gameObject = CreateScene().CreateObject("Manipulated"); Transform transform = gameObject.transform; var position = new Inno.Core.Mathematics.Vector3(3f, -2f, 4f); - Inno.Core.Mathematics.Quaternion rotation = - Inno.Core.Mathematics.Quaternion.CreateFromYawPitchRoll(0.2f, 0.3f, 0.4f); + Inno.Core.Mathematics.Quaternion rotation = Inno.Core.Mathematics.Quaternion.CreateFromYawPitchRoll(0.2f, 0.3f, 0.4f); var scale = new Inno.Core.Mathematics.Vector3(2f, 3f, 4f); - - using (EditorHistoryTransaction transaction = - m_runtime.interactions.history.BeginTransaction("Move GameObject")) + using (EditorHistoryTransaction transaction = m_runtime.interactions.history.BeginTransaction("Move GameObject")) { - Assert.True(m_edits.ChangeProperty( - transform, - nameof(Transform.localPosition), - () => transform.localPosition = position, - "Move GameObject")); - Assert.True(m_edits.ChangeProperty( - transform, - nameof(Transform.localRotation), - () => transform.localRotation = rotation, - "Move GameObject")); - Assert.True(m_edits.ChangeProperty( - transform, - nameof(Transform.localScale), - () => transform.localScale = scale, - "Move GameObject")); + Assert.True(m_edits.ChangeProperty(transform, nameof(Transform.localPosition), () => transform.localPosition = position, "Move GameObject")); + Assert.True(m_edits.ChangeProperty(transform, nameof(Transform.localRotation), () => transform.localRotation = rotation, "Move GameObject")); + Assert.True(m_edits.ChangeProperty(transform, nameof(Transform.localScale), () => transform.localScale = scale, "Move GameObject")); transaction.Commit(); } @@ -207,9 +153,7 @@ public void WorldPositionEditRestoresLocalStorageAndWorldResultThroughHistory() var beforeLocal = child.localPosition; var beforeWorld = child.worldPosition; var requestedWorld = new Inno.Core.Mathematics.Vector3(11f, 7f, 20f); - - Assert.True(m_edits.ChangeProperty(child, nameof(Transform.localPosition), - () => child.worldPosition = requestedWorld, "Edit World Position")); + Assert.True(m_edits.ChangeProperty(child, nameof(Transform.localPosition), () => child.worldPosition = requestedWorld, "Edit World Position")); var afterLocal = child.localPosition; Assert.Equal(requestedWorld, child.worldPosition); Assert.NotEqual(beforeLocal, afterLocal); @@ -228,27 +172,23 @@ public void ComponentAddRemoveResetAndOrderPreserveLogicalIdentity() GameObject gameObject = scene.CreateObject("Object"); var first = (HistoryComponent)m_edits.AddComponent(gameObject, typeof(HistoryComponent)); Guid firstId = first.identity.persistentId; - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Null(IdentityAllocator.current.Get(firstId)); Assert.True(m_runtime.interactions.history.Redo().succeeded); var restoredFirst = Assert.IsType(IdentityAllocator.current.Get(firstId)); Assert.NotSame(first, restoredFirst); Assert.Equal(7, restoredFirst.value); - var second = (HistoryComponent)m_edits.AddComponent(gameObject, typeof(HistoryComponent)); m_edits.SetComponentIndex(second, 1); Assert.Equal(1, gameObject.GetComponentIndex(second)); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(2, gameObject.GetComponentIndex(second)); - restoredFirst.value = 99; m_edits.ResetComponent(restoredFirst); Assert.Equal(7, restoredFirst.value); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(99, restoredFirst.value); Assert.Same(restoredFirst, IdentityAllocator.current.Get(firstId)); - Assert.True(m_edits.RemoveComponent(restoredFirst)); Assert.Null(IdentityAllocator.current.Get(firstId)); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -265,18 +205,14 @@ public void RemovingComponentRestoresSerializedAssetReferences() AssetPath assetPath = AssetPath.Project("Text/shared.txt"); Assert.True(m_assets.Import(assetPath)); TextAsset asset = m_assets.Load(assetPath); - GameObject owner = CreateScene().CreateObject("Audio Source Owner"); var component = owner.AddComponent(); component.asset = asset; Guid componentId = component.identity.persistentId; - Assert.True(m_edits.RemoveComponent(component)); EditorHistoryResult undo = m_runtime.interactions.history.Undo(); - Assert.True(undo.succeeded, undo.message); - var restored = Assert.IsType( - IdentityAllocator.current.Get(componentId)); + var restored = Assert.IsType(IdentityAllocator.current.Get(componentId)); Assert.Same(asset, restored.asset); Assert.True(m_runtime.interactions.history.Redo().succeeded); Assert.Null(IdentityAllocator.current.Get(componentId)); @@ -299,13 +235,11 @@ public void UndoMissingPluginComponentRecoversItsAssetAndKeepsTheRedoBranch() m_assets.Rescan(); ReloadHistoryModule(install: false); m_host.modules.generations.Wait(); - EditorHistoryResult undo = m_runtime.interactions.history.Undo(); Assert.True(undo.succeeded, undo.message); MissingGameComponent missing = Assert.IsType(m_session.scenes.Find(componentId)); int missingRuntimeId = Assert.IsType(missing.identity.runtimeId); Assert.Equal(componentId, missing.identity.persistentId); - File.WriteAllText(sourcePath, "shared recovery content"); File.WriteAllBytes(sourcePath + ".imeta", sourceIdentity); m_assets.Rescan(); @@ -330,7 +264,6 @@ public void FailedMissingRecoveryPreservesPlaceholderAndHistoryForRetry() m_host.modules.generations.Wait(); Assert.True(m_runtime.interactions.history.Undo().succeeded); MissingGameComponent missing = Assert.IsType(m_session.scenes.Find(componentId)); - AssertRejectedHistoryModule(); m_host.modules.generations.Wait(); Assert.Same(missing, m_session.scenes.Find(componentId)); @@ -358,9 +291,7 @@ bool rejectRestore } [MethodImpl(MethodImplOptions.NoInlining)] - private void AssertRejectedHistoryModule() - => Assert.ThrowsAny(() => ReloadHistoryModule(install: true)); - + private void AssertRejectedHistoryModule() => Assert.ThrowsAny(() => ReloadHistoryModule(install: true)); [Fact] public void MissingComponentEditsPreserveOriginalStateAndRecoveryDoesNotDirtySavedScene() { @@ -410,7 +341,7 @@ public void MissingSystemUndoAndRemovalKeepItsLogicalTypeForRecovery() m_host.modules.generations.Wait(); Assert.True(m_runtime.interactions.history.Undo().succeeded); MissingGameSystem missing = Assert.IsType(m_session.scenes.Find(id)); - Assert.True(m_edits.RemoveSystem(scene, missing)); + Assert.True(m_edits.RemoveSystem(missing)); Assert.True(m_runtime.interactions.history.Undo().succeeded); ReloadHistoryModule(install: true); AssertRecoveredSystem(id); @@ -427,7 +358,7 @@ private Guid AddAndRemoveReloadableSystem(GameScene scene) GameSystem system = scene.AddSystem(type); type.GetProperty("value")!.SetValue(system, 92); Guid id = system.identity.persistentId; - Assert.True(m_edits.RemoveSystem(scene, system)); + Assert.True(m_edits.RemoveSystem(system)); return id; } @@ -458,22 +389,18 @@ int missingRuntimeId private void ReloadHistoryModule(bool install) { DotNetModuleSource request = CreateHistoryRequest(); - using AssemblyReloadSession reload = install - ? m_host.modules.BeginReload([request]) - : m_host.modules.BeginReload([], [request.moduleName]); + using AssemblyReloadSession reload = install ? m_host.modules.BeginReload([request]) : m_host.modules.BeginReload([], [request.moduleName]); _ = m_reloads.Execute(reload); } - private DotNetModuleSource CreateHistoryRequest() - => new() - { - moduleName = "SceneHistoryRecovery", - artifactRootDirectory = Path.Combine(m_projectRoot, "Library", "Modules"), - mainAssemblyPath = Path.Combine(AppContext.BaseDirectory, "Modules", "SceneReload", "Inno.Scene.Reload.TestModule.dll"), - domain = AssemblyDomain.InnoPlugin, - scope = AssemblyScope.Runtime - }; - + private DotNetModuleSource CreateHistoryRequest() => new() + { + moduleName = "SceneHistoryRecovery", + artifactRootDirectory = Path.Combine(m_projectRoot, "Library", "Modules"), + mainAssemblyPath = Path.Combine(AppContext.BaseDirectory, "Modules", "SceneReload", "Inno.Scene.Reload.TestModule.dll"), + domain = AssemblyDomain.InnoPlugin, + scope = AssemblyScope.Runtime + }; [Fact] public void ExternalResolversAreRestoredBeforeDomainPropertyCompensation() { @@ -481,8 +408,7 @@ public void ExternalResolversAreRestoredBeforeDomainPropertyCompensation() var participant = new ExternalRecoveryOrder(() => externalCandidateActive); using IDisposable registration = m_reloads.Register(participant); using AssemblyReloadSession reload = m_host.modules.BeginReload([CreateHistoryRequest()]); - Assert.Throws(() => m_reloads.Execute(reload, - new ExternalContentChange(() => externalCandidateActive = true, () => externalCandidateActive = false))); + Assert.Throws(() => m_reloads.Execute(reload, new ExternalContentChange(() => externalCandidateActive = true, () => externalCandidateActive = false))); Assert.True(participant.restored); Assert.False(externalCandidateActive); reload.Dispose(); @@ -495,30 +421,45 @@ private sealed class ExternalContentChange( Action restore ) : IGenerationChange { - public void PrepareForActivation() { } + public void PrepareForActivation() + { + } + public void Apply() => activate(); - public void Complete() { } - public void RollbackStructure() { } + public void Complete() + { + } + + public void RollbackStructure() + { + } + public void RestorePreviousState() => restore(); } private sealed class ExternalRecoveryOrder(Func externalCandidateActive) : IEditorReloadParticipant, IGenerationChange { internal bool restored { get; private set; } + public IGenerationChange Capture(AssemblyReloadContext context) => this; - public void RefreshDiagnostics() { } + public void RefreshDiagnostics() + { + } + public void PrepareForActivation() => Assert.False(externalCandidateActive()); public void Apply() { Assert.True(externalCandidateActive()); throw new InvalidOperationException("Rejected domain candidate."); } + public void RollbackStructure() => Assert.True(externalCandidateActive()); public void RestorePreviousState() { Assert.False(externalCandidateActive()); restored = true; } + public void Complete() => Assert.Fail("Failed candidate cannot commit."); } @@ -538,14 +479,12 @@ public void RemovingAnElementAndSubtreeRestoresExternalSceneReferences() Guid componentId = targetComponent.identity.persistentId; Guid rootId = targetObject.identity.persistentId; Guid childId = targetChild.identity.persistentId; - Assert.True(m_edits.RemoveComponent(targetComponent)); EditorHistoryResult restoreComponentResult = m_runtime.interactions.history.Undo(); Assert.True(restoreComponentResult.succeeded, restoreComponentResult.message); var restoredComponent = Assert.IsType(IdentityAllocator.current.Get(componentId)); Assert.Same(restoredComponent, references.targetComponent); Assert.Equal(41, restoredComponent.value); - Assert.True(m_edits.DeleteGameObject(targetObject)); Assert.True(m_runtime.interactions.history.Undo().succeeded); GameObject restoredRoot = Assert.IsType(IdentityAllocator.current.Get(rootId)); @@ -553,7 +492,6 @@ public void RemovingAnElementAndSubtreeRestoresExternalSceneReferences() Assert.Same(restoredRoot.transform, restoredChild.transform.parent); Assert.Same(restoredChild, references.targetObject); Assert.Equal(componentId, references.targetComponent?.identity.persistentId); - Assert.True(m_runtime.interactions.history.Redo().succeeded); Assert.Null(IdentityAllocator.current.Get(rootId)); } @@ -564,35 +502,14 @@ public void ElementRestoreRejectsAnotherLogicalTypesPayloadBeforeAttachingElemen GameScene scene = CreateScene(); GameObject sourceObject = scene.CreateObject("Source"); var source = sourceObject.AddComponent(); - byte[] incompatibleState = SceneElementSerialization.CaptureState( - source, - m_host.serialization, - m_assets); + byte[] incompatibleState = SceneElementSerialization.CaptureState(source, m_host.serialization, m_assets); GameObject owner = scene.CreateObject("Owner"); TypeRef componentType = m_host.types.GetTypeRef(typeof(HistoryComponent)); TypeRef systemType = m_host.types.GetTypeRef(typeof(HistorySystem)); Guid componentId = Guid.NewGuid(); Guid systemId = Guid.NewGuid(); - - InvalidDataException componentFailure = Assert.Throws(() => - SceneElementSerialization.RestoreComponent( - owner, - componentType, - componentId, - componentIndex: owner.GetComponents().Count, - incompatibleState, - m_host.serialization, - m_assets)); - InvalidDataException systemFailure = Assert.Throws(() => - SceneElementSerialization.RestoreSystem( - scene, - systemType, - systemId, - systemIndex: scene.GetSystems().Count, - incompatibleState, - m_host.serialization, - m_assets)); - + InvalidDataException componentFailure = Assert.Throws(() => SceneElementSerialization.RestoreComponent(owner, componentType, componentId, componentIndex: owner.GetComponents().Count, incompatibleState, m_host.serialization, m_assets)); + InvalidDataException systemFailure = Assert.Throws(() => SceneElementSerialization.RestoreSystem(scene, systemType, systemId, systemIndex: scene.GetSystems().Count, incompatibleState, m_host.serialization, m_assets)); Assert.Contains("type", componentFailure.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("type", systemFailure.Message, StringComparison.OrdinalIgnoreCase); Assert.Null(IdentityAllocator.current.Get(componentId)); @@ -605,24 +522,11 @@ public void ElementRestoreRemovesPartialElementWhenRestoreCallbackFails() GameScene scene = CreateScene(); GameObject sourceOwner = scene.CreateObject("Source"); var source = sourceOwner.AddComponent(); - byte[] state = SceneElementSerialization.CaptureState( - source, - m_host.serialization, - m_assets); + byte[] state = SceneElementSerialization.CaptureState(source, m_host.serialization, m_assets); GameObject targetOwner = scene.CreateObject("Target"); TypeRef typeRef = m_host.types.GetTypeRef(typeof(RestoreCleanupFailureComponent)); Guid persistentId = Guid.NewGuid(); - - _ = Assert.ThrowsAny(() => - SceneElementSerialization.RestoreComponent( - targetOwner, - typeRef, - persistentId, - componentIndex: targetOwner.GetComponents().Count, - state, - m_host.serialization, - m_assets)); - + _ = Assert.ThrowsAny(() => SceneElementSerialization.RestoreComponent(targetOwner, typeRef, persistentId, componentIndex: targetOwner.GetComponents().Count, state, m_host.serialization, m_assets)); Assert.Null(IdentityAllocator.current.Get(persistentId)); } @@ -635,25 +539,14 @@ public void FailedSubtreeRestoreRemovesEveryCreatedObjectDespiteDestructionCallb GameObject child = scene.CreateObject("Child"); child.transform.SetParent(root.transform); _ = child.AddComponent(); - byte[] state = SceneSubtreeSerialization.Capture( - root, - m_host.serialization, - m_assets); + byte[] state = SceneSubtreeSerialization.Capture(root, m_host.serialization, m_assets); Guid rootId = root.identity.persistentId; Guid childId = child.identity.persistentId; - Assert.True(scene.DestroyObject(root)); SubtreeRestoreFailureComponent.throwOnDestroy = true; try { - _ = Assert.ThrowsAny(() => - SceneSubtreeSerialization.Restore( - scene, - state, - m_host.serialization, - m_assets, - parent: null, - siblingIndex: 0)); + _ = Assert.ThrowsAny(() => SceneSubtreeSerialization.Restore(scene, state, m_host.serialization, m_assets, parent: null, siblingIndex: 0)); } finally { @@ -676,7 +569,6 @@ public void FailedSubtreeHistoryRestoreReportsPreservedOnlyAfterExactSceneRecove _ = child.AddComponent(); Guid rootId = root.identity.persistentId; Guid childId = child.identity.persistentId; - Assert.True(m_edits.DeleteGameObject(root)); SubtreeRestoreFailureComponent.throwOnDestroy = true; EditorHistoryResult result; @@ -705,12 +597,8 @@ public void HierarchyUndoRestoresOnlyAffectedPlacements() GameObject secondParent = scene.CreateObject("Second"); GameObject child = scene.CreateObject("Child"); child.transform.SetParent(firstParent.transform); - - Assert.True(m_edits.ChangeHierarchy( - child, - _ => child.transform.SetParent(secondParent.transform))); + Assert.True(m_edits.ChangeHierarchy(child, _ => child.transform.SetParent(secondParent.transform))); Assert.Same(secondParent.transform, child.transform.parent); - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Same(firstParent.transform, child.transform.parent); Assert.True(m_runtime.interactions.history.Redo().succeeded); @@ -725,18 +613,15 @@ public void SystemHistoryPreservesIdentityStateAndExplicitOrder() var second = (HistorySystem)m_edits.AddSystem(scene, typeof(HistorySystem)); first.value = 30; Guid firstId = first.identity.persistentId; - m_edits.SetSystemIndex(scene, second, 0); Assert.Equal(0, scene.GetSystemIndex(second)); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(1, scene.GetSystemIndex(second)); - - m_edits.ResetSystem(scene, first); + m_edits.ResetSystem(first); Assert.Equal(11, first.value); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(30, first.value); - - Assert.True(m_edits.RemoveSystem(scene, first)); + Assert.True(m_edits.RemoveSystem(first)); Assert.True(m_runtime.interactions.history.Undo().succeeded); var restored = Assert.IsType(IdentityAllocator.current.Get(firstId)); Assert.Equal(30, restored.value); @@ -748,13 +633,10 @@ public void SceneDocumentUndoRecreatesTheSameLogicalScene() GameScene scene = m_edits.CreateScene(); scene.CreateObject("Persistent"); Guid sceneId = scene.identity.persistentId; - Assert.True(m_edits.CloseScene(scene)); Assert.Empty(SceneManager.loadedScenes); Assert.Null(IdentityAllocator.current.Get(sceneId)); - Assert.True( - m_runtime.interactions.history.canUndo, - m_runtime.interactions.history.undoUnavailableReason); + Assert.True(m_runtime.interactions.history.canUndo, m_runtime.interactions.history.undoUnavailableReason); EditorHistoryResult undo = m_runtime.interactions.history.Undo(); Assert.True(undo.succeeded, undo.message); GameScene restored = Assert.IsType(IdentityAllocator.current.Get(sceneId)); @@ -771,10 +653,8 @@ public void SceneHistoryRemainsUsableAfterHostTypeCacheRefresh() GameScene scene = CreateScene(); GameObject gameObject = scene.CreateObject("Before"); m_edits.RenameGameObject(gameObject, "After"); - m_host.types.Rebuild(); _ = m_runtime.panelCount; - Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal("Before", gameObject.name); Assert.True(m_runtime.interactions.history.Redo().succeeded); @@ -786,10 +666,8 @@ public void SceneRenameUsesTheSameScalarHistoryContractAsGameObjectRename() { GameScene scene = CreateScene(); GameObject gameObject = scene.CreateObject("Object Before"); - m_edits.RenameScene(scene, "Scene After"); m_edits.RenameGameObject(gameObject, "Object After"); - Assert.Equal("Scene After", scene.name); Assert.Equal("Object After", gameObject.name); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -807,9 +685,7 @@ public void GameObjectTagUndoAndRedoRefreshSceneQueries() { GameScene scene = CreateScene(); GameObject gameObject = scene.CreateObject("Tagged"); - m_edits.SetGameObjectTag(gameObject, "Player"); - Assert.Equal("Player", gameObject.tag); Assert.Same(gameObject, scene.FindObjectWithTag("Player")); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -826,9 +702,7 @@ public void GameObjectLayerUndoAndRedoRefreshSceneQueries() GameScene scene = CreateScene(); GameObject gameObject = scene.CreateObject("Layered"); var gameplay = new GameLayer(6); - m_edits.SetGameObjectLayer(gameObject, gameplay); - Assert.Equal(gameplay, gameObject.layer); Assert.Same(gameObject, scene.FindObjectWithLayer(gameplay)); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -839,8 +713,7 @@ public void GameObjectLayerUndoAndRedoRefreshSceneQueries() Assert.Same(gameObject, scene.FindObjectWithLayer(gameplay)); } - private GameScene CreateScene() - => m_session.scenes.LoadNewSceneAdditive(); + private GameScene CreateScene() => m_session.scenes.LoadNewSceneAdditive(); } [EditorModule("tests.scene-history-probe", order: 220)] @@ -855,7 +728,6 @@ IEditorSceneWorkspace workspace } public static SceneEdits? edits { get; private set; } - public static IEditorSceneWorkspace? documents { get; private set; } public static void Reset() @@ -889,13 +761,8 @@ internal sealed class CoupledHistoryComponent : GameBehavior { private bool m_primary = true; private CoupledHistoryMode m_mode; - [SerializableProperty] - public bool primary - { - get => m_primary; - set => m_primary = value && mode == CoupledHistoryMode.Base; - } + public bool primary { get => m_primary; set => m_primary = value && mode == CoupledHistoryMode.Base; } [SerializableProperty] public CoupledHistoryMode mode @@ -949,8 +816,7 @@ internal sealed class RestoreCleanupFailureComponent : GameBehavior public int value { get; set; } = 5; [OnSerializableRestored] - private void OnRestored() - => throw new InvalidOperationException("Injected restore callback failure."); + private void OnRestored() => throw new InvalidOperationException("Injected restore callback failure."); } [StableTypeId("8503e97b-1845-4ab2-a116-1ece04934a4b")] @@ -958,14 +824,11 @@ private void OnRestored() internal sealed class SubtreeRestoreFailureComponent : GameBehavior { internal static bool throwOnDestroy; - [SerializableProperty] public int value { get; set; } = 5; [OnSerializableRestored] - private void OnRestored() - => throw new InvalidOperationException("Injected subtree restore callback failure."); - + private void OnRestored() => throw new InvalidOperationException("Injected subtree restore callback failure."); protected override void OnDestroy() { if (throwOnDestroy) diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/ConsolePanelNativeLayoutTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/ConsolePanelNativeLayoutTests.cs index 1370d8129..bc60a5e36 100644 --- a/tests/editor/Inno.Editor.PlayMode.Tests/ConsolePanelNativeLayoutTests.cs +++ b/tests/editor/Inno.Editor.PlayMode.Tests/ConsolePanelNativeLayoutTests.cs @@ -11,6 +11,7 @@ using Inno.Core.Diagnostics; using Inno.Core.Logging; using Inno.Editor.Core; +using Inno.Editor.ImGui; using Inno.Editor.Diagnostics; using Inno.Editor.Interactions; using Inno.Extensibility.Modules; @@ -23,6 +24,61 @@ namespace Inno.Editor.PlayMode.Tests; public sealed unsafe class ConsolePanelNativeLayoutTests { + [Fact] + public void SnapshotFailureQuarantinesTheConsoleWithoutLeakingChildWindows() + { + string root = Path.Combine(Path.GetTempPath(), "InnoConsoleFailure", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + _ = Assembly.Load("Inno.Editor.Panel.Logging"); + ImGuiNativeContext* nativeContext = ImGuiApi.Raw.CreateContext(null); + try + { + ImGuiIO* io = ImGuiApi.Raw.GetIO(); + io->DisplaySize = Unsafe.BitCast(new Vector2(1200f, 800f)); + io->DeltaTime = 1f / 60f; + io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; + io->Fonts->RendererHasTextures = 1; + io->IniFilename = null; + using var modules = new ModuleHost(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ConsolePanelNativeLayoutTests).Assembly) }); + using var types = new TypeCatalog(modules, new ReflectionTypeCatalogSource()); + using var logs = new LogRouter(); + var diagnostics = new DiagnosticHub(); + using IDisposable diagnosticScope = diagnostics.EnterScope(); + using var ui = new EditorUiSession(); + using var runtime = new EditorInteractionRuntime( + new EditorContext(root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), + types, + logs, + [new RejectedConsole()]); + runtime.Start(); + EditorPanelExtension panel = Assert.Single(runtime.panels, value => value.id == "diagnostics.console"); + panel.isOpen = true; + using IDisposable scope = ui.Enter(); + for (int frame = 0; frame < 2; frame++) + { + ImGuiApi.NewFrame(); + ImGuiApi.SetNextWindowSize(new(800f, 600f)); + using NativeStringLease title = Utf8Codec.EncodeNullTerminated("Failure parent", allowsNull: false); + _ = ImGuiApi.Raw.Begin((sbyte*)title.pointer, null, 0); + nint parent = (nint)ImGuiApi.Raw.GetCurrentWindow(); + if (frame == 0) + { + Assert.False(panel.Draw(runtime.context)); + Assert.False(panel.isOpen); + } + Assert.Equal(parent, (nint)ImGuiApi.Raw.GetCurrentWindow()); + Inno.Editor.ImGui.ImGui.Text("Following valid drawing"); + ImGuiApi.End(); + ImGuiApi.Render(); + } + } + finally + { + ImGuiApi.Raw.DestroyContext(nativeContext); + Directory.Delete(root, recursive: true); + } + } + [Theory] [InlineData(false, 1f)] [InlineData(true, 1f)] @@ -50,7 +106,13 @@ float scale using var console = new EditorConsole(logs, hub, new InactivePlayMode()); console.Start(); using var reporter = hub.CreateReporter(new("shader", "Rendering")); - using var runtime = new EditorInteractionRuntime(new EditorContext(root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), types, logs, [console]); + using var ui = new EditorUiSession(); + using var runtime = new EditorInteractionRuntime( + new EditorContext(root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), + types, + logs, + [console] + ); runtime.Start(); logs.Flush(); console.Clear(); @@ -61,6 +123,7 @@ float scale panel.isOpen = true; void Draw(float width) { + using IDisposable scope = ui.Enter(); ImGuiApi.NewFrame(); ImGuiApi.SetNextWindowPos(new(0, 0)); ImGuiApi.SetNextWindowSize(new(width, 700)); @@ -79,8 +142,7 @@ void Draw(float width) if (Marshal.PtrToStringUTF8((nint)window->Name)?.Contains("##ConsoleEntryCard", StringComparison.Ordinal) == true) card = window; } Assert.True(card != null); - using NativeStringLease nativeTestText1 = Utf8Codec.EncodeNullTerminated("##HeaderTable", allowsNull: false); - ImGuiTable* header = ImGuiApi.Raw.TableFindByID(ImGuiApi.Raw.GetID(card, (sbyte*)nativeTestText1.pointer, null)); + ImGuiTable* header = FindActiveTable(card, 3); Assert.True(header != null); Vector2 toggle = new(header->Columns.Data[0].WorkMinX + 6, header->RowPosY1 + ImGuiApi.GetFontSize() / 2); ImGuiApi.Raw.AddMousePosEvent(ImGuiApi.Raw.GetIO(), toggle.X, toggle.Y); @@ -94,9 +156,7 @@ void Draw(float width) foreach (float width in new[] { 960f, 500f, 260f, 140f, 960f }) { Draw(width); - uint occurrenceId = ImGuiApi.Raw.GetID(card, unchecked((int)Assert.Single(console.Capture().occurrences).sequence)); - using NativeStringLease nativeTestText2 = Utf8Codec.EncodeNullTerminated("##ConsoleEntryDetails", allowsNull: false); - ImGuiTable* table = ImGuiApi.Raw.TableFindByID(ImGuiApi.Raw.ImHashStr((sbyte*)nativeTestText2.pointer, 0, occurrenceId)); + ImGuiTable* table = FindActiveTable(card, 2); bool details = table != null && table->LastFrameActive == ImGuiApi.GetFrameCount(); if (details) { @@ -112,13 +172,37 @@ void Draw(float width) finally { ImGuiApi.Raw.DestroyContext(context); Directory.Delete(root, true); } } + private static ImGuiTable* FindActiveTable( + ImGuiWindow* window, + int columns + ) { + ImGuiNativeContext* context = ImGuiApi.Raw.GetCurrentContext(); + for (int index = 0; index < context->Tables.Buf.Size; index++) + { + ImGuiTable* table = &context->Tables.Buf.Data[index]; + if (table->OuterWindow == window && table->ColumnsCount == columns && table->LastFrameActive == ImGuiApi.GetFrameCount()) + return table; + } + return null; + } + + private sealed class RejectedConsole : IEditorConsole + { + public int capacity { get; set; } + public bool clearOnPlay { get; set; } + public EditorConsoleSnapshot Capture() => throw new InvalidOperationException("Snapshot source failed."); + public void Clear() { } + } + private sealed class InactivePlayMode : IEditorPlayMode { public EditorPlayModeState state => EditorPlayModeState.Editing; public bool isPlaying => false; public string? lastFailure => null; public LogSessionId activeSessionId => LogSessionId.none; - public event Action? stateChanged { add { } remove { } } + private readonly Inno.Core.Events.EventDispatcher m_stateDispatcher = new(); + public IDisposable ListenStateChanges(Action listener) => m_stateDispatcher.CreateHub(); + public bool EnterPlayMode() => false; public bool ExitPlayMode() => false; } diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleSearchNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleSearchNativeTests.cs new file mode 100644 index 000000000..e2ad9c513 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleSearchNativeTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BGCS.Runtime; +using Inno.Core.Diagnostics; +using Inno.Core.Logging; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Xunit; +using NativeImGui = Inno.Native.ImGui.ImGui; +using NativeVector2 = Inno.Native.ImGui.ImVec2C; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class ConsoleSearchNativeTests +{ + [Theory] + [MemberData(nameof(VisualScaleMatrixTests.Metrics), MemberType = typeof(VisualScaleMatrixTests))] + [Trait("Acceptance", "Visual")] + public void ActualConsoleFiltersMessagesFilesSourcesAndLongUnicodeQueries( + float zoom, + float density, + float width, + float height + ) { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.SetDisplayMetrics(new(width, height), density, zoom); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + var logger = host.logs.CreateLogger(); + for (int occurrence = 0; occurrence < 2; occurrence++) + logger.Write(LogLevel.Info, "Needle message"); + logger.Write(LogLevel.Warn, "Unrelated message"); + string longMessage = new('x', 300); + logger.Write(LogLevel.Info, longMessage); + host.logs.Flush(); + using var reporter = host.diagnostics.CreateReporter(new("search-source", "SOURCE-NAME")); + reporter.Publish(new("SEARCH_CODE", "A diagnostic", DiagnosticSeverity.Error, location: new DiagnosticLocation("Assets/漢字.shader", 42, 3))); + host.DrawPanel(panel); + host.DrawPanel(panel); + Assert.Equal(5, host.console.Capture().occurrences.Count); + Assert.Equal(4, CountCards()); + foreach (string query in new[] { "nEeDlE", "漢字", "source-name", "search_code", longMessage }) + { + Search(host, panel, query); + Assert.Equal(1, CountCards()); + Assert.Equal(5, host.console.Capture().occurrences.Count); + } + Search(host, panel, "no matching content"); + Assert.Equal(0, CountCards()); + Search(host, panel, string.Empty); + Assert.Equal(4, CountCards()); + } + + [Theory] + [MemberData(nameof(VisualScaleMatrixTests.Metrics), MemberType = typeof(VisualScaleMatrixTests))] + [Trait("Acceptance", "Visual")] + public void FilteredKeyboardTargetCopiesTheSameGroupAndClearReleasesIt( + float zoom, + float density, + float width, + float height + ) { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.SetDisplayMetrics(new(width, height), density, zoom); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + var logger = host.logs.CreateLogger(); + for (int occurrence = 0; occurrence < 2; occurrence++) + logger.Write(LogLevel.Info, "Needle message"); + logger.Write(LogLevel.Warn, "Other entry"); + host.logs.Flush(); + host.DrawPanel(panel); + host.DrawPanel(panel); + Search(host, panel, "needle"); + ImGuiWindow* card = FindActiveWindow("##ConsoleEntryCard"); + Vector2 position = Unsafe.BitCast(card->Pos) + new Vector2(230f, 12f); + Click(host, panel, position); + Assert.Equal("panel/diagnostics.console/entry", host.runtime.interactions.focusedArea); + Assert.NotNull(host.runtime.interactions.focusedTarget); + float collapsedHeight = card->Size.Y; + NativeImGui.Raw.AddKeyEvent(NativeImGui.Raw.GetIO(), ImGuiKey.Enter, 1); + host.DrawPanel(panel); + NativeImGui.Raw.AddKeyEvent(NativeImGui.Raw.GetIO(), ImGuiKey.Enter, 0); + host.DrawPanel(panel); + host.DrawPanel(panel); + Assert.True(card->Size.Y > collapsedHeight + 1f); + host.DrawPanel(panel, () => + { + var context = host.runtime.interactions.For(host.runtime.interactions.focusedArea, host.runtime.interactions.focusedTarget); + Assert.True(context.Execute("console/copy-message")); + Assert.Equal("Needle message", Marshal.PtrToStringUTF8((nint)NativeImGui.Raw.GetClipboardText())); + Assert.True(context.Execute("console/copy-details")); + string? details = Marshal.PtrToStringUTF8((nint)NativeImGui.Raw.GetClipboardText()); + Assert.Contains("Needle message", details); + Assert.Contains("(x2)", details); + }); + host.console.Clear(); + host.DrawPanel(panel); + Assert.Null(host.runtime.interactions.focusedTarget); + Assert.Equal(0, CountCards()); + } + + private static void Search( + NativePanelHost host, + EditorPanelExtension panel, + string query + ) { + ImGuiWindow* child = FindActiveWindow("ConsoleChild", "ConsoleRegion"); + Vector2 position = Unsafe.BitCast(child->DC.CursorStartPos) + new Vector2(80f, 10f); + Click(host, panel, position); + ImGuiIO* io = NativeImGui.Raw.GetIO(); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 1); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.A, 1); + host.DrawPanel(panel); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.A, 0); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 0); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Backspace, 1); + host.DrawPanel(panel); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Backspace, 0); + using NativeStringLease text = Utf8Codec.EncodeNullTerminated(query, allowsNull: false); + NativeImGui.Raw.AddInputCharactersUTF8(io, (sbyte*)text.pointer); + host.DrawPanel(panel); + host.DrawPanel(panel); + } + + private static void Click( + NativePanelHost host, + EditorPanelExtension panel, + Vector2 position + ) { + ImGuiIO* io = NativeImGui.Raw.GetIO(); + NativeImGui.Raw.AddMousePosEvent(io, position.X, position.Y); + host.DrawPanel(panel); + NativeImGui.Raw.AddMouseButtonEvent(io, 0, 1); + host.DrawPanel(panel); + NativeImGui.Raw.AddMouseButtonEvent(io, 0, 0); + host.DrawPanel(panel); + } + + private static int CountCards() + { + ImGuiNativeContext* context = NativeImGui.Raw.GetCurrentContext(); + int count = 0; + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + if (window->Active != 0 && Marshal.PtrToStringUTF8((nint)window->Name)?.Contains("##ConsoleEntryCard", StringComparison.Ordinal) == true) + count++; + } + return count; + } + + private static ImGuiWindow* FindActiveWindow( + string containing, + string? excluding = null + ) { + ImGuiNativeContext* context = NativeImGui.Raw.GetCurrentContext(); + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + string name = Marshal.PtrToStringUTF8((nint)window->Name) ?? string.Empty; + if (window->Active != 0 && name.Contains(containing, StringComparison.Ordinal) + && (excluding is null || !name.Contains(excluding, StringComparison.Ordinal))) + return window; + } + throw new InvalidOperationException("The actual control window was not submitted: " + containing); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleVirtualizationNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleVirtualizationNativeTests.cs new file mode 100644 index 000000000..2314c176b --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/ConsoleVirtualizationNativeTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Runtime.InteropServices; +using Inno.Core.Logging; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Xunit; +using Xunit.Abstractions; +using NativeGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class ConsoleVirtualizationNativeTests(ITestOutputHelper output) +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetiringTheContextRecordClosesItsPopup(bool clear) + { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.console.capacity = 16; + var logger = host.logs.CreateLogger(); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + logger.Write(LogLevel.Info, "Original context record"); + host.logs.Flush(); + host.DrawPanel(panel); + host.DrawPanel(panel); + ImGuiNativeContext* context = NativeGui.Raw.GetCurrentContext(); + ImGuiWindow* card = null; + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + if (window->Active != 0 && Marshal.PtrToStringUTF8((nint)window->Name)?.Contains("##ConsoleEntryCard", StringComparison.Ordinal) == true) + card = window; + } + Assert.True(card is not null); + ImGuiIO* io = NativeGui.Raw.GetIO(); + NativeGui.Raw.AddMousePosEvent(io, card->Pos.X + 230f, card->Pos.Y + 12f); + host.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(io, 1, 1); + host.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(io, 1, 0); + host.DrawPanel(panel); + Assert.True(context->OpenPopupStack.Size > 0); + if (clear) + host.console.Clear(); + else + { + for (int index = 0; index < host.console.capacity; index++) + logger.Write(LogLevel.Info, "Replacement context record " + index); + host.logs.Flush(); + } + Assert.DoesNotContain(host.console.Capture().occurrences, static entry => entry.message == "Original context record"); + for (int frame = 0; frame < 3; frame++) + host.DrawPanel(panel); + Assert.Equal(0, context->OpenPopupStack.Size); + } + + [Fact] + public void RepeatedContextMenusDoNotAccumulateRetiredOccurrenceWindows() + { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.console.capacity = 16; + var logger = host.logs.CreateLogger(); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + ImGuiIO* io = NativeGui.Raw.GetIO(); + for (int iteration = 0; iteration < 100; iteration++) + { + logger.Write(LogLevel.Info, "Context entry " + iteration); + host.logs.Flush(); + host.DrawPanel(panel); + host.DrawPanel(panel); + ImGuiWindow* card = null; + ImGuiNativeContext* context = NativeGui.Raw.GetCurrentContext(); + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + if (window->Active != 0 && Marshal.PtrToStringUTF8((nint)window->Name)?.Contains("##ConsoleEntryCard", StringComparison.Ordinal) == true) + card = window; + } + Assert.True(card is not null); + NativeGui.Raw.AddMousePosEvent(io, card->Pos.X + 230f, card->Pos.Y + 12f); + host.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(io, 1, 1); + host.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(io, 1, 0); + host.DrawPanel(panel); + Assert.True(context->OpenPopupStack.Size > 0); + NativePanelInput.Key(host, panel, ImGuiKey.Escape); + } + output.WriteLine("Context menus: 100; retained records: " + host.console.Capture().occurrences.Count + + "; native windows: " + NativeGui.Raw.GetCurrentContext()->Windows.Size); + Assert.InRange(NativeGui.Raw.GetCurrentContext()->Windows.Size, 1, 32); + } + + [Fact] + public void TenThousandOccurrencesSubmitOnlyViewportCardsAndKeepKeyboardNavigation() + { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.console.capacity = 10_000; + var logger = host.logs.CreateLogger(); + for (int index = 0; index < 10_000; index++) + { + logger.Write(LogLevel.Info, "Entry " + index.ToString("D5") + "\nDetails / 资源信息"); + if (index % 128 == 0) + host.logs.Flush(); + } + host.logs.Flush(); + Assert.Equal(10_000, host.console.Capture().occurrences.Count); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + for (int frame = 0; frame < 4; frame++) + host.DrawPanel(panel); + Assert.InRange(NativeGui.Raw.GetCurrentContext()->Windows.Size, 1, 128); + NativePanelInput.Focus(host, panel, "ConsoleRegion"); + NativePanelInput.Key(host, panel, ImGuiKey.Home); + NativePanelInput.Key(host, panel, ImGuiKey.Enter); + for (int frame = 0; frame < 3; frame++) + host.DrawPanel(panel); + Assert.InRange(NativeGui.Raw.GetCurrentContext()->Windows.Size, 1, 128); + } + + [Fact] + public void RetiredOccurrencesDoNotAccumulateNativeChildWindows() + { + using var host = new NativePanelHost("Inno.Editor.Panel.Logging"); + host.console.capacity = 16; + var logger = host.logs.CreateLogger(); + EditorPanelExtension panel = host.GetPanel("diagnostics.console"); + for (int iteration = 0; iteration < 150; iteration++) + { + logger.Write(LogLevel.Info, "Retained occurrence " + iteration); + host.logs.Flush(); + host.DrawPanel(panel); + host.DrawPanel(panel); + } + Assert.Equal(16, host.console.Capture().occurrences.Count); + output.WriteLine("Retained records: 16; completed updates: 150; native windows: " + NativeGui.Raw.GetCurrentContext()->Windows.Size); + Assert.InRange(NativeGui.Raw.GetCurrentContext()->Windows.Size, 1, 64); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/DefaultLayoutNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/DefaultLayoutNativeTests.cs new file mode 100644 index 000000000..be061f641 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/DefaultLayoutNativeTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Inno.Adapter.Modules.DotNet; +using Inno.Core.Input; +using Inno.Core.Logging; +using Inno.Editor.Core; +using Inno.Editor.ImGui; +using Inno.Extensibility.Catalogs; +using Inno.Extensibility.Modules; +using Inno.Extensibility.Types; +using Inno.Native.ImGui; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class DefaultLayoutNativeTests +{ + [Theory] + [InlineData(1280f, 720f)] + [InlineData(800f, 600f)] + [Trait("Acceptance", "Visual")] + public void StandardDockTreeLoadsAndResizesThroughThePresentationRuntime( + float width, + float height + ) { + using var host = new NativePanelHost(); + using var modules = new ModuleHost(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(DefaultLayoutNativeTests).Assembly) }); + using var types = new TypeCatalog(modules, new EmptyTypeSource()); + using var logs = new LogRouter(); + var context = new EditorContext(host.runtime.context.projectDirectory, new EditorKeyboardPolicy(KeyModifier.Control, "Ctrl")); + using var runtime = new ImGuiEditorRuntime(context, types, logs, []); + ImGuiIO* io = NativeGui.Raw.GetIO(); + io->ConfigFlags |= (int)ImGuiConfigFlags.DockingEnable; + io->DisplaySize = Unsafe.BitCast(new(1600f, 900f)); + byte[] layout = Encoding.UTF8.GetBytes(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "DefaultEditorLayout.ini")) + '\0'); + fixed (byte* pointer = layout) + NativeGui.Raw.LoadIniSettingsFromMemory((sbyte*)pointer, (ulong)(layout.Length - 1)); + runtime.Start(); + string[] titles = ["Scene", "Game", "File", "Hierarchy", "Inspector", "Shader Editor", "Console", "Stats"]; + var rectangles = new Dictionary(); + for (int frame = 0; frame < 12; frame++) + { + if (frame == 6) + io->DisplaySize = Unsafe.BitCast(new(width, height)); + host.Frame(() => + { + runtime.Draw(); + foreach (string title in titles) + { + bool open = true; + _ = NativeGui.Begin(title, ref open, 0); + try + { + rectangles[title] = (NativeGui.GetWindowPos(), NativeGui.GetWindowSize(), NativeGui.GetWindowDockID()); + } + finally + { + NativeGui.End(); + } + } + }); + } + foreach (var rectangle in rectangles.Values) + { + Assert.NotEqual(0u, rectangle.dock); + Assert.True(rectangle.size.X > 0f && rectangle.size.Y > 0f); + Assert.InRange(rectangle.position.X, -1f, width); + Assert.InRange(rectangle.position.Y, -1f, height); + Assert.InRange(rectangle.position.X + rectangle.size.X, 0f, width + 1f); + Assert.InRange(rectangle.position.Y + rectangle.size.Y, 0f, height + 1f); + } + Assert.Equal(rectangles["Inspector"].dock, rectangles["Shader Editor"].dock); + Assert.Equal(rectangles["Console"].dock, rectangles["Stats"].dock); + Assert.True(rectangles["Scene"].position.X < rectangles["Game"].position.X); + Assert.True(rectangles["File"].position.Y > rectangles["Scene"].position.Y); + Assert.True(rectangles["Hierarchy"].position.X > rectangles["File"].position.X); + Assert.True(rectangles["Inspector"].position.X > rectangles["Hierarchy"].position.X); + Assert.True(rectangles["Console"].position.Y > rectangles["Inspector"].position.Y); + Assert.InRange(rectangles["File"].size.X / width, 0.43f, 0.49f); + Assert.InRange(rectangles["Hierarchy"].size.X / width, 0.25f, 0.30f); + Assert.InRange(rectangles["Inspector"].size.X / width, 0.25f, 0.30f); + } + + private sealed class EmptyTypeSource : ITypeCatalogSource + { + public IReadOnlyList GetTypes(Assembly assembly) => []; + public TypeCatalogMetadata GetMetadata(Type type) => throw new NotSupportedException(); + public Type? ConstructGenericType( + Type definition, + IReadOnlyList arguments + ) => throw new NotSupportedException(); + public bool CanCreateInstance(Type type) => false; + public object CreateInstance(Type type) => throw new NotSupportedException(); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/EditorLayoutInitializationTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/EditorLayoutInitializationTests.cs new file mode 100644 index 000000000..6ba5a874a --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/EditorLayoutInitializationTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Inno.Core.IO; +using Inno.Core.Input; +using Inno.Editor.Core; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed class EditorLayoutInitializationTests : IDisposable +{ + private readonly string m_owner = Path.Combine(Path.GetTempPath(), "InnoInitialLayoutTests"); + private readonly string m_root; + + public EditorLayoutInitializationTests() + { + m_root = PathBoundary.Resolve(m_owner, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(m_root); + } + + [Fact] + public void MissingProjectDocumentReceivesOnlyTheStandardLayout() + { + string template = ReadTemplate(); + EditorContext context = CreateContext(template); + Assert.Equal(template, File.ReadAllText(context.layoutPath)); + Assert.False(context.SaveLayoutIfChanged()); + Assert.Equal(["Panels"], context.GetLayoutSectionNames()); + Assert.True(context.TryGetLayoutSection("Panels", out var panels)); + Assert.Equal(8, panels.Count); + Assert.All(panels.Values, value => Assert.Equal("true", value)); + Assert.DoesNotContain("Module.", template, StringComparison.Ordinal); + Assert.DoesNotContain("FlappyBird", template, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("Sprite Animation", template, StringComparison.Ordinal); + Assert.DoesNotContain("Compiling Scripts", template, StringComparison.Ordinal); + Assert.DoesNotContain("Export as", template, StringComparison.Ordinal); + Assert.DoesNotContain("Debug##", template, StringComparison.Ordinal); + } + + [Theory] + [InlineData("")] + [InlineData("[Window][User]\nSize=123,456\n\n[InnoEditor][Panels]\nscene.hierarchy=false\n")] + public void ExistingDocumentIsAuthoritativeIncludingAnIntentionallyEmptyFile(string document) + { + string path = Path.Combine(m_root, "editor.ini"); + File.WriteAllText(path, document); + DateTime timestamp = new(2020, 2, 3, 4, 5, 6, DateTimeKind.Utc); + File.SetLastWriteTimeUtc(path, timestamp); + byte[] bytes = File.ReadAllBytes(path); + EditorContext context = CreateContext(ReadTemplate()); + Assert.Equal(bytes, File.ReadAllBytes(path)); + Assert.Equal(timestamp, File.GetLastWriteTimeUtc(path)); + Assert.False(context.SaveLayoutIfChanged()); + Assert.Equal(bytes, File.ReadAllBytes(path)); + } + + [Fact] + public void ConcurrentInitializersNeverReplaceTheWinningDocument() + { + var layouts = new ConcurrentBag(); + Parallel.For(0, 16, index => + { + string document = $"[Window][Creator {index}]\nSize=400,300\n"; + layouts.Add(CreateContext(document).imguiLayout); + }); + string winner = CreateContext(null).imguiLayout; + Assert.All(layouts, value => Assert.Equal(winner, value)); + Assert.Single(Directory.GetFiles(m_root)); + } + + [Fact] + public void FailedCreationDoesNotLeaveAPartialFileOrStagingPayload() + { + Directory.CreateDirectory(Path.Combine(m_root, "editor.ini")); + Assert.ThrowsAny(() => CreateContext(ReadTemplate())); + Assert.Empty(Directory.GetFiles(m_root)); + } + + [Fact] + public void OrdinarySaveAndReopenRetainLayoutAndIndependentStateSections() + { + EditorContext context = CreateContext(ReadTemplate()); + context.SetLayoutSection("Module.fixture", new Dictionary { ["value"] = "42" }); + context.SetImGuiLayout("[Window][User layout]\nPos=10,20\nSize=600,400\n"); + context.SaveLayout(); + byte[] saved = File.ReadAllBytes(context.layoutPath); + EditorContext reopened = CreateContext(ReadTemplate()); + Assert.Contains("[Window][User layout]", reopened.imguiLayout); + Assert.True(reopened.TryGetLayoutSection("Module.fixture", out var values)); + Assert.Equal("42", values["value"]); + Assert.True(reopened.TryGetLayoutSection("Panels", out var panels)); + Assert.Equal(8, panels.Count); + Assert.Equal(saved, File.ReadAllBytes(reopened.layoutPath)); + Assert.Single(Directory.GetFiles(m_root)); + } + + [Fact] + public void AContextWithoutACompositionDefaultRemainsPassive() + { + EditorContext context = CreateContext(null); + Assert.False(File.Exists(context.layoutPath)); + Assert.Empty(context.imguiLayout); + Assert.Empty(context.GetLayoutSectionNames()); + } + + public void Dispose() => Directory.Delete(PathBoundary.RequireUnlinkedPath(m_owner, m_root), recursive: true); + + private EditorContext CreateContext(string? initialDocument) => new( + m_root, new EditorKeyboardPolicy(KeyModifier.Control, "Ctrl"), initialDocument); + + private static string ReadTemplate() => File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "DefaultEditorLayout.ini")); +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/EditorPlayModeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/EditorPlayModeTests.cs index 280d6de42..51b079158 100644 --- a/tests/editor/Inno.Editor.PlayMode.Tests/EditorPlayModeTests.cs +++ b/tests/editor/Inno.Editor.PlayMode.Tests/EditorPlayModeTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Core.Identity; @@ -27,25 +26,21 @@ using Xunit; namespace Inno.Editor.PlayMode.Tests; - public sealed class EditorPlayModeTests : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoEditorPlayModeTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoEditorPlayModeTests", Guid.NewGuid().ToString("N")); private readonly EngineHost m_engineHost; private readonly RuntimeSession m_editSession; private readonly AssetPipeline m_authoringAssets; private readonly IDisposable m_editScope; - public EditorPlayModeTests() { Directory.CreateDirectory(Path.Combine(m_projectRoot, "Assets")); - m_engineHost = new EngineHostBuilder() - .UseMetadataSources(new DotNetAssemblyCatalogSource(typeof(EditorPlayModeTests).Assembly), - new ReflectionTypeCatalogSource(), new ReflectionSerializationMetadataSource()) - .Build(); + m_engineHost = new EngineHostBuilder().UseMetadataSources( + new DotNetAssemblyCatalogSource(typeof(EditorPlayModeTests).Assembly), + new ReflectionTypeCatalogSource(), + new ReflectionSerializationMetadataSource() + ).Build(); m_editSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Edit)); m_editScope = m_editSession.EnterExecutionScope(); m_authoringAssets = new AssetPipeline( @@ -55,12 +50,8 @@ public EditorPlayModeTests() new IdentityAllocator(), m_engineHost.diagnostics, m_engineHost.logs, - AssetPipelineOptions.Create( - Path.Combine(m_projectRoot, "Assets"), - Path.Combine(m_projectRoot, "Library")) with - { - enableFileSystemWatcher = false - }); + AssetPipelineOptions.Create(Path.Combine(m_projectRoot, "Assets"), Path.Combine(m_projectRoot, "Library"))with { enableFileSystemWatcher = false } + ); } public void Dispose() @@ -73,25 +64,38 @@ public void Dispose() Directory.Delete(m_projectRoot, recursive: true); } + [Fact] + public void StateSubscriptionsNotifyAllObserversAndReleaseIndependently() + { + using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, new FakeScriptCompilation(EditorScriptCompilationState.Ready), new FakeScenePlayMode()); + int observed = 0; + using IDisposable failing = harness.playMode.ListenStateChanges(_ => throw new InvalidOperationException("Rejected observer.")); + using IDisposable healthy = harness.playMode.ListenStateChanges(_ => observed++); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + AggregateException error = Assert.Throws(() => harness.playMode.EnterPlayMode()); + Assert.Single(error.InnerExceptions); + Assert.Equal(1, observed); + Assert.Equal(EditorPlayModeState.Compiling, harness.playMode.state); + failing.Dispose(); + healthy.Dispose(); + Assert.True(harness.playMode.ExitPlayMode()); + Assert.Equal(1, observed); + } + [Fact] public void PlayShortcutStopsTheSessionFromAFocusedGameView() { - using var harness = new PlayModeHarness( - m_projectRoot, - m_engineHost, - new FakeScriptCompilation(EditorScriptCompilationState.Ready), - new FakeScenePlayMode()); - using var modules = new Inno.Extensibility.Modules.ModuleHost( - new Inno.Extensibility.Modules.ModuleHostOptions - { - catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorPlayModeController).Assembly) - }); + using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, new FakeScriptCompilation(EditorScriptCompilationState.Ready), new FakeScenePlayMode()); + using var modules = new Inno.Extensibility.Modules.ModuleHost(new Inno.Extensibility.Modules.ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(EditorPlayModeController).Assembly) }); using var types = new TypeCatalog(modules, new ReflectionTypeCatalogSource()); using var interactions = new EditorInteractionRuntime( new EditorContext(m_projectRoot, new EditorKeyboardPolicy(KeyModifier.Control, "Super")), types, m_engineHost.logs, - [harness.playMode]); + [harness.playMode] + ); interactions.Start(); Assert.True(harness.playMode.EnterPlayMode()); harness.Update(); @@ -134,8 +138,7 @@ public void ReloadGuardPreservesStartupCompilationAndPinsThePlayingGeneration() [Fact] public void ReleasingGuardRegistrationDoesNotRetainItsBlockingState() { - using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, - new FakeScriptCompilation(EditorScriptCompilationState.Compiling), new FakeScenePlayMode()); + using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, new FakeScriptCompilation(EditorScriptCompilationState.Compiling), new FakeScenePlayMode()); var reloads = new EditorReloadCoordinator(); IDisposable registration = reloads.RegisterGuard((IEditorReloadGuard)harness.playMode); Assert.True(harness.playMode.EnterPlayMode()); @@ -155,18 +158,14 @@ public void EntryWaitsForCompilationAndExitRestoresEditingHistory() harness.Update(); Assert.Equal(EditorPlayModeState.Compiling, harness.playMode.state); Assert.Equal(0, scenes.beginCount); - scripting.state = EditorScriptCompilationState.Ready; harness.Update(); Assert.Equal(EditorPlayModeState.Preparing, harness.playMode.state); harness.Update(); - Assert.True( - harness.playMode.state == EditorPlayModeState.Playing, - harness.playMode.lastFailure); + Assert.True(harness.playMode.state == EditorPlayModeState.Playing, harness.playMode.lastFailure); Assert.Equal(1, scenes.beginCount); Assert.Equal(1, harness.history.beginCount); Assert.Equal(0, harness.history.disposeCount); - Assert.True(harness.playMode.ExitPlayMode()); harness.Update(); Assert.Equal(EditorPlayModeState.Editing, harness.playMode.state); @@ -197,6 +196,7 @@ public void PendingStopRetainsHistoryAndBlocksNewGenerationUntilWorkDrains(bool Assert.True(harness.playMode.ExitPlayMode()); harness.Update(); } + Assert.Equal(EditorPlayModeState.Stopping, harness.playMode.state); Assert.Equal(0, harness.history.disposeCount); Assert.Throws(() => m_engineHost.generations.EnsureReady("reload while stopping")); @@ -223,10 +223,8 @@ public void CompilationFailureReturnsToEditWithoutReplacingScenes() var scripting = new FakeScriptCompilation(EditorScriptCompilationState.Failed); var scenes = new FakeScenePlayMode(); using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, scripting, scenes); - Assert.True(harness.playMode.EnterPlayMode()); harness.Update(); - Assert.Equal(EditorPlayModeState.Failed, harness.playMode.state); Assert.Contains("valid script generation", harness.playMode.lastFailure, StringComparison.OrdinalIgnoreCase); Assert.Equal(0, scenes.beginCount); @@ -238,11 +236,9 @@ public void EnteringPlayCanBeCancelledBeforeScriptsBecomeReady() var scripting = new FakeScriptCompilation(EditorScriptCompilationState.Compiling); var scenes = new FakeScenePlayMode(); using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, scripting, scenes); - Assert.True(harness.playMode.EnterPlayMode()); Assert.True(harness.playMode.ExitPlayMode()); harness.Update(); - Assert.Equal(EditorPlayModeState.Editing, harness.playMode.state); Assert.Equal(0, scenes.beginCount); } @@ -259,15 +255,12 @@ public void HostLoopDispatchesGameLifecycleOnlyWhilePlaying() world.LoadScene(scene); }); using var harness = new PlayModeHarness(m_projectRoot, m_engineHost, scripting, scenes); - harness.Simulate(0.016f); Assert.Null(system); - Assert.True(harness.playMode.EnterPlayMode()); harness.Update(); harness.Update(); harness.Simulate(0.02f); - Assert.NotNull(system); Assert.Equal(1, system.fixedCount); Assert.Equal(1, system.updateCount); @@ -288,11 +281,9 @@ public void SimulationFailureRequestsAndCompletesSafeEditRestoration() Assert.True(harness.playMode.EnterPlayMode()); harness.Update(); harness.Update(); - harness.Simulate(0.016f); Assert.Equal(EditorPlayModeState.Stopping, harness.playMode.state); harness.Update(); - Assert.Equal(EditorPlayModeState.Editing, harness.playMode.state); Assert.Equal(1, scenes.restoreCount); Assert.Contains("update failed", harness.playMode.lastFailure, StringComparison.OrdinalIgnoreCase); @@ -308,7 +299,8 @@ public void SceneSessionRestoresGraphIdentitySelectionAndEditValues() m_engineHost.types, m_engineHost.serialization, m_engineHost.logs, - selection); + selection + ); IEditorSceneWorkspace workspace = workspaceHost.workspace; var editScene = new GameScene("Edit Scene"); GameObject editObject = editScene.CreateObject("Edit Object"); @@ -316,9 +308,7 @@ public void SceneSessionRestoresGraphIdentitySelectionAndEditValues() Guid objectId = editObject.identity.persistentId; SceneManager.LoadScene(editScene); selection.SetSelection(editObject); - - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); using IDisposable session = workspaceHost.playMode.BeginPlayMode(runtimeSession); GameScene runtimeScene; GameObject runtimeObject; @@ -327,6 +317,7 @@ public void SceneSessionRestoresGraphIdentitySelectionAndEditValues() runtimeScene = Assert.Single(SceneManager.loadedScenes); runtimeObject = Assert.Single(runtimeScene.GetObjects()); } + Assert.NotSame(editScene, runtimeScene); Assert.NotSame(editObject, runtimeObject); Assert.Equal(sceneId, runtimeScene.identity.persistentId); @@ -341,11 +332,10 @@ public void SceneSessionRestoresGraphIdentitySelectionAndEditValues() runtimeObject.name = "Runtime Object"; _ = runtimeScene.CreateObject("Runtime Only"); } + Assert.False(workspace.IsDirty(runtimeScene)); Assert.Throws(() => workspace.Save(runtimeScene, string.Empty)); - session.Dispose(); - GameScene restoredScene = Assert.Single(SceneManager.loadedScenes); GameObject restoredObject = Assert.Single(restoredScene.GetObjects()); Assert.Same(editScene, restoredScene); @@ -366,12 +356,11 @@ public void DeletingTheLastRuntimeSceneRestoresTheEditSceneOnExit() m_authoringAssets, m_engineHost.types, m_engineHost.serialization, - m_engineHost.logs); + m_engineHost.logs + ); var editScene = new GameScene("Edit Scene"); SceneManager.LoadScene(editScene); - - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); IDisposable playLease = workspaceHost.playMode.BeginPlayMode(runtimeSession); using (runtimeSession.EnterExecutionScope()) { @@ -382,7 +371,6 @@ public void DeletingTheLastRuntimeSceneRestoresTheEditSceneOnExit() } playLease.Dispose(); - Assert.Same(editScene, Assert.Single(SceneManager.loadedScenes)); Assert.Same(editScene, Assert.Single(workspaceHost.workspace.scenes)); Assert.True(workspaceHost.workspace.canPersist); @@ -397,29 +385,21 @@ public void SceneSessionMaterializesSerializedAssetReferences() AssetPath assetPath = AssetPath.Project("Text/shared.txt"); Assert.True(m_authoringAssets.Import(assetPath)); TextAsset asset = m_authoringAssets.Load(assetPath); - using EditorSceneWorkspaceHost workspaceHost = EditorSceneWorkspaceFactory.Create( m_editSession, m_authoringAssets, m_engineHost.types, m_engineHost.serialization, - m_engineHost.logs); + m_engineHost.logs + ); var editScene = new GameScene("Asset Reference Scene"); - editScene.CreateObject("Asset Reference") - .AddComponent() - .asset = asset; + editScene.CreateObject("Asset Reference").AddComponent().asset = asset; SceneManager.LoadScene(editScene); - - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); using IDisposable playLease = workspaceHost.playMode.BeginPlayMode(runtimeSession); using IDisposable runtimeScope = runtimeSession.EnterExecutionScope(); - GameObject runtimeObject = Assert.Single( - Assert.Single(SceneManager.loadedScenes).GetObjects()); - TextAsset? runtimeAsset = runtimeObject - .GetComponent() - .asset; - + GameObject runtimeObject = Assert.Single(Assert.Single(SceneManager.loadedScenes).GetObjects()); + TextAsset? runtimeAsset = runtimeObject.GetComponent().asset; Assert.Same(asset, runtimeAsset); } @@ -431,42 +411,30 @@ public void GamePresentationSwitchesFromEditToPlayAndBackWithoutSharingObjects() m_authoringAssets, m_engineHost.types, m_engineHost.serialization, - m_engineHost.logs); + m_engineHost.logs + ); IEditorGameScenePresentation presentation = workspaceHost.gamePresentation; var editScene = new GameScene("Edit Presentation"); GameObject editObject = editScene.CreateObject("Edit Object"); SceneManager.LoadScene(editScene); - using ContentReadScope editing = presentation.Capture(); Assert.Same(editScene, Assert.Single(editing.GetValues())); Assert.Equal(editScene.identity.persistentId, editing.activeContent); - - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); IDisposable playLease = workspaceHost.playMode.BeginPlayMode(runtimeSession); using ContentReadScope playing = presentation.Capture(); GameScene runtimeScene = Assert.Single(playing.GetValues()); GameObject runtimeObject = Assert.Single(runtimeScene.GetObjects()); - Assert.NotSame(editScene, runtimeScene); Assert.NotSame(editObject, runtimeObject); Assert.Equal(runtimeScene.identity.persistentId, playing.activeContent); runtimeObject.name = "Runtime Object"; - runtimeObject.transform.localRotation = Quaternion.FromEulerAnglesXYZDegrees( - new Vector3(0f, 0f, 90f)); - Assert.Equal( - "Runtime Object", - Assert.Single(Assert.Single(playing.GetValues()).GetObjects()).name); - Assert.Equal( - 90f, - Assert.Single(Assert.Single(playing.GetValues()).GetObjects()) - .transform.localRotation.ToEulerAnglesXYZDegrees().z, - precision: 3); + runtimeObject.transform.localRotation = Quaternion.FromEulerAnglesXYZDegrees(new Vector3(0f, 0f, 90f)); + Assert.Equal("Runtime Object", Assert.Single(Assert.Single(playing.GetValues()).GetObjects()).name); + Assert.Equal(90f, Assert.Single(Assert.Single(playing.GetValues()).GetObjects()).transform.localRotation.ToEulerAnglesXYZDegrees().z, precision: 3); Assert.Equal("Edit Object", editObject.name); Assert.Equal(Quaternion.identity, editObject.transform.localRotation); - playLease.Dispose(); - using ContentReadScope restored = presentation.Capture(); Assert.Same(editScene, Assert.Single(restored.GetValues())); Assert.Equal(editScene.identity.persistentId, restored.activeContent); @@ -481,19 +449,15 @@ public void RejectedPlayWorldDoesNotReplaceTheEditPresentation() m_authoringAssets, m_engineHost.types, m_engineHost.serialization, - m_engineHost.logs); + m_engineHost.logs + ); IEditorGameScenePresentation presentation = workspaceHost.gamePresentation; var editScene = new GameScene("Edit Presentation"); SceneManager.LoadScene(editScene); - - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); using (runtimeSession.EnterExecutionScope()) runtimeSession.scenes.LoadScene(new GameScene("Unexpected Existing Runtime Scene")); - - Assert.Throws( - () => workspaceHost.playMode.BeginPlayMode(runtimeSession)); - + Assert.Throws(() => workspaceHost.playMode.BeginPlayMode(runtimeSession)); using ContentReadScope snapshot = presentation.Capture(); Assert.Same(editScene, Assert.Single(snapshot.GetValues())); Assert.Equal(editScene.identity.persistentId, snapshot.activeContent); @@ -504,20 +468,21 @@ public void RejectedPlayWorldDoesNotReplaceTheEditPresentation() public void ScenePresentationScopeRejectsStalePlayRootsAfterStop() { using EditorSceneWorkspaceHost workspaceHost = EditorSceneWorkspaceFactory.Create( - m_editSession, m_authoringAssets, m_engineHost.types, - m_engineHost.serialization, m_engineHost.logs); + m_editSession, + m_authoringAssets, + m_engineHost.types, + m_engineHost.serialization, + m_engineHost.logs + ); var editScene = new GameScene("Identity Presentation"); SceneManager.LoadScene(editScene); - using RuntimeSession runtimeSession = m_engineHost.CreateSession( - CreateSessionOptions(RuntimeSessionKind.Play)); + using RuntimeSession runtimeSession = m_engineHost.CreateSession(CreateSessionOptions(RuntimeSessionKind.Play)); IDisposable playLease = workspaceHost.playMode.BeginPlayMode(runtimeSession); using ContentReadScope playing = workspaceHost.gamePresentation.Capture(); Identity captured = Assert.Single(playing.contents); Assert.Equal(editScene.identity.persistentId, captured.persistentId); Assert.NotEqual(editScene.identity.runtimeIdentity, captured.runtimeIdentity); - playLease.Dispose(); - runtimeSession.Dispose(); Assert.Empty(playing.GetValues()); Assert.False(playing.TryGetValue(captured.persistentId, out _)); @@ -540,60 +505,58 @@ public sealed class CountingSystem : GameSystem public sealed class ThrowingSystem : GameSystem { - protected override void OnUpdate() - => throw new InvalidOperationException("Injected simulation failure."); + protected override void OnUpdate() => throw new InvalidOperationException("Injected simulation failure."); } private sealed class PlayModeHarness : IDisposable { private readonly EditorPlayModeController m_controller; private readonly FakeHistoryIsolation m_history = new(); - internal PlayModeHarness( string projectRoot, EngineHost engineHost, IEditorScriptCompilation scripting, IEditorScenePlayMode scenes, - IRuntimeSubsystemFactory? factory = null) - { + IRuntimeSubsystemFactory? factory = null + ) { m_controller = new EditorPlayModeController( engineHost, - new RuntimeSessionOptions - { + new RuntimeSessionOptions { kind = RuntimeSessionKind.Play, applicationId = "inno.tests.play", - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine( - projectRoot, - "PersistentData", - "inno.tests.play"), "Logs")), + createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(projectRoot, "PersistentData", "inno.tests.play"), "Logs")), jobExecutionMode = RuntimeJobExecutionMode.SingleThread, createSubsystems = _ => factory is null ? [] : [factory] }, scenes, scripting, m_history, - engineHost.logs); + engineHost.logs + ); } internal IEditorPlayMode playMode => m_controller; internal FakeHistoryIsolation history => m_history; internal void Update() => m_controller.AdvanceTransition(); - internal void Simulate(float deltaTime) => m_controller.Tick(deltaTime); - public void Dispose() { m_controller.Dispose(); } } - private sealed class FakeScriptCompilation(EditorScriptCompilationState initialState) - : IEditorScriptCompilation + private sealed class FakeScriptCompilation(EditorScriptCompilationState initialState) : IEditorScriptCompilation { + public bool requiresExclusiveProgress => false; + public float progress => 0f; + + public void CancelCompilation() + { + } + private EditorScriptCompilationState m_state = initialState; private FakeCompilationTicket? m_ticket; - public IScriptCompilationTicket RequestCompilation() { m_ticket = new FakeCompilationTicket(); @@ -620,12 +583,14 @@ public EditorScriptCompilationState state private sealed class PendingPlayFactory(PendingPlaySubsystem subsystem) : IRuntimeSubsystemFactory { public RuntimeSubsystemDescriptor descriptor { get; } = new(new RuntimeSubsystemId("test.pending-play")); + public IRuntimeSubsystem Create(RuntimeSubsystemContext context) => subsystem; } private sealed class PendingPlaySubsystem : RuntimeSubsystem { internal TaskCompletionSource work { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); + internal bool stopped; protected override void OnStart() => lifetime.Track(work.Task); protected override void OnStop() => stopped = true; @@ -634,18 +599,10 @@ private sealed class PendingPlaySubsystem : RuntimeSubsystem private sealed class FakeCompilationTicket : IScriptCompilationTicket { public long requestId => 1; - public ScriptCompilationTicketState state { get; private set; } - public string status => "Test compilation ticket."; - public ScriptCompilationResult? result => null; - - public bool isCompleted - => state is ScriptCompilationTicketState.Succeeded - or ScriptCompilationTicketState.Failed - or ScriptCompilationTicketState.Canceled - or ScriptCompilationTicketState.Superseded; + public bool isCompleted => state is ScriptCompilationTicketState.Succeeded or ScriptCompilationTicketState.Failed or ScriptCompilationTicketState.Canceled or ScriptCompilationTicketState.Superseded; internal void SetState(EditorScriptCompilationState value) { @@ -673,7 +630,6 @@ public IDisposable BeginPlayMode(RuntimeSession runtimeSession) private sealed class Session(FakeScenePlayMode owner) : IDisposable { private bool m_restored; - public void Dispose() { if (m_restored) @@ -686,6 +642,7 @@ public void Dispose() private sealed class FakeSelectionCoordinator : IEditorSelectionCoordinator { + public IDisposable RegisterIdentityDomain(Inno.Core.Identity.IdentityAllocator allocator) => new Inno.Core.Execution.LifetimeScope(); public object? selectedTarget { get; private set; } public void SetSelection(object? target) => selectedTarget = target; @@ -694,7 +651,6 @@ private sealed class FakeSelectionCoordinator : IEditorSelectionCoordinator private sealed class FakeHistoryIsolation : IEditorHistoryIsolation { internal int beginCount { get; private set; } - internal int disposeCount { get; private set; } public IDisposable BeginHistoryIsolation() @@ -706,7 +662,6 @@ public IDisposable BeginHistoryIsolation() private sealed class Lease(FakeHistoryIsolation owner) : IDisposable { private bool m_disposed; - public void Dispose() { if (m_disposed) @@ -719,17 +674,12 @@ public void Dispose() private RuntimeSessionOptions CreateSessionOptions(RuntimeSessionKind kind) { - string applicationId = kind == RuntimeSessionKind.Edit - ? "inno.tests.edit" - : "inno.tests.play"; + string applicationId = kind == RuntimeSessionKind.Edit ? "inno.tests.edit" : "inno.tests.play"; return new RuntimeSessionOptions { kind = kind, applicationId = applicationId, - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine( - m_projectRoot, - "PersistentData", - applicationId), "Logs")), + createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(m_projectRoot, "PersistentData", applicationId), "Logs")), jobExecutionMode = RuntimeJobExecutionMode.SingleThread }; } diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/EditorPopupLayoutTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/EditorPopupLayoutTests.cs index 49ad0887b..26d174611 100644 --- a/tests/editor/Inno.Editor.PlayMode.Tests/EditorPopupLayoutTests.cs +++ b/tests/editor/Inno.Editor.PlayMode.Tests/EditorPopupLayoutTests.cs @@ -1,16 +1,15 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; using System; using System.Numerics; - using Inno.Native.ImGui; using EditorWidget = Inno.Editor.ImGui.ImGuiWidget.ImGuiWidget; using NativeImGui = Inno.Native.ImGui.ImGui; using Xunit; namespace Inno.Editor.PlayMode.Tests; - public sealed unsafe class EditorPopupLayoutTests { [Fact] @@ -19,8 +18,10 @@ public void ContextMenuSearchUsesCompletePopupContentWidth() ImGuiNativeContext* context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ConfigureContext(); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.SetupStyle(); string query = string.Empty; for (int frame = 0; frame < 3; frame++) { @@ -30,19 +31,19 @@ public void ContextMenuSearchUsesCompletePopupContentWidth() _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText0.pointer, null, 0); _ = NativeImGui.Button("Target"); NativeImGui.OpenPopup("commands"); - - Assert.True(EditorWidget.BeginContextMenu("commands")); - _ = EditorWidget.SearchInput("commands", "Search commands...", ref query); + Assert.True(EditorUI.current.widgets.BeginContextMenu("commands")); + _ = EditorUI.current.widgets.SearchInput("commands", "Search commands...", ref query); if (frame == 2) { float inputWidth = NativeImGui.GetItemRectSize().X; float contentWidth = NativeImGui.GetContentRegionAvail().X; - Assert.True(inputWidth >= EditorWidget.style.searchPopupWidth - 1f); + Assert.True(inputWidth >= EditorUI.current.widgets.style.searchPopupWidth - 1f); Assert.InRange(MathF.Abs(inputWidth - contentWidth), 0f, 2f); Assert.InRange(NativeImGui.GetScrollMaxX(), 0f, 1f); } + NativeImGui.Selectable("Rename"); - EditorWidget.EndContextMenu(); + EditorUI.current.widgets.EndContextMenu(); NativeImGui.End(); NativeImGui.Render(); } @@ -59,9 +60,10 @@ public void TwoItemSelectorOpensBelowControlWithoutOverflow() ImGuiNativeContext* context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ConfigureContext(); - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); for (int frame = 0; frame < 3; frame++) { NativeImGui.NewFrame(); @@ -72,8 +74,7 @@ public void TwoItemSelectorOpensBelowControlWithoutOverflow() NativeImGui.SetNextItemWidth(140f); float triggerBottom = NativeImGui.GetCursorScreenPos().Y + NativeImGui.GetFrameHeight(); NativeImGui.OpenPopup("##menu_selector_popup_tag"); - - Assert.True(EditorWidget.BeginBoundedCombo("tag", "Untagged")); + Assert.True(EditorUI.current.widgets.BeginBoundedCombo("tag", "Untagged")); Vector2 popupPosition = NativeImGui.GetWindowPos(); Vector2 popupSize = NativeImGui.GetWindowSize(); _ = NativeImGui.Selectable("Untagged"); @@ -85,7 +86,8 @@ public void TwoItemSelectorOpensBelowControlWithoutOverflow() Assert.True(popupPosition.Y + popupSize.Y <= 270f + 1f); Assert.InRange(NativeImGui.GetScrollMaxY(), 0f, 1f); } - EditorWidget.EndBoundedCombo(); + + EditorUI.current.widgets.EndBoundedCombo(); NativeImGui.End(); NativeImGui.Render(); } @@ -102,8 +104,10 @@ public void LongSelectorNearWindowBottomScrollsWithoutOpeningUpward() ImGuiNativeContext* context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ConfigureContext(); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.SetupStyle(); for (int frame = 0; frame < 3; frame++) { NativeImGui.NewFrame(); @@ -115,8 +119,7 @@ public void LongSelectorNearWindowBottomScrollsWithoutOpeningUpward() float triggerBottom = NativeImGui.GetCursorScreenPos().Y + NativeImGui.GetFrameHeight(); NativeImGui.SetNextItemWidth(140f); NativeImGui.OpenPopup("##menu_selector_popup_long"); - - Assert.True(EditorWidget.BeginBoundedCombo("long", "Choose")); + Assert.True(EditorUI.current.widgets.BeginBoundedCombo("long", "Choose")); Vector2 popupPosition = NativeImGui.GetWindowPos(); Vector2 popupSize = NativeImGui.GetWindowSize(); for (int index = 0; index < 40; index++) @@ -127,7 +130,8 @@ public void LongSelectorNearWindowBottomScrollsWithoutOpeningUpward() Assert.True(popupPosition.Y + popupSize.Y <= 270f + 1f); Assert.True(NativeImGui.GetScrollMaxY() > 0f); } - EditorWidget.EndBoundedCombo(); + + EditorUI.current.widgets.EndBoundedCombo(); NativeImGui.End(); NativeImGui.Render(); } @@ -144,8 +148,10 @@ public void SelectorInsideLayoutChildUsesContainingPanelBounds() ImGuiNativeContext* context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ConfigureContext(); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.SetupStyle(); for (int frame = 0; frame < 3; frame++) { NativeImGui.NewFrame(); @@ -156,8 +162,7 @@ public void SelectorInsideLayoutChildUsesContainingPanelBounds() _ = NativeImGui.BeginChild("Fields", new Vector2(200f, 55f), 0, 0); NativeImGui.SetNextItemWidth(140f); NativeImGui.OpenPopup("##menu_selector_popup_nested"); - - Assert.True(EditorWidget.BeginBoundedCombo("nested", "Choose")); + Assert.True(EditorUI.current.widgets.BeginBoundedCombo("nested", "Choose")); Vector2 popupPosition = NativeImGui.GetWindowPos(); Vector2 popupSize = NativeImGui.GetWindowSize(); for (int index = 0; index < 40; index++) @@ -167,7 +172,8 @@ public void SelectorInsideLayoutChildUsesContainingPanelBounds() Assert.True(popupSize.Y > 55f); Assert.True(popupPosition.Y + popupSize.Y <= 270f + 1f); } - EditorWidget.EndBoundedCombo(); + + EditorUI.current.widgets.EndBoundedCombo(); NativeImGui.EndChild(); NativeImGui.End(); NativeImGui.Render(); diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/GraphCanvasNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/GraphCanvasNativeTests.cs new file mode 100644 index 000000000..e1f4b6233 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/GraphCanvasNativeTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; +using System.Numerics; +using Inno.Core.Graphs; +using Inno.Editor.Graph; +using Inno.Editor.Graph.ImGui; +using Inno.Native.ImGui; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class GraphCanvasNativeTests +{ + [Fact] + public void NonShaderPointerWorkflowUsesTheRealDocumentHistory() + { + using var host = new NativeProjectPanelHost(); + var document = new GraphDocument(); + var first = new GraphNodeRecord(new("workflow.first"), "workflow.step") { position = new(20f, 20f) }; + var second = new GraphNodeRecord(new("workflow.second"), "workflow.step") { position = new(300f, 20f) }; + document.AddNode(first); + document.AddNode(second); + var history = host.runtime.interactions.history; + var controller = host.graphs.OpenDocument(Guid.NewGuid(), document, history); + var state = new GraphCanvasState(); + var pointer = new GraphCanvasPointerController(state); + var layout = new GraphCanvasLayout(); + Vector2 origin = default; + Frame(new(0f, 0f), false); + Frame(new(0f, 0f), false); + Vector2 hit = origin + new Vector2(30f, 25f); + Frame(hit, true); + Frame(hit + new Vector2(50f, 40f), true); + Frame(hit + new Vector2(50f, 40f), false); + Assert.Equal(new GraphPosition(70f, 60f), controller.document.FindNode(first.id)!.position); + Assert.True(history.Undo().succeeded); + Assert.Equal(new GraphPosition(20f, 20f), controller.document.FindNode(first.id)!.position); + Assert.True(history.Redo().succeeded); + Frame(origin + new Vector2(270f, 100f), true); + Frame(origin + new Vector2(300f, 60f), false); + GraphEdgeRecord edge = Assert.Single(controller.document.edges); + Assert.Equal(first.id, edge.output.nodeId); + Assert.Equal(second.id, edge.input.nodeId); + Assert.True(history.Undo().succeeded); + Assert.Empty(controller.document.edges); + Assert.True(history.Redo().succeeded); + Assert.Single(controller.document.edges); + + void Frame( + Vector2 mouse, + bool down + ) { + var io = NativeGui.Raw.GetIO(); + NativeGui.Raw.AddMousePosEvent(io, mouse.X, mouse.Y); + NativeGui.Raw.AddMouseButtonEvent(io, 0, down ? (byte)1 : (byte)0); + host.native.Frame(() => + { + NativeGui.SetNextWindowPos(new Vector2(20f, 20f), (int)ImGuiCond.Always); + NativeGui.SetNextWindowSize(new Vector2(700f, 450f), (int)ImGuiCond.Always); + bool open = true; + _ = NativeGui.Begin("Workflow graph", ref open, (int)(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings)); + try + { + origin = Ui.GetCursorScreenPosition(); + Vector2 size = Ui.GetContentRegionAvailable(); + _ = Ui.InvisibleButton("workflow-canvas", size); + var view = new GraphCanvasView(state, origin, size); + _ = layout.SetNodes(controller.document.nodes.Select(node => new GraphCanvasNode(node.id, node.position, + new(200f, 100f), 24f, "Workflow step", [ + new(new(node.id, new("previous")), GraphPortDirection.Input, new(0f, 40f), "Previous"), + new(new(node.id, new("next")), GraphPortDirection.Output, new(200f, 40f), "Next") + ]))); + GraphCanvasPointerResult result = pointer.Step(view, layout, new() + { + position = Ui.GetMousePos(), + isHovered = Ui.IsItemHovered(Inno.Editor.ImGui.ImGuiHoveredFlags.AllowWhenBlockedByActiveItem), + isPressed = Ui.IsMouseClicked(Inno.Editor.ImGui.ImGuiMouseButton.Left), + isDown = Ui.IsMouseButtonDown(Inno.Editor.ImGui.ImGuiMouseButton.Left), + isReleased = Ui.IsMouseReleased(Inno.Editor.ImGui.ImGuiMouseButton.Left) + }); + if (result.movedNodes is { } positions) + controller.MoveNodes(positions); + if (result.output is GraphEndpoint output && result.input is GraphEndpoint input) + _ = controller.Connect(output, input); + var draw = Ui.GetDrawList(); + foreach (GraphCanvasNode node in layout.nodes) + _ = view.DrawNode(draw, node, 0xFF504030, state.selectedNodes.Contains(node.id), + previewPosition: pointer.previewPositions.TryGetValue(node.id, out GraphPosition position) ? position : null); + } + finally + { + NativeGui.End(); + } + }); + } + } + + [Fact] + public void NonShaderNodeDrawingRestoresItsClipAfterAnExtensionThrows() + { + using var host = new NativePanelHost(); + var state = new GraphCanvasState(); + GraphNodeId id = new("workflow.begin"); + var node = new GraphCanvasNode(id, new(10f, 10f), new(200f, 100f), 24f, "Workflow step", + [new(new(id, new("next")), GraphPortDirection.Output, new(200f, 40f), "Continue", labelBounds: (new(0f, 28f), new(200f, 52f)))]); + for (int frame = 0; frame < 3; frame++) + { + host.Frame(() => + { + NativeGui.SetNextWindowPos(new Vector2(20f, 20f), (int)ImGuiCond.Always); + NativeGui.SetNextWindowSize(new Vector2(600f, 400f), (int)ImGuiCond.Always); + bool open = true; + _ = NativeGui.Begin("Non-shader graph", ref open, (int)ImGuiWindowFlags.NoSavedSettings); + try + { + ImGuiWindow* window = NativeGui.Raw.GetCurrentWindow(); + int clipDepth = window->DrawList->ClipRectStack.Size; + var draw = Ui.GetDrawList(); + var view = new GraphCanvasView(state, new(40f, 60f), new(540f, 300f)); + var failure = new InvalidOperationException("Workflow node extension failed."); + Assert.Same(failure, Assert.Throws(() => view.DrawNode(draw, node, 0xFF504030, true, + () => throw failure))); + Assert.Equal(clipDepth, window->DrawList->ClipRectStack.Size); + Assert.True(view.DrawNode(draw, node, 0xFF504030, false)); + var offscreen = new GraphCanvasNode(new("workflow.hidden"), new(5000f, 5000f), new(200f, 100f), 24f, "Hidden", []); + Assert.False(view.DrawNode(draw, offscreen, 0xFF504030, false, () => throw new InvalidOperationException("Culled content must not run."))); + var outsideWindow = new GraphCanvasNode(new("workflow.clipped-parent"), new(900f, 900f), new(200f, 100f), 24f, "Clipped by parent", []); + var oversized = new GraphCanvasView(state, new(40f, 60f), new(1800f, 1800f)); + Assert.False(oversized.DrawNode(draw, outsideWindow, 0xFF504030, false, () => throw new InvalidOperationException("Parent-clipped content must not run."))); + Assert.Equal(clipDepth, window->DrawList->ClipRectStack.Size); + } + finally + { + NativeGui.End(); + } + }); + } + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/Inno.Editor.PlayMode.Tests.csproj b/tests/editor/Inno.Editor.PlayMode.Tests/Inno.Editor.PlayMode.Tests.csproj index e6108ef6e..cf91e7d99 100644 --- a/tests/editor/Inno.Editor.PlayMode.Tests/Inno.Editor.PlayMode.Tests.csproj +++ b/tests/editor/Inno.Editor.PlayMode.Tests/Inno.Editor.PlayMode.Tests.csproj @@ -1,5 +1,18 @@ + + + + + + + + + + + + + @@ -15,9 +28,25 @@ true true Editor + editor + $(InnoNativeTarget) + + + + + + + + + diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Input.cs b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Input.cs new file mode 100644 index 000000000..bf70727c7 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Input.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json; +using BGCS.Runtime; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Inno.Scene; +using Inno.Scene.Components; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; +using NativeVector = Inno.Native.ImGui.ImVec2C; +using SceneVector = Inno.Core.Mathematics.Vector3; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed partial class LargePanelPerformanceTests +{ + [Theory] + [InlineData("hierarchy-scroll")] + [InlineData("assets-scroll")] + [InlineData("assets-search")] + [InlineData("hierarchy-edit")] + [Trait("Acceptance", "Performance")] + public unsafe void LargeViewsMeasureActualInputAndContinuousEditWork(string scenario) + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + bool hierarchy = scenario.StartsWith("hierarchy", StringComparison.Ordinal); + GameScene? scene = hierarchy ? LoadInteractionHierarchy(host) : null; + if (!hierarchy) + { + string? input = InputDirectory("assets"); + if (input is not null && Directory.Exists(input)) + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + else + for (int index = 0; index < 10_000; index++) + Directory.CreateDirectory(Path.Combine(host.projectRoot, "Assets", "Folder " + index.ToString("D5"))); + host.assets.Rescan(); + Assert.Equal(10_000, Directory.EnumerateDirectories(Path.Combine(host.projectRoot, "Assets"), "Folder *").Count()); + } + EditorPanelExtension panel = host.native.GetPanel(hierarchy ? "scene.hierarchy" : "asset.file-browser"); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + ImGuiIO* io = NativeGui.Raw.GetIO(); + int edits = 0; + float maximumScroll = 0f; + SceneVector initial = scene?.GetObjects()[0].transform.localPosition ?? default; + Transform? transform = scene?.GetObjects()[0].transform; + using EditorEditGesture? gesture = scenario == "hierarchy-edit" + ? new EditorEditGesture(host.runtime.interactions.history, "Measured continuous edit") + : null; + if (scenario == "assets-search") + FocusAssetSearch(host, panel); + Action operation = scenario switch + { + "assets-search" => Search, + "hierarchy-edit" => Edit, + _ => Scroll + }; + MeasureInteraction(scenario, operation); + if (gesture is not null) + { + gesture.Commit(); + Assert.True(host.runtime.interactions.history.Undo().succeeded); + Assert.Equal(initial, transform!.localPosition); + Assert.False(host.runtime.interactions.history.canUndo); + Assert.True(host.runtime.interactions.history.Redo().succeeded); + Assert.Equal((float)edits, transform.localPosition.x); + } + else if (scenario.EndsWith("scroll", StringComparison.Ordinal)) + Assert.True(maximumScroll > 0f, "Actual wheel input never moved the visible list."); + + void Scroll() + { + ImGuiWindow* window = FindInteractionWindow(hierarchy ? "##HierarchyContent" : "##EntriesScroll"); + Vector2 position = Unsafe.BitCast(window->Pos) + new Vector2(window->Size.X * 0.7f, 140f); + NativeGui.Raw.AddMousePosEvent(io, position.X, position.Y); + NativeGui.Raw.AddMouseWheelEvent(io, 0f, -3f); + host.native.DrawPanel(panel); + maximumScroll = MathF.Max(maximumScroll, window->Scroll.Y); + } + + void Search() + { + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 1); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.A, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.A, 0); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 0); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.Backspace, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.Backspace, 0); + string query = ++edits % 2 == 0 ? "Folder 00000" : "Folder 00001"; + using NativeStringLease text = Utf8Codec.EncodeNullTerminated(query, allowsNull: false); + NativeGui.Raw.AddInputCharactersUTF8(io, (sbyte*)text.pointer); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + Assert.NotEqual(0u, NativeGui.Raw.GetCurrentContext()->InputTextState.ID); + Assert.Equal(query.Length, NativeGui.Raw.GetCurrentContext()->InputTextState.TextLen); + } + + void Edit() + { + int value = ++edits; + Assert.True(host.edits.ChangeProperty(transform!, nameof(Transform.localPosition), + () => transform!.localPosition = new(value, 0f, 0f), "Measured continuous edit", gesture!.mergeKey)); + host.native.DrawPanel(panel); + } + } + + private static GameScene LoadInteractionHierarchy(NativeProjectPanelHost host) + { + string? input = InputDirectory("hierarchy"); + if (input is not null && Directory.Exists(input)) + { + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + host.assets.Rescan(); + return host.workspace.Open("Untitled Scene.iscene"); + } + GameScene scene = host.CreateScene(); + for (int index = 0; index < 10_000; index++) + _ = scene.CreateObject("Object " + index.ToString("D5")); + _ = host.workspace.Save(scene, string.Empty); + return scene; + } + + private static unsafe void FocusAssetSearch( + NativeProjectPanelHost host, + EditorPanelExtension panel + ) { + ImGuiWindow* pane = FindInteractionWindow("##ContentPane"); + Vector2 start = Unsafe.BitCast(pane->DC.CursorStartPos); + ImGuiIO* io = NativeGui.Raw.GetIO(); + // Locate the existing text input using ordinary clicks; no private Panel field is read or changed. + for (float offset = 24f; offset <= 64f; offset += 4f) + { + NativeGui.Raw.AddMousePosEvent(io, pane->Pos.X + pane->Size.X - 70f, start.Y + offset); + NativeGui.Raw.AddMouseButtonEvent(io, 0, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(io, 0, 0); + host.native.DrawPanel(panel); + ImGuiNativeContext* context = NativeGui.Raw.GetCurrentContext(); + if (context->InputTextState.ID != 0 && context->ActiveId == context->InputTextState.ID) + return; + } + throw new InvalidOperationException("Ordinary pointer input did not focus the existing asset search field."); + } + + private static unsafe ImGuiWindow* FindInteractionWindow(string fragment) + { + ImGuiNativeContext* context = NativeGui.Raw.GetCurrentContext(); + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + string? name = Marshal.PtrToStringUTF8((nint)window->Name); + if (window->Active != 0 && name?.Contains(fragment, StringComparison.Ordinal) == true) + return window; + } + throw new InvalidOperationException("The drawn native input owner is absent: " + fragment); + } + + private void MeasureInteraction( + string scenario, + Action operation + ) { + for (int warmup = 0; warmup < 5; warmup++) + operation(); + int repetitions = int.Parse(Environment.GetEnvironmentVariable("INNO_UI_PERFORMANCE_REPETITIONS") ?? "5", + System.Globalization.CultureInfo.InvariantCulture); + Assert.InRange(repetitions, 1, 5); + using Process process = Process.GetCurrentProcess(); + var samples = new List(); + for (int repetition = 0; repetition < repetitions; repetition++) + { + var milliseconds = new double[10]; + long before = GC.GetAllocatedBytesForCurrentThread(); + long totalBefore = GC.GetTotalAllocatedBytes(precise: true); + TimeSpan cpuBefore = process.TotalProcessorTime; + int gen0 = GC.CollectionCount(0); + int gen1 = GC.CollectionCount(1); + int gen2 = GC.CollectionCount(2); + for (int frame = 0; frame < milliseconds.Length; frame++) + { + long started = Stopwatch.GetTimestamp(); + operation(); + milliseconds[frame] = Stopwatch.GetElapsedTime(started).TotalMilliseconds; + } + long allocated = GC.GetAllocatedBytesForCurrentThread() - before; + double[] sorted = milliseconds.Order().ToArray(); + samples.Add(new + { + repetition, milliseconds, median = (sorted[4] + sorted[5]) / 2, p95 = sorted[9], allocatedBytes = allocated, + totalAllocatedBytes = GC.GetTotalAllocatedBytes(precise: true) - totalBefore, + cpuMilliseconds = (process.TotalProcessorTime - cpuBefore).TotalMilliseconds, + gen0Collections = GC.CollectionCount(0) - gen0, + gen1Collections = GC.CollectionCount(1) - gen1, + gen2Collections = GC.CollectionCount(2) - gen2 + }); + } + output.WriteLine(JsonSerializer.Serialize(new + { + workload = scenario, samples, + configuration = typeof(LargePanelPerformanceTests).Assembly.GetCustomAttribute()?.Configuration, + viewport = "1000x800", warmupOperations = 5, measuredOperationsPerRepetition = 10, + nativeFramesPerOperation = scenario == "assets-search" ? 4 : 1, + consumer = scenario == "hierarchy-edit" ? "continuous-domain-gesture-and-actual-hierarchy-draw" : "actual-native-input-and-panel-draw", + sharedInput = InputDirectory(scenario.StartsWith("hierarchy", StringComparison.Ordinal) ? "hierarchy" : "assets") + })); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Workflows.cs b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Workflows.cs new file mode 100644 index 000000000..1efac275a --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.Workflows.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using BGCS.Runtime; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Identity; +using Inno.Editor.Interactions; +using Inno.Editor.Panel.Hierarchy; +using Inno.Native.ImGui; +using Inno.Scene; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed partial class LargePanelPerformanceTests +{ + [Theory] + [InlineData("hierarchy")] + [InlineData("list")] + [InlineData("grid")] + [InlineData("tree")] + [Trait("Acceptance", "Workflows")] + public unsafe void TenThousandEntriesKeepRangeRenameAndDragAcrossClippedRows(string view) + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + bool hierarchy = view == "hierarchy"; + GameScene? scene = hierarchy ? LoadInteractionHierarchy(host) : null; + if (!hierarchy) + { + string? input = InputDirectory("assets"); + if (input is not null && Directory.Exists(input)) + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + else + for (int index = 0; index < 10_000; index++) + Directory.CreateDirectory(Path.Combine(host.projectRoot, "Assets", "Folder " + index.ToString("D5"))); + host.assets.Rescan(); + } + EditorPanelExtension panel = host.native.GetPanel(hierarchy ? "scene.hierarchy" : "asset.file-browser"); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + if (view == "grid") + SelectGrid(host, panel); + IdentityObject first = hierarchy ? scene!.GetObjects()[0] : Entry("Folder 00000"); + IdentityObject last = hierarchy ? scene!.GetObjects()[^1] : Entry("Folder 09999"); + string area = hierarchy ? "panel/scene.hierarchy" : "panel/asset.file-browser"; + string windowName = hierarchy ? "##HierarchyContent" : view == "tree" ? "##TreeScroll" : "##EntriesScroll"; + EditorInteractions interactions = host.runtime.interactions; + if (view is "list" or "grid") + { + SetLargeQuery(host, panel, "Folder 00042"); + ImGuiWindow* filtered = FindInteractionWindow(windowName); + NativeGui.Raw.AddMousePosEvent(NativeGui.Raw.GetIO(), filtered->Pos.X + filtered->Size.X - 60f, + filtered->Pos.Y + filtered->Size.Y * 0.7f); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 0); + host.native.DrawPanel(panel); + NativePanelInput.Key(host.native, panel, ImGuiKey.Home); + Assert.Same(Entry("Folder 00042"), interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.End); + Assert.Same(Entry("Folder 00042"), interactions.selection.selectedTarget); + SetLargeQuery(host, panel, string.Empty); + NativeGui.Raw.AddMousePosEvent(NativeGui.Raw.GetIO(), filtered->Pos.X + filtered->Size.X * 0.4f, + filtered->Pos.Y + filtered->Size.Y * 0.7f); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 0); + host.native.DrawPanel(panel); + } + interactions.SetSelection(first); + NativePanelInput.Focus(host.native, panel, windowName); + NativePanelInput.Key(host.native, panel, ImGuiKey.End, shift: true); + Assert.Equal(10_000, interactions.selection.count); + Assert.True(interactions.selection.Contains(first)); + Assert.True(interactions.selection.Contains(last)); + NativePanelInput.Key(host.native, panel, ImGuiKey.End); + Assert.Same(last, interactions.selection.selectedTarget); + Assert.Equal(1, interactions.selection.count); + string original = hierarchy ? ((GameObject)last).name : ((AssetFileEntry)last).name; + Guid? objectId = hierarchy ? ((GameObject)last).identity.persistentId : null; + Assert.True(interactions.For(area, last).Execute(hierarchy ? "hierarchy/rename" : "file-browser/rename")); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + ImGuiNativeContext* context = NativeGui.Raw.GetCurrentContext(); + Assert.NotEqual(0u, context->ActiveId); + Assert.Equal(context->ActiveId, context->InputTextState.ID); + Assert.Equal(FindInteractionWindow(windowName)->ID, context->ActiveIdWindow->ID); + NativePanelInput.Key(host.native, panel, ImGuiKey.A, primary: true); + NativePanelInput.Key(host.native, panel, ImGuiKey.Backspace); + using (NativeStringLease text = Utf8Codec.EncodeNullTerminated("Renamed last", allowsNull: false)) + { + NativeGui.Raw.AddInputCharactersUTF8(NativeGui.Raw.GetIO(), (sbyte*)text.pointer); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + } + NativePanelInput.Key(host.native, panel, ImGuiKey.Enter); + Assert.True(interactions.history.canUndo); + if (hierarchy) + { + Assert.Equal("Renamed last", ((GameObject)last).name); + Assert.Equal(objectId, ((GameObject)last).identity.persistentId); + } + else + last = Entry("Renamed last"); + Assert.True(interactions.history.Undo().succeeded); + if (hierarchy) + Assert.Equal(original, ((GameObject)last).name); + else + Assert.True(Directory.Exists(Path.Combine(host.projectRoot, "Assets", original))); + Assert.True(interactions.history.Redo().succeeded); + if (!hierarchy) + last = Entry("Renamed last"); + interactions.SetSelection(last); + RuntimeIdentity drag = interactions.For(area, last).BeginDrag(new EditorDragData(last, "Clipped source")); + Assert.Equal(hierarchy ? ((GameObject)last).identity.runtimeIdentity : ((AssetFileEntry)last).identity.runtimeIdentity, drag); + ImGuiWindow* window = FindInteractionWindow(windowName); + Assert.True(window->ScrollMax.Y > window->Size.Y * 20f); + NativeGui.Raw.AddMousePosEvent(NativeGui.Raw.GetIO(), window->Pos.X + window->Size.X * 0.7f, window->Pos.Y + 140f); + NativeGui.Raw.AddMouseWheelEvent(NativeGui.Raw.GetIO(), 0f, 100_000f); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + Assert.Equal(0f, window->Scroll.Y); + Assert.True(interactions.For(area).TryGetActiveDragIdentity(out RuntimeIdentity retained)); + Assert.Equal(drag, retained); + EditorInteraction drop = hierarchy + ? interactions.For(area, new HierarchyObjectDropTarget((GameObject)first)) + : interactions.For(area, ((AssetFileEntry)first).assetPath.ToString()); + Assert.True(drop.QueryDrop(drag, EditorDropPlacement.Into).canDrop); + Assert.True(drop.Drop(drag, EditorDropPlacement.Into).accepted); + if (hierarchy) + { + Assert.Same(((GameObject)first).transform, ((GameObject)last).transform.parent); + Assert.Equal(10_000, scene!.GetObjects().Count); + } + else + Assert.True(Directory.Exists(Path.Combine(host.projectRoot, "Assets", "Folder 00000", "Renamed last"))); + Assert.True(interactions.history.Undo().succeeded); + if (hierarchy) + Assert.Null(((GameObject)last).transform.parent); + else + Assert.True(Directory.Exists(Path.Combine(host.projectRoot, "Assets", "Renamed last"))); + Assert.True(interactions.history.Redo().succeeded); + host.native.DrawPanel(panel); + + AssetFileEntry Entry(string name) + { + Assert.True(host.assets.TryGetFileSystemEntry(AssetPath.Project(name), out AssetFileEntry entry)); + return entry; + } + } + + private static unsafe void SetLargeQuery( + NativeProjectPanelHost host, + EditorPanelExtension panel, + string query + ) { + FocusAssetSearch(host, panel); + NativePanelInput.Key(host.native, panel, ImGuiKey.A, primary: true); + NativePanelInput.Key(host.native, panel, ImGuiKey.Backspace); + using NativeStringLease text = Utf8Codec.EncodeNullTerminated(query, allowsNull: false); + NativeGui.Raw.AddInputCharactersUTF8(NativeGui.Raw.GetIO(), (sbyte*)text.pointer); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + } + + private static unsafe void SelectGrid( + NativeProjectPanelHost host, + EditorPanelExtension panel + ) { + ImGuiWindow* pane = FindInteractionWindow("##ContentPane"); + float start = pane->DC.CursorStartPos.Y; + for (float offset = 24f; offset <= 64f; offset += 4f) + { + NativeGui.Raw.AddMousePosEvent(NativeGui.Raw.GetIO(), pane->Pos.X + 20f, start + offset); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 1); + host.native.DrawPanel(panel); + NativeGui.Raw.AddMouseButtonEvent(NativeGui.Raw.GetIO(), 0, 0); + host.native.DrawPanel(panel); + host.runtime.SaveState(); + if (File.ReadAllText(Path.Combine(host.projectRoot, "editor.ini")).Contains("viewMode=\"Grid\"", StringComparison.Ordinal)) + return; + } + throw new InvalidOperationException("Ordinary pointer input did not select the actual Browser grid."); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.cs new file mode 100644 index 000000000..016439727 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/LargePanelPerformanceTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Threading; +using Inno.Assets; +using Inno.Core.Serialization; +using Inno.Editor.Interactions; +using Inno.Scene; +using Xunit; +using Xunit.Abstractions; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed partial class LargePanelPerformanceTests(ITestOutputHelper output) +{ + [Fact] + [Trait("Acceptance", "Performance")] + public void DueDirtyInspectionsMeasureTheCompleteSavedSceneTraversal() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + Inno.Scene.GameScene scene = LoadSavedHierarchy(host); + Measure(host, null, "10000-hierarchy-dirty-inspections", + () => Assert.False(host.workspace.IsDirty(scene)), static () => Thread.Sleep(110)); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void ChangedDirtyInspectionsMeasureTheCompleteSavedSceneTraversal() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + GameScene scene = LoadSavedHierarchy(host); + GameObject value = scene.GetObjects()[0]; + int revision = 0; + Measure(host, null, "10000-hierarchy-dirty-edits", () => + { + value.transform.localPosition = new Inno.Core.Mathematics.Vector3(++revision % 2 + 1, 0, 0); + Assert.True(host.workspace.IsDirty(scene)); + }, static () => Thread.Sleep(110), consumer: "complete-authoritative-dirty-inspection-after-each-owned-write"); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void NeutralCaptureSeparatesSerializationFromSourceFreshness() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + Inno.Scene.GameScene scene = LoadSavedHierarchy(host); + SerializationContext context = AssetSerializationContext.Create(host.assets, new AssetDependencyCollection(includeLastKnownPaths: false)); + SerializationSnapshot? previous = null; + Measure(host, null, "10000-hierarchy-neutral-capture", + () => previous = host.serialization.CaptureSnapshot(scene, context, previous), + consumer: "complete-authoritative-serialization-capture"); + Assert.NotNull(previous); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void SourceFreshnessMeasuresTheActualSavedSceneLoadBoundary() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + Inno.Scene.GameScene scene = LoadSavedHierarchy(host); + Assert.True(host.workspace.TryGetSourcePath(scene, out string path)); + SceneAsset asset = host.assets.Load(AssetPath.Project(path)); + Measure(host, null, "10000-hierarchy-source-freshness", + () => Assert.Same(asset, host.assets.Load(asset.identity.persistentId)), + consumer: "canonical-source-load-with-freshness-verification"); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void SceneMetadataReadsMeasureTheOwnedAttachmentBoundary() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + GameScene scene = LoadSavedHierarchy(host); + IReadOnlyList objects = scene.GetObjects(); + long checksum = 0; + Measure(host, null, "10000-hierarchy-metadata-reads", () => + { + foreach (GameObject value in objects) + { + checksum += value.name.Length + value.tag.Length + value.layer.index; + checksum += value.activeSelf ? 1 : 0; + checksum += value.transform.parent is null ? 1 : 0; + checksum += value.transform.siblingIndex; + } + }, consumer: "public-metadata-getters-with-current-attachment-validation"); + Assert.True(checksum > 0); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void TenThousandHierarchyRowsProduceRepeatableFrameMeasurements() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + string? input = InputDirectory("hierarchy"); + if (input is not null && Directory.Exists(input)) + { + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + host.assets.Rescan(); + _ = host.workspace.Open("Untitled Scene.iscene"); + } + else + { + var scene = host.CreateScene(); + for (int index = 0; index < 10_000; index++) + _ = scene.CreateObject("Object " + index.ToString("D5")); + if (input is not null) + { + Assert.Equal("Untitled Scene.iscene", host.workspace.Save(scene, string.Empty)); + CopyDirectory(Path.Combine(host.projectRoot, "Assets"), input); + } + } + Assert.Equal(10_000, host.workspace.activeScene!.GetObjects().Count); + Measure(host, host.native.GetPanel("scene.hierarchy"), "10000-hierarchy-rows"); + } + + [Fact] + [Trait("Acceptance", "Performance")] + public void TenThousandAssetRowsProduceRepeatableFrameMeasurements() + { + using var host = new NativeProjectPanelHost(supportsVertexOffsets: true); + string? input = InputDirectory("assets"); + if (input is not null && Directory.Exists(input)) + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + else + for (int index = 0; index < 10_000; index++) + Directory.CreateDirectory(Path.Combine(host.projectRoot, "Assets", "Folder " + index.ToString("D5"))); + host.assets.Rescan(); + Assert.Equal(10_000, Directory.EnumerateDirectories(Path.Combine(host.projectRoot, "Assets"), "Folder *").Count()); + if (input is not null && !Directory.Exists(input)) + CopyDirectory(Path.Combine(host.projectRoot, "Assets"), input); + EditorPanelExtension panel = host.native.GetPanel("asset.file-browser"); + Measure(host, panel, "10000-asset-directory-rows"); + } + + private static Inno.Scene.GameScene LoadSavedHierarchy(NativeProjectPanelHost host) + { + string? input = InputDirectory("hierarchy"); + Inno.Scene.GameScene scene; + if (input is not null && Directory.Exists(input)) + { + CopyDirectory(input, Path.Combine(host.projectRoot, "Assets")); + host.assets.Rescan(); + scene = host.workspace.Open("Untitled Scene.iscene"); + } + else + { + scene = host.CreateScene(); + for (int index = 0; index < 10_000; index++) + _ = scene.CreateObject("Object " + index.ToString("D5")); + _ = host.workspace.Save(scene, string.Empty); + } + Assert.Equal(10_000, scene.GetObjects().Count); + return scene; + } + + private static string? InputDirectory(string workload) + { + string? owner = Environment.GetEnvironmentVariable("INNO_UI_PERFORMANCE_INPUT_ROOT"); + return owner is null ? null : Path.Combine(Path.GetFullPath(owner), workload); + } + + private static void CopyDirectory( + string source, + string destination + ) { + Directory.CreateDirectory(destination); + foreach (string directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory))); + foreach (string file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) + File.Copy(file, Path.Combine(destination, Path.GetRelativePath(source, file)), overwrite: false); + } + + private void Measure( + NativeProjectPanelHost host, + EditorPanelExtension? panel, + string workload, + Action? operation = null, + Action? prepare = null, + string? consumer = null + ) { + operation ??= () => host.native.DrawPanel(panel!); + for (int warmup = 0; warmup < 5; warmup++) + { + prepare?.Invoke(); + operation(); + } + string? requested = Environment.GetEnvironmentVariable("INNO_UI_PERFORMANCE_REPETITIONS"); + int repetitions = requested is null ? 5 : int.Parse(requested, System.Globalization.CultureInfo.InvariantCulture); + Assert.InRange(repetitions, 1, 5); + using Process process = Process.GetCurrentProcess(); + var samples = new List(); + for (int repetition = 0; repetition < repetitions; repetition++) + { + var milliseconds = new double[10]; + long bytesBefore = GC.GetAllocatedBytesForCurrentThread(); + long totalBytesBefore = GC.GetTotalAllocatedBytes(precise: true); + TimeSpan cpuBefore = process.TotalProcessorTime; + int collectionsBefore = GC.CollectionCount(0); + int gen1Before = GC.CollectionCount(1); + int gen2Before = GC.CollectionCount(2); + for (int frame = 0; frame < milliseconds.Length; frame++) + { + prepare?.Invoke(); + long started = Stopwatch.GetTimestamp(); + operation(); + milliseconds[frame] = Stopwatch.GetElapsedTime(started).TotalMilliseconds; + } + long allocated = GC.GetAllocatedBytesForCurrentThread() - bytesBefore; + double[] sorted = milliseconds.Order().ToArray(); + samples.Add(new + { + repetition, milliseconds, median = (sorted[4] + sorted[5]) / 2, + p95 = sorted[9], allocatedBytes = allocated, + totalAllocatedBytes = GC.GetTotalAllocatedBytes(precise: true) - totalBytesBefore, + cpuMilliseconds = (process.TotalProcessorTime - cpuBefore).TotalMilliseconds, + gen0Collections = GC.CollectionCount(0) - collectionsBefore, + gen1Collections = GC.CollectionCount(1) - gen1Before, + gen2Collections = GC.CollectionCount(2) - gen2Before + }); + } + output.WriteLine(JsonSerializer.Serialize(new + { + workload, + configuration = typeof(LargePanelPerformanceTests).Assembly.GetCustomAttribute()?.Configuration + ?? throw new InvalidOperationException("The benchmark assembly has no SDK configuration metadata."), + samples, + framework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, + viewport = "1000x800", warmupFrames = 5, measuredFramesPerRepetition = 10, + sharedInput = InputDirectory(workload.Contains("hierarchy", StringComparison.Ordinal) ? "hierarchy" : "assets"), + consumer = consumer ?? (prepare is null ? "native-command-offset-validation" : "complete-authoritative-dirty-inspection"), + preparationExcludedFromFrameTiming = prepare is not null, + supportsVertexOffsets = true + })); + if (panel is not null) + Assert.True(panel.isOpen); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/ListPanelKeyboardNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/ListPanelKeyboardNativeTests.cs new file mode 100644 index 000000000..69722d10e --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/ListPanelKeyboardNativeTests.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Editor.Assets.ImGui; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Inno.Scene; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed class ListPanelKeyboardNativeTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public void BrowserResolvesCurrentSourceEntriesAfterAnUnchangedRescan(bool tree) + { + using var host = new NativeProjectPanelHost(); + host.assets.CreateDirectory(AssetPath.Project("Alpha")); + host.assets.CreateDirectory(AssetPath.Project("Beta")); + host.assets.Rescan(); + EditorPanelExtension panel = host.native.GetPanel("asset.file-browser"); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + Assert.True(host.assets.TryGetFileSystemEntry(AssetPath.Project("Alpha"), out AssetFileEntry first)); + host.runtime.interactions.SetSelection(first); + NativePanelInput.Focus(host.native, panel, tree ? "##TreeScroll" : "##EntriesScroll"); + long contentRevision = host.assets.revision; + host.assets.Rescan(); + Assert.Equal(contentRevision, host.assets.revision); + Assert.True(host.assets.TryGetFileSystemEntry(AssetPath.Project("Beta"), out AssetFileEntry current)); + NativePanelInput.Key(host.native, panel, ImGuiKey.End); + Assert.Same(current, host.runtime.interactions.selection.selectedTarget); + } + + [Theory] + [MemberData(nameof(VisualScaleMatrixTests.Metrics), MemberType = typeof(VisualScaleMatrixTests))] + [Trait("Acceptance", "Visual")] + public void HierarchyUsesVisibleRangeAndKeepsTextEditingExclusive( + float zoom, + float framebufferScale, + float width, + float height + ) { + using var host = new NativeProjectPanelHost(); + host.native.SetDisplayMetrics(new(width, height), framebufferScale, zoom); + GameScene scene = host.CreateScene(); + GameObject[] objects = Enumerable.Range(0, 12).Select(index => scene.CreateObject("Object " + index.ToString("D2"))).ToArray(); + GameObject child = scene.CreateObject("Collapsed child"); + child.transform.SetParent(objects[10].transform); + EditorPanelExtension panel = host.native.GetPanel("scene.hierarchy"); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + EditorInteractions interactions = host.runtime.interactions; + interactions.SetSelection(objects[0]); + NativePanelInput.Focus(host.native, panel, "##HierarchyContent"); + NativePanelInput.Key(host.native, panel, ImGuiKey.DownArrow); + Assert.Same(objects[1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.End, shift: true); + Assert.Equal(11, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.UpArrow, primary: true); + Assert.Equal(11, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.Space, primary: true); + Assert.False(interactions.selection.Contains(objects[10])); + Assert.Equal(10, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.DownArrow); + Assert.Same(objects[^1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.Home); + Assert.Same(scene, interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.PageDown); + Assert.Same(objects[^1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.PageUp); + Assert.Same(scene, interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.End); + NativePanelInput.Key(host.native, panel, ImGuiKey.Enter); + NativePanelInput.Key(host.native, panel, ImGuiKey.UpArrow); + Assert.Same(objects[^1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.Escape); + Assert.Equal("Object 11", objects[^1].name); + Assert.False(interactions.history.canUndo); + } + + [Theory] + [MemberData(nameof(BrowserMetrics))] + [Trait("Acceptance", "Visual")] + public void BrowserUsesTheFocusedVisibleOrderAndActivatesFoldersAfterTraversal( + bool tree, + float zoom, + float framebufferScale, + float width, + float height + ) { + using var host = new NativeProjectPanelHost(); + host.native.SetDisplayMetrics(new(width, height), framebufferScale, zoom); + AssetFileEntry[] entries = Enumerable.Range(0, 12).Select(index => + { + AssetPath path = AssetPath.Project("Folder " + index.ToString("D2")); + host.assets.CreateDirectory(path); + Assert.True(host.assets.TryGetFileSystemEntry(path, out AssetFileEntry entry)); + return entry; + }).ToArray(); + host.assets.CreateDirectory(AssetPath.Project("Folder 10/Collapsed child")); + EditorPanelExtension panel = host.native.GetPanel("asset.file-browser"); + host.native.DrawPanel(panel); + host.native.DrawPanel(panel); + EditorInteractions interactions = host.runtime.interactions; + for (int index = 0; index < entries.Length; index++) + { + Assert.True(host.assets.TryGetFileSystemEntry(AssetPath.Project("Folder " + index.ToString("D2")), out AssetFileEntry current)); + entries[index] = current; + } + interactions.SetSelection(entries[0]); + NativePanelInput.Focus(host.native, panel, tree ? "##TreeScroll" : "##EntriesScroll"); + NativePanelInput.Key(host.native, panel, ImGuiKey.DownArrow); + Assert.Same(entries[1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.End, shift: true); + Assert.Equal(11, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.UpArrow, primary: true); + Assert.Equal(11, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.Space, primary: true); + Assert.False(interactions.selection.Contains(entries[10])); + Assert.Equal(10, interactions.selection.count); + NativePanelInput.Key(host.native, panel, ImGuiKey.DownArrow); + Assert.Same(entries[^1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.End); + Assert.Same(entries[^1], interactions.selection.selectedTarget); + NativePanelInput.Key(host.native, panel, ImGuiKey.Enter); + Assert.True(interactions.TryGetModule(out var browser)); + Assert.Equal(entries[^1].assetPath.ToString(), browser!.browser.currentDirectory); + Assert.False(interactions.history.canUndo); + } + + public static IEnumerable BrowserMetrics() + { + foreach (bool tree in new[] { false, true }) + foreach (object[] metric in VisualScaleMatrixTests.Metrics()) + yield return [tree, .. metric]; + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/MissingSceneSourceTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/MissingSceneSourceTests.cs new file mode 100644 index 000000000..5453329f6 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/MissingSceneSourceTests.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Threading; +using Inno.Assets; +using Inno.Scene; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed class MissingSceneSourceTests +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + [Trait("Acceptance", "Workflows")] + public void UnavailableSourceKeepsAssignmentEditsAndIdentityUntilRecovery(bool edited) + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject value = scene.CreateObject("Retained object"); + string path = host.workspace.Save(scene, string.Empty); + string physical = Path.Combine(host.projectRoot, "Assets", path); + byte[] source = File.ReadAllBytes(physical); + byte[] metadata = File.ReadAllBytes(physical + ".imeta"); + Guid id = host.assets.Load(AssetPath.Project(path)).identity.persistentId; + if (edited) + value.name = "Unsaved edit"; + File.Delete(physical); + Assert.True(host.workspace.TryGetSourcePath(scene, out string pendingPath)); + Assert.Equal(path, pendingPath); + host.assets.Rescan(); + Assert.True(host.workspace.IsDirty(scene)); + Assert.True(host.workspace.TryGetSourcePath(scene, out string knownPath)); + Assert.Equal(path, knownPath); + Assert.Throws(() => host.workspace.Save(scene, string.Empty)); + Assert.False(File.Exists(physical)); + Assert.Equal(edited ? "Unsaved edit" : "Retained object", value.name); + File.WriteAllBytes(physical, source); + File.WriteAllBytes(physical + ".imeta", metadata); + host.assets.Rescan(); + Thread.Sleep(120); + Assert.True(host.workspace.TryGetSourcePath(scene, out string recovered)); + Assert.Equal(path, recovered); + Assert.Equal(id, host.assets.Load(AssetPath.Project(path)).identity.persistentId); + Assert.Equal(edited, host.workspace.IsDirty(scene)); + host.assets.Move(AssetPath.Project(path), AssetPath.Project("Recovered.iscene")); + Assert.True(host.workspace.TryGetSourcePath(scene, out string renamed)); + Assert.Equal("Recovered.iscene", renamed); + Assert.Equal("Recovered.iscene", host.workspace.Save(scene, string.Empty)); + Assert.True(host.edits.CloseScene(scene)); + GameScene reopened = host.workspace.Open("Recovered.iscene"); + Assert.Equal(edited ? "Unsaved edit" : "Retained object", Assert.Single(reopened.GetObjects()).name); + Assert.False(host.workspace.IsDirty(reopened)); + Assert.Equal(id, host.assets.Load(AssetPath.Project("Recovered.iscene")).identity.persistentId); + } + + [Fact] + [Trait("Acceptance", "Workflows")] + public void ExplicitSaveToAnotherDirectoryDoesNotRecreateTheMissingAssignedSource() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject value = scene.CreateObject("Retained object"); + string original = host.workspace.Save(scene, string.Empty); + string physical = Path.Combine(host.projectRoot, "Assets", original); + Guid originalId = host.assets.Load(AssetPath.Project(original)).identity.persistentId; + File.Delete(physical); + host.assets.Rescan(); + Assert.True(host.workspace.IsDirty(scene)); + host.assets.CreateDirectory(AssetPath.Project("Recovery")); + value.name = "Saved elsewhere"; + string replacement = host.workspace.SaveToDirectory(scene, "Recovery"); + Assert.StartsWith("Recovery/", replacement, StringComparison.Ordinal); + Assert.False(File.Exists(physical)); + Assert.True(host.workspace.TryGetSourcePath(scene, out string assigned)); + Assert.Equal(replacement, assigned); + Assert.NotEqual(originalId, host.assets.Load(AssetPath.Project(replacement)).identity.persistentId); + Assert.False(host.workspace.IsDirty(scene)); + Assert.True(host.edits.CloseScene(scene)); + GameScene reopened = host.workspace.Open(replacement); + Assert.Equal("Saved elsewhere", Assert.Single(reopened.GetObjects()).name); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/NativeClipboard.cs b/tests/editor/Inno.Editor.PlayMode.Tests/NativeClipboard.cs new file mode 100644 index 000000000..d06247b3b --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/NativeClipboard.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +internal sealed unsafe class NativeClipboard : IDisposable +{ + private GCHandle m_handle; + private nint m_text; + private ExceptionDispatchInfo? m_failure; + + internal NativeClipboard(ImGuiNativeContext* context) + { + m_handle = GCHandle.Alloc(this); + ImGuiPlatformIO* platform = NativeImGui.Raw.GetPlatformIO(context); + platform->PlatformClipboardUserData = (void*)GCHandle.ToIntPtr(m_handle); + platform->PlatformGetClipboardTextFn = &Read; + platform->PlatformSetClipboardTextFn = &Write; + m_text = Marshal.StringToCoTaskMemUTF8(string.Empty); + } + + internal void ThrowIfFaulted() => m_failure?.Throw(); + + public void Dispose() + { + if (!m_handle.IsAllocated) + return; + Marshal.FreeCoTaskMem(m_text); + m_text = 0; + m_handle.Free(); + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static sbyte* Read(ImGuiNativeContext* context) + { + NativeClipboard owner = GetOwner(context); + return (sbyte*)owner.m_text; + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void Write( + ImGuiNativeContext* context, + sbyte* text + ) { + NativeClipboard owner = GetOwner(context); + try + { + nint replacement = Marshal.StringToCoTaskMemUTF8(Marshal.PtrToStringUTF8((nint)text) ?? string.Empty); + Marshal.FreeCoTaskMem(owner.m_text); + owner.m_text = replacement; + } + catch (Exception failure) + { + owner.m_failure ??= ExceptionDispatchInfo.Capture(failure); + } + } + + private static NativeClipboard GetOwner(ImGuiNativeContext* context) + => (NativeClipboard)GCHandle.FromIntPtr((nint)NativeImGui.Raw.GetPlatformIO(context)->PlatformClipboardUserData).Target!; +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelHost.cs b/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelHost.cs new file mode 100644 index 000000000..2cc575c0d --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelHost.cs @@ -0,0 +1,241 @@ +using System; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using Inno.Adapter.Modules.DotNet; +using Inno.Core.Diagnostics; +using Inno.Core.Input; +using Inno.Core.Logging; +using Inno.Editor.Core; +using Inno.Editor.Diagnostics; +using Inno.Editor.ImGui; +using Inno.Editor.Interactions; +using Inno.Extensibility.Modules; +using Inno.Extensibility.Types; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; +using NativeVector2 = Inno.Native.ImGui.ImVec2C; +using ImGuiCond = Inno.Native.ImGui.ImGuiCond; +using ImGuiWindowFlags = Inno.Native.ImGui.ImGuiWindowFlags; + +namespace Inno.Editor.PlayMode.Tests; + +internal sealed unsafe class NativePanelHost : IDisposable +{ + private readonly string m_root = Path.Combine(Path.GetTempPath(), "InnoNativePanels", Guid.NewGuid().ToString("N")); + private readonly ImGuiNativeContext* m_nativeContext; + private readonly NativeClipboard m_clipboard; + private readonly IDisposable? m_diagnosticScope; + private readonly ModuleHost? m_modules; + private readonly TypeCatalog? m_types; + private readonly bool m_ownsServices; + private readonly bool m_validateVertexOffsets; + private float m_time; + private Vector2 m_panelSize = new(1000f, 800f); + + internal NativePanelHost(params string[] assemblies) : this(false, assemblies) { } + + internal NativePanelHost( + bool supportsVertexOffsets, + params string[] assemblies + ) : this(supportsVertexOffsets, new EditorKeyboardPolicy(KeyModifier.Control, "Super"), assemblies) { } + + internal NativePanelHost( + bool supportsVertexOffsets, + EditorKeyboardPolicy keyboard, + params string[] assemblies + ) { + m_validateVertexOffsets = supportsVertexOffsets; + m_ownsServices = true; + Directory.CreateDirectory(m_root); + foreach (string assembly in assemblies) + _ = Assembly.Load(assembly); + diagnostics = new DiagnosticHub(); + m_diagnosticScope = diagnostics.EnterScope(); + logs = new LogRouter(); + m_modules = new ModuleHost(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(NativePanelHost).Assembly) }); + m_types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); + console = new EditorConsole(logs, diagnostics, new InactivePlayMode()); + console.Start(); + runtime = new EditorInteractionRuntime(new EditorContext(m_root, keyboard), m_types, logs, [console]); + runtime.Start(); + ui = new EditorUiSession(); + m_nativeContext = CreateNativeContext(ui, supportsVertexOffsets); + m_clipboard = new NativeClipboard(m_nativeContext); + logs.Flush(); + console.Clear(); + } + + internal NativePanelHost( + EditorInteractionRuntime runtime, + EditorUiSession ui, + EditorConsole console, + LogRouter logs, + DiagnosticHub diagnostics, + bool supportsVertexOffsets = false + ) { + this.runtime = runtime; + this.ui = ui; + this.console = console; + this.logs = logs; + m_validateVertexOffsets = supportsVertexOffsets; + this.diagnostics = diagnostics; + m_root = string.Empty; + m_nativeContext = CreateNativeContext(ui, supportsVertexOffsets); + m_clipboard = new NativeClipboard(m_nativeContext); + } + + internal DiagnosticHub diagnostics { get; } + internal LogRouter logs { get; } + internal EditorConsole console { get; } + internal EditorInteractionRuntime runtime { get; } + internal EditorUiSession ui { get; } + + internal EditorPanelExtension GetPanel(string id) + { + EditorPanelExtension panel = runtime.panels.Single(value => value.id == id); + panel.isOpen = true; + return panel; + } + + internal void Frame( + Action draw, + float deltaTime = 1f / 60f + ) { + m_time += deltaTime; + runtime.Update(new EditorFrame(deltaTime, m_time, true)); + NativeImGui.Raw.GetIO()->DeltaTime = deltaTime; + using IDisposable scope = ui.Enter(); + NativeImGui.NewFrame(); + try + { + draw(); + } + finally + { + NativeImGui.Render(); + if (m_validateVertexOffsets) + ValidateDrawData(); + m_clipboard.ThrowIfFaulted(); + } + } + + internal void SetDisplayMetrics( + Vector2 logicalSize, + float framebufferScale, + float zoom + ) { + ImGuiIO* io = NativeImGui.Raw.GetIO(); + io->DisplaySize = Unsafe.BitCast(logicalSize); + io->DisplayFramebufferScale = Unsafe.BitCast(new(framebufferScale)); + m_panelSize = Vector2.Min(new(1000f, 800f), logicalSize - new Vector2(40f)); + ui.widgets.style.SetZoom(zoom); + using IDisposable scope = ui.Enter(); + ui.widgets.SetupStyle(); + } + + internal void DrawPanel( + EditorPanelExtension panel, + Action? afterDraw = null, + float deltaTime = 1f / 60f + ) { + Frame(() => + { + NativeImGui.SetNextWindowPos(new Vector2(20f, 20f), (int)ImGuiCond.Always); + NativeImGui.SetNextWindowSize(m_panelSize, (int)ImGuiCond.Always); + bool open = true; + _ = NativeImGui.Begin("Panel acceptance", ref open, (int)ImGuiWindowFlags.NoSavedSettings); + try + { + if (panel.isOpen && !panel.Draw(runtime.context)) + { + logs.Flush(); + string failures = string.Join(Environment.NewLine, console.Capture().occurrences + .Where(static entry => entry.level >= LogLevel.Error) + .Select(static entry => entry.message + Environment.NewLine + entry.stackTrace)); + throw new InvalidOperationException("The actual panel was quarantined: " + panel.id + Environment.NewLine + failures); + } + afterDraw?.Invoke(); + } + finally + { + NativeImGui.End(); + } + }, deltaTime); + } + + public void Dispose() + { + if (m_ownsServices) + { + runtime.Dispose(); + console.Dispose(); + } + ui.Dispose(); + if (m_ownsServices) + { + m_types?.Dispose(); + m_modules?.Dispose(); + logs.Dispose(); + m_diagnosticScope?.Dispose(); + } + NativeImGui.Raw.DestroyContext(m_nativeContext); + m_clipboard.Dispose(); + if (m_ownsServices) + Directory.Delete(m_root, recursive: true); + } + + private static ImGuiNativeContext* CreateNativeContext( + EditorUiSession ui, + bool supportsVertexOffsets + ) { + ImGuiNativeContext* context = NativeImGui.Raw.CreateContext(null); + ImGuiIO* io = NativeImGui.Raw.GetIO(); + io->DisplaySize = Unsafe.BitCast(new Vector2(1200f, 900f)); + io->DeltaTime = 1f / 60f; + io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; + if (supportsVertexOffsets) + io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasVtxOffset; + io->ConfigFlags |= (int)ImGuiConfigFlags.NavEnableKeyboard; + // Fixture input uses an explicit Control convention on every runner. + io->ConfigMacOsxBehaviors = 0; + io->Fonts->RendererHasTextures = 1; + io->IniFilename = null; + using (ui.Enter()) + ui.widgets.SetupStyle(); + return context; + } + + private static void ValidateDrawData() + { + ImDrawData* data = NativeImGui.Raw.GetDrawData(); + for (int listIndex = 0; listIndex < data->CmdListsCount; listIndex++) + { + ImDrawList* list = data->CmdLists.Data[listIndex]; + for (int commandIndex = 0; commandIndex < list->CmdBuffer.Size; commandIndex++) + { + ImDrawCmd command = list->CmdBuffer.Data[commandIndex]; + ulong lastIndex = (ulong)command.IdxOffset + command.ElemCount; + if (lastIndex > (ulong)list->IdxBuffer.Size) + throw new InvalidOperationException("Native draw data exceeded its index buffer."); + for (ulong index = command.IdxOffset; index < lastIndex; index++) + if ((ulong)command.VtxOffset + list->IdxBuffer.Data[index] >= (ulong)list->VtxBuffer.Size) + throw new InvalidOperationException("Native draw data exceeded its vertex buffer after applying the command offset."); + } + } + } + + internal sealed class InactivePlayMode : IEditorPlayMode + { + private readonly Inno.Core.Events.EventDispatcher m_events = new(); + public EditorPlayModeState state => EditorPlayModeState.Editing; + public bool isPlaying => false; + public string? lastFailure => null; + public LogSessionId activeSessionId => LogSessionId.none; + public IDisposable ListenStateChanges(Action listener) => m_events.CreateHub(); + public bool EnterPlayMode() => false; + public bool ExitPlayMode() => false; + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelInput.cs b/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelInput.cs new file mode 100644 index 000000000..e3e25f95a --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/NativePanelInput.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.InteropServices; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using NativeImGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +internal static unsafe class NativePanelInput +{ + internal static void Focus( + NativePanelHost host, + EditorPanelExtension panel, + string windowFragment + ) { + host.DrawPanel(panel, () => + { + ImGuiNativeContext* context = NativeImGui.Raw.GetCurrentContext(); + for (int index = 0; index < context->Windows.Size; index++) + { + ImGuiWindow* window = context->Windows.Data[index]; + string? name = Marshal.PtrToStringUTF8((nint)window->Name); + if (window->Active != 0 && name?.Contains(windowFragment, StringComparison.Ordinal) == true) + { + NativeImGui.Raw.SetWindowFocus(window->Name); + return; + } + } + throw new InvalidOperationException("The actual input owner window is absent: " + windowFragment); + }); + } + + internal static void Key( + NativePanelHost host, + EditorPanelExtension panel, + ImGuiKey key, + bool shift = false, + bool primary = false + ) { + ImGuiIO* io = NativeImGui.Raw.GetIO(); + if (shift) + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModShift, 1); + if (primary) + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 1); + NativeImGui.Raw.AddKeyEvent(io, key, 1); + host.DrawPanel(panel); + NativeImGui.Raw.AddKeyEvent(io, key, 0); + if (shift) + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModShift, 0); + if (primary) + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 0); + host.DrawPanel(panel); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/NativeProjectPanelHost.cs b/tests/editor/Inno.Editor.PlayMode.Tests/NativeProjectPanelHost.cs new file mode 100644 index 000000000..29db505f5 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/NativeProjectPanelHost.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Reflection; +using Inno.Adapter.Modules.DotNet; +using Inno.Adapter.Serialization.DotNet; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Identity; +using Inno.Core.Input; +using Inno.Core.IO; +using Inno.Core.Logging; +using Inno.Core.Settings; +using Inno.Core.Serialization; +using Inno.Editor.Core; +using Inno.Editor.Diagnostics; +using Inno.Editor.ImGui; +using Inno.Editor.Graph; +using Inno.Editor.Interactions; +using Inno.Editor.Scene; +using Inno.Plugins.Authoring; +using Inno.Runtime; +using Inno.Scene; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +internal sealed class NativeProjectPanelHost : IDisposable +{ + private readonly string m_root = Path.Combine(Path.GetTempPath(), "InnoNativeProjectPanels", Guid.NewGuid().ToString("N")); + private readonly EngineHost m_engine; + private readonly RuntimeSession m_session; + private readonly IDisposable m_execution; + private readonly EditorConsole m_console; + private readonly ProjectSettingsStore m_settings; + private readonly PluginEnvironment m_plugins; + + internal NativeProjectPanelHost( + bool supportsVertexOffsets = false, + bool includeSettings = false, + IByteDocumentStore? settingsDocument = null + ) { + _ = Assembly.Load("Inno.Editor.Panel.Hierarchy"); + _ = Assembly.Load("Inno.Editor.Panel.FileBrowser"); + _ = Assembly.Load("Inno.Editor.Workbench"); + _ = Assembly.Load("Inno.Scene.Assets"); + string source = Path.Combine(m_root, "Assets"); + string library = Path.Combine(m_root, "Library"); + string pluginRoot = Path.Combine(m_root, "Plugins"); + Directory.CreateDirectory(source); + m_engine = new EngineHostBuilder().UseMetadataSources( + new DotNetAssemblyCatalogSource(typeof(NativeProjectPanelHost).Assembly), + new ReflectionTypeCatalogSource(), + new ReflectionSerializationMetadataSource()).Build(); + m_session = m_engine.CreateSession(new RuntimeSessionOptions + { + kind = RuntimeSessionKind.Edit, + applicationId = "inno.tests.native-panels", + createLogSink = _ => new FileLogSink(Path.Combine(library, "Logs")), + jobExecutionMode = RuntimeJobExecutionMode.SingleThread + }); + m_execution = m_session.EnterExecutionScope(); + var identities = new IdentityAllocator(); + assets = new AssetPipeline(m_engine.modules, m_engine.types, m_engine.serialization, identities, + m_engine.diagnostics, m_engine.logs, AssetPipelineOptions.Create(source, library) with { enableFileSystemWatcher = false }); + m_settings = new ProjectSettingsStore(settingsDocument ?? new FileByteDocumentStore(Path.Combine(m_root, "Settings.Project.inno")), + m_engine.types, m_engine.serialization, new ProjectId("tests.native-panels"), AssetSerializationContext.Create(assets)); + var pluginSources = new PluginSourceService(m_engine.serialization, pluginRoot, library); + m_plugins = new PluginEnvironment(assets, m_settings, m_engine.serialization, pluginRoot, library, pluginSources.Scan(), m_engine.modules.generations); + m_console = new EditorConsole(m_engine.logs, m_engine.diagnostics, new NativePanelHost.InactivePlayMode()); + m_console.Start(); + var ui = new EditorUiSession(); + reloads = new EditorReloadCoordinator(); + SettingsPanelServices? settingsServices = includeSettings + ? new SettingsPanelServices(m_root, assets, m_plugins, m_settings, m_engine) + : null; + if (includeSettings) + _ = Assembly.Load("Inno.Editor.Panel.Settings"); + runtime = new EditorInteractionRuntime( + new EditorContext(m_root, new EditorKeyboardPolicy(KeyModifier.Control, "Ctrl")), + m_engine.types, + m_engine.logs, + [ + m_engine.types, m_engine.serialization, m_session, identities, m_session.identities, + assets, m_plugins, m_settings, reloads, ui, m_console, + .. (settingsServices?.services ?? []) + ]); + runtime.Start(); + _ = runtime.panelCount; + Assert.True(runtime.interactions.TryGetModule(out var probe)); + workspace = probe!.workspace; + graphs = probe.graphs; + edits = probe.edits; + native = new NativePanelHost(runtime, ui, m_console, m_engine.logs, m_engine.diagnostics, supportsVertexOffsets); + } + + internal EditorReloadCoordinator reloads { get; } + internal ProjectSettingsStore projectSettings => m_settings; + internal AssetPipeline assets { get; } + internal SerializationRegistry serialization => m_engine.serialization; + internal string projectRoot => m_root; + internal EditorInteractionRuntime runtime { get; } + internal IEditorSceneWorkspace workspace { get; } + internal GraphEditorModule graphs { get; } + internal SceneEdits edits { get; } + internal NativePanelHost native { get; } + + internal GameScene CreateScene() => m_session.scenes.LoadNewSceneAdditive(); + internal void RefreshTypes() => m_engine.types.Rebuild(); + + public void Dispose() + { + runtime.Dispose(); + native.Dispose(); + m_console.Dispose(); + m_plugins.Dispose(); + assets.Dispose(); + m_settings.Dispose(); + m_execution.Dispose(); + m_session.Dispose(); + m_engine.Dispose(); + Directory.Delete(m_root, recursive: true); + } +} + +[EditorModule("tests.native-project-panels", order: 2000)] +public sealed class NativeProjectPanelProbe( + IEditorSceneWorkspace workspace, + GraphEditorModule graphs, + SceneEdits edits +) : EditorModule { + public IEditorSceneWorkspace workspace { get; } = workspace; + public GraphEditorModule graphs { get; } = graphs; + public SceneEdits edits { get; } = edits; +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/PlayModeLogRetentionTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/PlayModeLogRetentionTests.cs index a4fde5537..5cb91fabc 100644 --- a/tests/editor/Inno.Editor.PlayMode.Tests/PlayModeLogRetentionTests.cs +++ b/tests/editor/Inno.Editor.PlayMode.Tests/PlayModeLogRetentionTests.cs @@ -196,7 +196,16 @@ private sealed class FakePlayMode : IEditorPlayMode public LogSessionId activeSessionId { get; private set; } - public event Action? stateChanged; + private readonly Inno.Core.Events.EventDispatcher m_stateDispatcher = new(); + private readonly Inno.Core.Events.EventHub m_stateEvents; + + internal FakePlayMode() => m_stateEvents = m_stateDispatcher.CreateHub(); + public IDisposable ListenStateChanges(Action listener) + => m_stateEvents.Listen(notification => listener(notification.state)); + private sealed class StateNotification(EditorPlayModeState state) : Inno.Core.Events.Event + { + internal EditorPlayModeState state { get; } = state; + } public bool EnterPlayMode() => false; @@ -218,7 +227,7 @@ internal void End() internal void SetState(EditorPlayModeState value) { state = value; - stateChanged?.Invoke(value); + m_stateDispatcher.Emit(new StateNotification(value)); } } } diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/SceneDirtyCaptureTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/SceneDirtyCaptureTests.cs new file mode 100644 index 000000000..85118e13c --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/SceneDirtyCaptureTests.cs @@ -0,0 +1,203 @@ +using System; +using System.Security.Cryptography; +using System.Threading; +using Inno.Assets; +using Inno.Core.Serialization; +using Inno.Core.Serialization.Converters; +using Inno.Core.Mathematics; +using Inno.Extensibility.Types; +using Inno.Scene; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed class SceneDirtyCaptureTests +{ + [Fact] + public void OwnedRevisionCaptureKeepsDirtyStateExactAcrossHistoryRestoreAndDirectWrites() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject value = scene.CreateObject("Original"); + host.workspace.Save(scene, string.Empty); + byte[] saved = Hash(host, scene); + Check(false); + host.edits.RenameGameObject(value, "Changed"); + Check(true); + Assert.True(host.runtime.interactions.history.Undo().succeeded); + Check(false); + Assert.True(host.runtime.interactions.history.Redo().succeeded); + Check(true); + Assert.True(host.runtime.interactions.history.Undo().succeeded); + Check(false); + Assert.True(host.edits.ChangeProperty(value.transform, "localPosition", + () => value.transform.localPosition = new Vector3(3, 4, 5), "Move")); + Check(true); + Assert.True(host.runtime.interactions.history.Undo().succeeded); + Check(false); + Assert.True(host.runtime.interactions.history.Redo().succeeded); + Check(true); + value.transform.localPosition = Vector3.ZERO; + Check(false); + + void Check(bool expected) + { + Thread.Sleep(120); + Assert.Equal(expected, !Hash(host, scene).AsSpan().SequenceEqual(saved)); + Assert.Equal(expected, host.workspace.IsDirty(scene)); + } + } + + [Fact] + public void CompleteSceneCapturesReuseOnlyUnchangedNeutralState() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject owner = scene.CreateObject("Owner"); + GameObject target = scene.CreateObject("Target"); + DirtyCaptureComponent component = owner.AddComponent(); + component.target = target; + SerializationContext context = AssetSerializationContext.Create(host.assets, new AssetDependencyCollection(includeLastKnownPaths: false)); + SerializationSnapshot before = host.serialization.CaptureSnapshot(scene, context); + Assert.Equal(host.serialization.Serialize(scene, context), before.ToArray()); + Assert.Same(before, host.serialization.CaptureSnapshot(scene, context, before)); + component.number = 42; + SerializationSnapshot changed = host.serialization.CaptureSnapshot(scene, context, before); + Assert.NotSame(before, changed); + Assert.Equal(host.serialization.Serialize(scene, context), changed.ToArray()); + Assert.Same(changed, host.serialization.CaptureSnapshot(scene, context, changed)); + component.number = 0; + SerializationSnapshot restored = host.serialization.CaptureSnapshot(scene, context, changed); + Assert.Equal(before.ToArray(), restored.ToArray()); + Assert.Same(restored, host.serialization.CaptureSnapshot(scene, context, restored)); + } + + [Fact] + public void DirtyInspectionIncludesReorderedComponentSnapshots() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject owner = scene.CreateObject("Owner"); + DirtyCaptureComponent first = owner.AddComponent(); + _ = owner.AddComponent(); + host.workspace.Save(scene, string.Empty); + byte[] saved = Hash(host, scene); + Thread.Sleep(120); + Assert.False(host.workspace.IsDirty(scene)); + int original = owner.GetComponentIndex(first); + owner.SetComponentIndex(first, owner.GetComponents().Count - 1); + Assert.False(saved.AsSpan().SequenceEqual(Hash(host, scene))); + Thread.Sleep(120); + Assert.True(host.workspace.IsDirty(scene)); + owner.SetComponentIndex(first, original); + Assert.Equal(saved, Hash(host, scene)); + Thread.Sleep(120); + Assert.False(host.workspace.IsDirty(scene)); + } + + [Fact] + public void ExactDirtyInspectionFindsDirectFieldsConverterStateReferencesAndTopology() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + GameObject owner = scene.CreateObject("Owner"); + DirtyCaptureComponent component = owner.AddComponent(); + GameObject target = scene.CreateObject("Target"); + host.workspace.Save(scene, string.Empty); + byte[] saved = Hash(host, scene); + Check(false); + component.number = 7; + Check(true); + component.number = 0; + Check(false); + component.state.value = 23; + Check(true); + component.state.value = 0; + Check(false); + component.target = target; + Check(true); + component.target = null; + Check(false); + target.SetActive(false); + Check(true); + target.SetActive(true); + Check(false); + GameObject added = scene.CreateObject("Added"); + Check(true); + scene.DestroyObject(added); + Check(false); + Assert.True(component.state.reads >= 10); + + void Check(bool expected) + { + Thread.Sleep(120); + Assert.Equal(expected, !Hash(host, scene).AsSpan().SequenceEqual(saved)); + Assert.Equal(expected, host.workspace.IsDirty(scene)); + } + } + + [Fact] + public void FailedCapturePreservesTheSavedBaselineAndRecoversWithoutPublishingCleanState() + { + using var host = new NativeProjectPanelHost(); + GameScene scene = host.CreateScene(); + DirtyCaptureComponent component = scene.CreateObject("Owner").AddComponent(); + host.workspace.Save(scene, string.Empty); + Thread.Sleep(120); + Assert.False(host.workspace.IsDirty(scene)); + component.state.reject = true; + Thread.Sleep(120); + Assert.True(host.workspace.IsDirty(scene)); + component.state.reject = false; + component.number = 9; + Thread.Sleep(120); + Assert.True(host.workspace.IsDirty(scene)); + component.number = 0; + Thread.Sleep(120); + Assert.False(host.workspace.IsDirty(scene)); + component.number = 17; + host.workspace.Save(scene, string.Empty); + Thread.Sleep(120); + Assert.False(host.workspace.IsDirty(scene)); + component.number = 0; + Thread.Sleep(120); + Assert.True(host.workspace.IsDirty(scene)); + } + + private static byte[] Hash( + NativeProjectPanelHost host, + GameScene scene + ) => SHA256.HashData(host.serialization.Serialize(scene, + AssetSerializationContext.Create(host.assets, new AssetDependencyCollection(includeLastKnownPaths: false)))); +} + +[StableTypeId("942e794b-1c53-45a6-80e6-a641d76fdd09")] +[AllowMultipleComponent] +internal sealed class DirtyCaptureComponent : GameComponent +{ + [SerializableProperty] public int number; + [SerializableProperty] public GameObject? target; + [SerializableProperty] public DirtyCaptureValue state { get; set; } = new(); +} + +internal sealed class DirtyCaptureValue +{ + internal int value; + internal bool reject; + internal int reads; +} + +internal sealed class DirtyCaptureValueConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + DirtyCaptureValue value + ) { + value.reads++; + writer.Write("value", value.value); + if (value.reject) + throw new InvalidOperationException("The test-owned converter rejected capture."); + } + + public override DirtyCaptureValue Read(SerializationReader reader) => new() { value = reader.Read("value") }; +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/SceneDocumentLifecycleNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/SceneDocumentLifecycleNativeTests.cs new file mode 100644 index 000000000..7d74386d0 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/SceneDocumentLifecycleNativeTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using Inno.Editor.Interactions; +using Inno.Scene; +using Xunit; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed class SceneDocumentLifecycleNativeTests +{ + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void OneHundredSavedSceneCyclesRetireLiveScenesAndPreserveSourceAndHistory() + { + using var host = new NativeProjectPanelHost(); + GameScene original = host.CreateScene(); + _ = original.CreateObject("Persistent lifecycle object"); + string path = host.workspace.Save(original, string.Empty); + byte[] source = File.ReadAllBytes(Path.Combine(host.projectRoot, "Assets", path)); + Assert.True(host.edits.CloseScene(original)); + EditorPanelExtension panel = host.native.GetPanel("scene.hierarchy"); + var retired = new List>(); + for (int iteration = 0; iteration < 100; iteration++) + retired.Add(OpenDrawAndClose(host, panel, path)); + Assert.Empty(host.workspace.scenes); + Assert.True(host.runtime.interactions.history.canUndo); + Assert.Equal(source, File.ReadAllBytes(Path.Combine(host.projectRoot, "Assets", path))); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.All(retired, static reference => Assert.False(reference.TryGetTarget(out _))); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference OpenDrawAndClose( + NativeProjectPanelHost host, + EditorPanelExtension panel, + string path + ) { + GameScene scene = host.workspace.Open(path); + host.native.DrawPanel(panel); + Assert.True(host.edits.CloseScene(scene)); + host.native.DrawPanel(panel); + Assert.True(scene.isDestroyed); + return new WeakReference(scene); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/SettingsApplyNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/SettingsApplyNativeTests.cs new file mode 100644 index 000000000..b9388dabc --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/SettingsApplyNativeTests.cs @@ -0,0 +1,340 @@ +using System; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Inno.Core.IO; +using Inno.Core.Serialization; +using Inno.Core.Settings; +using Inno.Editor.Interactions; +using Inno.Editor.Settings; +using Inno.Native.ImGui; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class SettingsApplyNativeTests +{ + [Fact] + [Trait("Acceptance", "Workflows")] + public void FaultedDrawerReleasesReloadAdmissionUntilItsExtensionsRecover() + { + using var host = new NativeProjectPanelHost(includeSettings: true); + var driver = new SettingsInput(host); + Assert.True(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + driver.Draw(); + AtomicEditorSetting.throwOnDraw = true; + try + { + Assert.ThrowsAny(() => driver.Search("Atomic Editor")); + } + finally + { + AtomicEditorSetting.throwOnDraw = false; + } + Assert.True(host.reloads.CanActivateReload(out _)); + EditorActionState state = host.runtime.interactions.For("editor/main-menu").Query("editor.settings.open"); + Assert.True(state.isVisible); + Assert.False(state.isEnabled); + Assert.False(string.IsNullOrWhiteSpace(state.unavailableReason)); + Assert.False(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + host.RefreshTypes(); + Assert.True(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + driver.Draw(); + driver.Search("Atomic Editor"); + driver.CloseClean(); + Assert.False(driver.isVisible); + Assert.True(host.reloads.CanActivateReload(out _)); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void OneHundredSettingsCyclesReleaseDrawnValuesAndReloadAdmission() + { + AtomicEditorSetting.snapshots.Clear(); + AtomicEditorSetting.observe = true; + try + { + using var host = new NativeProjectPanelHost(includeSettings: true); + for (int cycle = 0; cycle < 100; cycle++) + OpenAndClose(host); + Assert.True(AtomicEditorSetting.snapshots.Count >= 100); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.All(AtomicEditorSetting.snapshots, snapshot => Assert.False(snapshot.TryGetTarget(out _))); + } + finally + { + AtomicEditorSetting.observe = false; + AtomicEditorSetting.snapshots.Clear(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void OpenAndClose(NativeProjectPanelHost host) + { + Assert.True(host.reloads.CanRequestReload(out _)); + Assert.True(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + var driver = new SettingsInput(host); + driver.Draw(); + Assert.False(host.reloads.CanActivateReload(out string? reason)); + Assert.Contains("Settings", reason); + driver.Search("Atomic Editor"); + driver.CloseClean(); + Assert.False(driver.isVisible); + Assert.True(host.reloads.CanRequestReload(out _)); + Assert.True(host.reloads.CanActivateReload(out _)); + } + + [Fact] + [Trait("Acceptance", "Workflows")] + public void FailedRollbackKeepsHistoryFaultedAndPreventsFurtherApply() + { + string directory = Path.Combine(Path.GetTempPath(), "InnoSettingsRollback", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + try + { + var document = new FailingSettingsDocument(new FileByteDocumentStore(Path.Combine(directory, "Project.inno"))); + using var host = new NativeProjectPanelHost(includeSettings: true, settingsDocument: document); + Assert.True(host.runtime.interactions.TryGetModule(out var editor)); + var driver = new SettingsInput(host); + Assert.True(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + driver.Draw(); + AtomicEditorSetting.stage = true; + driver.Search("Atomic Editor"); + Assert.False(AtomicEditorSetting.stage); + AtomicProjectEditor.stage = true; + driver.Search("Atomic Project"); + Assert.False(AtomicProjectEditor.stage); + string editorFile = Path.Combine(host.projectRoot, "Settings.Editor.inno"); + string savedFile = Path.Combine(directory, "Editor.inno"); + bool obstructed = false; + using IDisposable listener = editor!.ListenChanges(_ => + { + if (obstructed) + return; + File.Move(editorFile, savedFile); + Directory.CreateDirectory(editorFile); + obstructed = true; + }); + document.failWrites = true; + try + { + driver.Apply(); + Assert.True(obstructed); + Assert.Equal(42, editor.Get("Editor/Atomic Editor/Value").GetAsInt32("value")); + Assert.Equal(0, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + Assert.True(host.runtime.interactions.history.isFaulted); + Assert.False(host.runtime.interactions.history.canUndo); + Assert.False(host.runtime.interactions.history.canRedo); + Assert.False(string.IsNullOrWhiteSpace(host.runtime.interactions.history.faultReason)); + } + finally + { + if (obstructed) + { + Directory.Delete(editorFile); + File.Move(savedFile, editorFile); + } + } + document.failWrites = false; + driver.Apply(); + Assert.True(host.runtime.interactions.history.isFaulted); + Assert.Equal(0, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + driver.Cancel(); + host.native.logs.Flush(); + Assert.False(driver.isVisible, driver.diagnostic + Environment.NewLine + string.Join(Environment.NewLine, host.native.console.Capture().occurrences.Select(entry => entry.message))); + } + finally + { + AtomicEditorSetting.stage = false; + AtomicProjectEditor.stage = false; + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + [Trait("Acceptance", "Workflows")] + public void FailedSettingsBatchPreservesAllOwnersAndDraftsForRetry() + { + string directory = Path.Combine(Path.GetTempPath(), "InnoSettingsPublication", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + try + { + var document = new FailingSettingsDocument(new FileByteDocumentStore(Path.Combine(directory, "Project.inno"))); + using var host = new NativeProjectPanelHost(includeSettings: true, settingsDocument: document); + Assert.True(host.runtime.interactions.TryGetModule(out var editor)); + var driver = new SettingsInput(host); + Assert.True(host.runtime.interactions.For("editor/main-menu").Execute("editor.settings.open")); + driver.Draw(); + AtomicEditorSetting.stage = true; + driver.Search("Atomic Editor"); + Assert.False(AtomicEditorSetting.stage); + AtomicProjectEditor.stage = true; + driver.Search("Atomic Project"); + Assert.False(AtomicProjectEditor.stage); + document.failWrites = true; + driver.Apply(); + Assert.Equal(0, editor!.Get("Editor/Atomic Editor/Value").GetAsInt32("value")); + Assert.Equal(0, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + Assert.False(host.runtime.interactions.history.canUndo); + Assert.False(host.runtime.interactions.history.isFaulted); + document.failWrites = false; + driver.Apply(); + Assert.Equal(42, editor.Get("Editor/Atomic Editor/Value").GetAsInt32("value")); + Assert.Equal(84, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + Assert.True(host.runtime.interactions.history.Undo().succeeded); + Assert.Equal(0, editor.Get("Editor/Atomic Editor/Value").GetAsInt32("value")); + Assert.Equal(0, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + Assert.False(host.runtime.interactions.history.canUndo); + Assert.True(host.runtime.interactions.history.Redo().succeeded); + Assert.Equal(42, editor.Get("Editor/Atomic Editor/Value").GetAsInt32("value")); + Assert.Equal(84, host.projectSettings.Get(new("tests.ui.atomic-settings")).value); + } + finally + { + AtomicEditorSetting.stage = false; + AtomicProjectEditor.stage = false; + Directory.Delete(directory, recursive: true); + } + } + + private sealed class FailingSettingsDocument(IByteDocumentStore owner) : IByteDocumentStore + { + internal bool failWrites { get; set; } + public string documentName => owner.documentName; + public bool canWrite => owner.canWrite; + public bool exists => owner.exists; + public byte[]? Read() => owner.Read(); + public void Write(ReadOnlySpan data) + { + if (failWrites) + throw new IOException("Controlled project settings publication failure."); + owner.Write(data); + } + } + + private sealed class SettingsInput(NativeProjectPanelHost host) + { + private Vector2 m_search; + private Vector2 m_apply; + private Vector2 m_cancel; + internal bool isVisible { get; private set; } + internal string diagnostic => $"Apply={m_apply}; Cancel={m_cancel}; Search={m_search}"; + + internal void Draw() + { + host.native.Frame(() => + { + NativeGui.SetNextWindowPos(new Vector2(20f), (int)ImGuiCond.Always); + NativeGui.SetNextWindowSize(new Vector2(1000f, 800f), (int)ImGuiCond.Always); + bool open = true; + _ = NativeGui.Begin("Settings acceptance", ref open, (int)ImGuiWindowFlags.NoSavedSettings); + try + { + Vector2 content = NativeGui.GetCursorScreenPos(); + m_search = content + new Vector2(115f, NativeGui.GetFrameHeight() * 0.5f + 3f); + EditorModalExtension modal = Assert.Single(host.runtime.modals, modal => modal.id == "editor.settings"); + Assert.True(modal.TryGetPresentation(out var presentation)); + isVisible = presentation.isVisible; + if (!isVisible) + return; + Assert.True(modal.Draw(host.runtime.context), "The operational Settings failure must not quarantine the modal."); + m_apply = (NativeGui.GetItemRectMin() + NativeGui.GetItemRectMax()) * 0.5f; + var style = NativeGui.Raw.GetStyle(); + m_cancel = new Vector2(NativeGui.GetItemRectMin().X - style->ItemSpacing.X + - NativeGui.CalcTextSize("Cancel").X * 0.5f - style->FramePadding.X, m_apply.Y); + } + finally + { + NativeGui.End(); + } + }); + } + + internal void Search(string text) + { + Click(m_search); + ImGuiIO* io = NativeGui.Raw.GetIO(); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 1); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.A, 1); + Draw(); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.A, 0); + NativeGui.Raw.AddKeyEvent(io, ImGuiKey.ImGuiModCtrl, 0); + Draw(); + foreach (char character in text) + NativeGui.Raw.AddInputCharacter(io, character); + Draw(); + Draw(); + } + + internal void Apply() => Click(m_apply); + internal void Cancel() => Click(m_cancel); + internal void CloseClean() => Click(m_apply); + + private void Click(Vector2 point) + { + ImGuiIO* io = NativeGui.Raw.GetIO(); + NativeGui.Raw.AddMousePosEvent(io, point.X, point.Y); + Draw(); + NativeGui.Raw.AddMouseButtonEvent(io, 0, 1); + Draw(); + NativeGui.Raw.AddMouseButtonEvent(io, 0, 0); + Draw(); + Draw(); + } + } +} + +[EditorSettingPath("Editor/Atomic Editor/Value")] +public sealed class AtomicEditorSetting : EditorSetting +{ + public static bool stage; + public static bool throwOnDraw; + public static bool observe; + public static readonly List> snapshots = []; + public override EditorSettingObject defaultValue + { + get + { + var value = new EditorSettingObject(); + value.SetAsInt32("value", 0); + return value; + } + } + protected override void OnDraw(EditorSettingObject setting) + { + if (throwOnDraw) + throw new InvalidOperationException("Controlled Settings drawer failure."); + if (observe && (snapshots.Count == 0 || !snapshots[^1].TryGetTarget(out var last) || !ReferenceEquals(last, setting))) + snapshots.Add(new WeakReference(setting)); + if (!stage) + return; + setting.SetAsInt32("value", 42); + stage = false; + } +} + +[GenerateSerializationConverter] +[ProjectSettingDefinition("tests.ui.atomic-settings")] +public sealed class AtomicProjectSetting : ISerializable +{ + [SerializableProperty] + public int value { get; set; } +} + +[ProjectSettingPath("Project/Atomic Project/Value")] +public sealed class AtomicProjectEditor : ProjectSettingEditor +{ + public static bool stage; + protected override void OnDraw(AtomicProjectSetting setting) + { + if (!stage) + return; + setting.value = 84; + stage = false; + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/SettingsPanelServices.cs b/tests/editor/Inno.Editor.PlayMode.Tests/SettingsPanelServices.cs new file mode 100644 index 000000000..56e2dbb70 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/SettingsPanelServices.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Inno.Assets.Pipeline; +using Inno.Build; +using Inno.Build.Managed; +using Inno.Core.Settings; +using Inno.Plugins.Authoring; +using Inno.Runtime; +using Inno.Scripting.Compiler; + +namespace Inno.Editor.PlayMode.Tests; + +internal sealed class SettingsPanelServices +{ + internal SettingsPanelServices( + string root, + AssetPipeline assets, + PluginEnvironment plugins, + ProjectSettingsStore settings, + EngineHost engine + ) { + string host = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + var compiler = new ScriptCompiler(new ScriptCompilerOptions + { + projectRootDirectory = root, + compilationEnvironment = new SdkScriptCompilationEnvironment(host, root) + }, assets, plugins); + var target = new FixtureTarget(); + var pipeline = new BuildPipeline(assets, plugins, settings, engine.serialization, engine.modules.generations, + compiler, Path.Combine(root, "SupportPacks"), [new GameBuildTargetBinding(target, target)], + new ManagedDeploymentCatalog([new FixtureDeployment()])); + var build = new BuildSettingsStore(Path.Combine(root, "Settings.Build.inno"), engine.serialization, + BuildSettings.CreateDefault("Settings acceptance", string.Empty, target.id)); + services = [pipeline, build]; + } + + internal IReadOnlyList services { get; } + + private sealed class FixtureTarget : IGameBuildTarget, IGameContentCompiler + { + public BuildTargetId id => new("tests-ui-settings"); + public BuildTargetId target => id; + public ManagedDeploymentId defaultManagedDeployment => new("tests-ui-managed"); + public string runtimeIdentifier => "tests-ui-rid"; + public string displayName => "Settings fixture"; + public void Validate(string directory) => throw new NotSupportedException("Settings acceptance does not package players."); + public ValueTask PackageAsync( + GameBuildPackageContext context, + CancellationToken cancellationToken = default + ) => throw new NotSupportedException("Settings acceptance does not package players."); + public ValueTask CompileAsync( + GameBuildContentContext context, + CancellationToken cancellationToken = default + ) => throw new NotSupportedException("Settings acceptance does not compile content."); + } + + private sealed class FixtureDeployment : IManagedDeploymentCompiler + { + public ManagedDeploymentId id => new("tests-ui-managed"); + public ManagedDeploymentCapabilities capabilities => new(["tests-ui-rid"], true, false, false); + public ValueTask CompileAsync( + ManagedDeploymentRequest request, + CancellationToken cancellationToken = default + ) => throw new NotSupportedException("Settings acceptance does not compile players."); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/StatisticsPanelNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/StatisticsPanelNativeTests.cs new file mode 100644 index 000000000..4fb40b275 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/StatisticsPanelNativeTests.cs @@ -0,0 +1,86 @@ +using System; +using System.Numerics; +using Inno.Editor.Interactions; +using Inno.Native.ImGui; +using Xunit; +using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class StatisticsPanelNativeTests +{ + [Fact] + public void ActualPanelSamplesAtHalfSecondIntervalsAndResetsAfterHiddenFrames() + { + using var host = new NativePanelHost("Inno.Editor.Panel.Stats"); + EditorPanelExtension panel = host.GetPanel("diagnostics.stats"); + Assert.Equal(0, DrawAndCountTrendVertices(host, panel, 0.1f)); + for (int frame = 0; frame < 4; frame++) + Assert.Equal(0, DrawAndCountTrendVertices(host, panel, 0.1f)); + int twoSampleVertices = DrawAndCountTrendVertices(host, panel, 0.1f); + Assert.True(twoSampleVertices > 0); + panel.isOpen = false; + host.DrawPanel(panel, deltaTime: 0.1f); + host.DrawPanel(panel, deltaTime: 0.1f); + panel.isOpen = true; + Assert.Equal(0, DrawAndCountTrendVertices(host, panel, 0.1f)); + } + + [Theory] + [InlineData(0.5f, 120)] + [InlineData(0.6f, 101)] + public void ActualPanelBoundsCapacityAndElapsedSampleWindow( + float deltaTime, + int maximumSamples + ) { + using var host = new NativePanelHost("Inno.Editor.Panel.Stats"); + EditorPanelExtension panel = host.GetPanel("diagnostics.stats"); + Assert.Equal(0, DrawAndCountTrendVertices(host, panel, deltaTime)); + int verticesPerInterval = DrawAndCountTrendVertices(host, panel, deltaTime); + Assert.True(verticesPerInterval > 0); + int maximum = 0; + for (int frame = 0; frame < 360; frame++) + { + int vertices = DrawAndCountTrendVertices(host, panel, deltaTime); + maximum = Math.Max(maximum, vertices); + Assert.InRange(vertices, 0, (maximumSamples - 1) * verticesPerInterval); + } + Assert.True(maximum >= (maximumSamples - 3) * verticesPerInterval); + } + + private static int DrawAndCountTrendVertices( + NativePanelHost host, + EditorPanelExtension panel, + float deltaTime + ) { + int count = 0; + host.Frame(() => + { + NativeImGui.SetNextWindowPos(new(20f, 20f), (int)ImGuiCond.Always); + NativeImGui.SetNextWindowSize(new(1000f, 800f), (int)ImGuiCond.Always); + bool open = true; + _ = NativeImGui.Begin("Statistics acceptance", ref open, (int)ImGuiWindowFlags.NoSavedSettings); + Vector4 color = new(0.93f, 0.17f, 0.73f, 1f); + uint packed = Ui.ToPackedColor(color); + Ui.PushStyleColor(Inno.Editor.ImGui.ImGuiCol.PlotLines, color); + try + { + Assert.True(panel.Draw(host.runtime.context)); + ImDrawList* draw = NativeImGui.Raw.GetWindowDrawList(); + for (int index = 0; index < draw->VtxBuffer.Size; index++) + { + uint vertex = draw->VtxBuffer.Data[index].Col; + if ((vertex & 0x00ffffff) == (packed & 0x00ffffff) && (vertex >> 24) != 0) + count++; + } + } + finally + { + Ui.PopStyleColor(); + NativeImGui.End(); + } + }, deltaTime); + return count; + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/TreeKeyboardOwnershipNativeTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/TreeKeyboardOwnershipNativeTests.cs new file mode 100644 index 000000000..9e94ba578 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/TreeKeyboardOwnershipNativeTests.cs @@ -0,0 +1,111 @@ +using System; +using System.Numerics; +using Inno.Core.Input; +using Inno.Editor.Core; +using Inno.Editor.ImGui.ImGuiWidget; +using Inno.Native.ImGui; +using Xunit; +using NativeImGui = Inno.Native.ImGui.ImGui; +using Ui = Inno.Editor.ImGui.ImGui; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class TreeKeyboardOwnershipNativeTests +{ + [Theory] + [InlineData(false, KeyModifier.Control)] + [InlineData(true, KeyModifier.Control)] + [InlineData(false, KeyModifier.Super)] + [InlineData(true, KeyModifier.Super)] + public void ListActivationDoesNotToggleDisclosureOrStealAnOverlayButton( + bool commandKeyBehavior, + KeyModifier primaryModifier + ) { + using var host = new NativePanelHost(false, new EditorKeyboardPolicy(primaryModifier, "Super")); + ImGuiIO* io = NativeImGui.Raw.GetIO(); + io->ConfigMacOsxBehaviors = commandKeyBehavior ? (byte)1 : (byte)0; + ImGuiKey primaryKey = primaryModifier == KeyModifier.Super ? ImGuiKey.ImGuiModSuper : ImGuiKey.ImGuiModCtrl; + bool rowFocus = true; + bool overlayFocus = false; + bool open = false; + int toggles = 0; + int overlayPresses = 0; + bool initialize = true; + uint overlayId = 0; + string overlayState = string.Empty; + ListNavigationResult navigation = default; + Action draw = () => + { + NativeImGui.SetNextWindowSize(new Vector2(600f, 400f), (int)ImGuiCond.Always); + bool visible = true; + _ = NativeImGui.Begin("Tree keyboard owner", ref visible, (int)ImGuiWindowFlags.NoSavedSettings); + try + { + if (initialize) + { + host.ui.widgets.SetNextTreeNodeOpen(true); + initialize = false; + } + TreeNodeResult row = host.ui.widgets.TreeNode("keyboard-row", _ => Ui.Text("A domain-independent row"), new() + { + requestFocus = rowFocus, + drawViewportOverlay = () => + { + NativeImGui.SameLine(0f, -1f); + if (overlayFocus) + Ui.FocusNextItem(); + if (NativeImGui.SmallButton("Overlay")) + overlayPresses++; + overlayId = NativeImGui.Raw.GetCurrentContext()->LastItemData.ID; + ImGuiNativeContext* state = NativeImGui.Raw.GetCurrentContext(); + overlayState = $"id={overlayId:X}, nav={state->NavId:X}, active={state->ActiveId:X}, activate={state->NavActivateId:X}, pressed={state->NavActivatePressedId:X}, keyOwner={NativeImGui.GetKeyOwner(ImGuiKey.Space):X}"; + } + }); + open = row.isOpen; + if (open) + NativeImGui.TreePop(); + navigation = host.ui.widgets.NavigateList(host.runtime.interactions, 1, 0); + if (navigation.toggle) + toggles++; + } + finally + { + NativeImGui.End(); + } + }; + host.Frame(draw); + host.Frame(draw); + host.Frame(draw); + rowFocus = false; + NativeImGui.Raw.AddKeyEvent(io, primaryKey, 1); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Space, 1); + host.Frame(draw); + Assert.True(open); + Assert.Equal(1, toggles); + Assert.Equal(0, overlayPresses); + host.Frame(() => + { + var input = Ui.GetInput(); + Assert.Equal(primaryModifier == KeyModifier.Control, input.control); + Assert.Equal(primaryModifier == KeyModifier.Super, input.super); + draw(); + }); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Space, 0); + NativeImGui.Raw.AddKeyEvent(io, primaryKey, 0); + host.Frame(draw); + overlayFocus = true; + host.Frame(draw); + overlayFocus = false; + host.Frame(draw); + Assert.Equal(overlayId, NativeImGui.Raw.GetCurrentContext()->NavId); + // Programmatic focus does not enter native keyboard navigation mode by itself. + NativeImGui.SetNavCursorVisible(true); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Space, 1); + host.Frame(draw); + NativeImGui.Raw.AddKeyEvent(io, ImGuiKey.Space, 0); + host.Frame(draw); + Assert.True(overlayPresses == 1, overlayState); + Assert.False(navigation.selectionRequested); + Assert.True(open); + } +} diff --git a/tests/editor/Inno.Editor.PlayMode.Tests/VisualScaleMatrixTests.cs b/tests/editor/Inno.Editor.PlayMode.Tests/VisualScaleMatrixTests.cs new file mode 100644 index 000000000..68aa168e8 --- /dev/null +++ b/tests/editor/Inno.Editor.PlayMode.Tests/VisualScaleMatrixTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using Inno.Editor.ImGui; +using Inno.Native.ImGui; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; +using NativeVector = Inno.Native.ImGui.ImVec2C; + +namespace Inno.Editor.PlayMode.Tests; + +public sealed unsafe class VisualScaleMatrixTests +{ + public static IEnumerable Metrics() + { + foreach (float zoom in new[] { 0.75f, 0.85f, 0.90f, 1f, 1.10f, 1.25f, 1.50f }) + foreach (float density in new[] { 1f, 1.25f, 1.50f, 2f }) + foreach (Vector2 size in new[] { new Vector2(1280f, 720f), new Vector2(1920f, 1080f) }) + yield return [zoom, density, size.X, size.Y]; + } + + [Theory] + [MemberData(nameof(Metrics))] + [Trait("Acceptance", "Visual")] + public void NativeFormGeometryRemainsBalancedAcrossSimulatedDisplayMetrics( + float zoom, + float density, + float width, + float height + ) { + using var host = new NativePanelHost(); + ImGuiIO* io = NativeGui.Raw.GetIO(); + io->DisplaySize = Unsafe.BitCast(new(width, height)); + io->DisplayFramebufferScale = Unsafe.BitCast(new(density)); + host.ui.widgets.style.SetZoom(zoom); + float labelWidth = 0f; + float valueWidth = 0f; + for (int frame = 0; frame < 4; frame++) + { + host.Frame(() => + { + NativeGui.SetNextWindowPos(new Vector2(20f, 20f), (int)Inno.Editor.ImGui.ImGuiCond.Always); + NativeGui.SetNextWindowSize(new Vector2(320f, 500f), (int)Inno.Editor.ImGui.ImGuiCond.Always); + bool open = true; + _ = NativeGui.Begin("Narrow inspector matrix", ref open, (int)Inno.Editor.ImGui.ImGuiWindowFlags.NoSavedSettings); + try + { + var widgets = host.ui.widgets; + widgets.PropertyRow("reference", () => + { + labelWidth = NativeGui.GetContentRegionAvail().X; + widgets.PropertyLabel("Resource reference / 资源引用 / Missing"); + }, () => + { + valueWidth = NativeGui.GetContentRegionAvail().X; + string value = "Unassigned / 未指定"; + widgets.Disabled(true, () => NativeGui.TextUnformatted(value)); + }); + widgets.MixedValue(true, () => Assert.True(widgets.isMixedValue)); + Assert.False(widgets.isMixedValue); + Assert.Equal(zoom, widgets.style.zoom); + Assert.Equal(new Vector2(density), Unsafe.BitCast(io->DisplayFramebufferScale)); + Assert.InRange(NativeGui.GetScrollMaxX(), 0f, 1f); + } + finally + { + NativeGui.End(); + } + }); + } + Assert.InRange(labelWidth / (labelWidth + valueWidth), 0.37f, 0.42f); + Assert.True(float.IsFinite(labelWidth) && float.IsFinite(valueWidth)); + } +} diff --git a/tests/fixtures/editor-ui/PanelLifecycle.editor.cs b/tests/fixtures/editor-ui/PanelLifecycle.editor.cs new file mode 100644 index 000000000..f9676fa01 --- /dev/null +++ b/tests/fixtures/editor-ui/PanelLifecycle.editor.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using InnoEditor.Core; +using InnoEditor.Interactions; +using InnoEditor.Scene; + +namespace EditorUiAcceptance; + +/// +/// Exercises the normal product panel protocol from an ordinary source Editor extension. +/// +/// +/// The current Host's public commands, selection, panel and History entry points. +/// +/// +/// The current Edit workspace whose loaded scenes must survive window-only operations. +/// +[EditorModule("tests.editor-ui.panel-lifecycle", order: 10_000)] +public sealed class PanelLifecycle( + EditorInteractions interactions, + IEditorSceneWorkspace workspace +) : EditorModule +{ + private readonly List m_resources = ["completed-cycles\tmanaged-bytes-after-full-gc\tprivate-bytes\tselection-count\tscene-count\thistory-resident-bytes\thistory-disk-bytes\tpending-events"]; + private readonly List m_preparationResources = ["phase\tmanaged-bytes-after-full-gc\tprivate-bytes\tselection-count\tscene-count\thistory-resident-bytes\thistory-disk-bytes\tpending-events"]; + private string[] m_panels = []; + private bool[] m_originalOpenStates = []; + private string? m_undoName; + private string? m_redoName; + private long m_historyResidentBytes; + private long m_historyDiskBytes; + private int m_selectionCount; + private int m_sceneCount; + private int m_warmup; + private int m_preparationFrames; + private int m_frame; + private int m_cycles; + private bool m_baselineCaptured; + private bool m_completed; + + /// + protected override void OnUpdate(EditorContext context) + { + if (m_completed) + return; + if (m_panels.Length == 0 && m_warmup++ < 20) + { + return; + } + if (m_panels.Length == 0) + { + if (workspace.scenes.Count == 0) + workspace.Open("FlappyBird/FlappyBird.iscene"); + m_panels = interactions.GetPanelDeclarations().Select(static panel => panel.id).Order(StringComparer.Ordinal).ToArray(); + if (m_panels.Length < 8) + throw new InvalidOperationException("The complete Editor product must expose its eight standard panels."); + m_originalOpenStates = new bool[m_panels.Length]; + m_preparationResources.Add(CaptureResourceRow("before-first-panel-draw")); + for (int index = 0; index < m_panels.Length; index++) + { + string panel = m_panels[index]; + if (!interactions.TryGetPanelState(panel, out m_originalOpenStates[index]) || !interactions.OpenPanel(panel)) + throw new InvalidOperationException("The product could not open panel '" + panel + "' for preparation."); + } + m_preparationFrames = 120; + return; + } + if (m_preparationFrames > 0) + { + if (--m_preparationFrames > 0) + return; + foreach (string panel in m_panels) + if (!interactions.ClosePanel(panel)) + throw new InvalidOperationException("The product could not close panel '" + panel + "' after preparation."); + m_preparationResources.Add(CaptureResourceRow("after-120-drawn-frames")); + return; + } + bool expectedOpen = (m_frame++ & 1) != 0; + foreach (string panel in m_panels) + { + if (!interactions.TryGetPanelState(panel, out bool isOpen) || isOpen != expectedOpen) + throw new InvalidOperationException("Panel state or native drawing failed for '" + panel + "' at cycle " + m_cycles + "."); + } + if (expectedOpen) + m_cycles++; + else + { + VerifyDocumentState(); + if (m_cycles % 10 == 0) + CaptureResources(); + } + if (m_cycles == 100 && !expectedOpen) + { + for (int index = 0; index < m_panels.Length; index++) + { + bool restored = m_originalOpenStates[index] ? interactions.OpenPanel(m_panels[index]) : interactions.ClosePanel(m_panels[index]); + if (!restored) + throw new InvalidOperationException("The product could not restore panel '" + m_panels[index] + "'."); + } + string directory = Path.Combine(context.projectDirectory, "Library", "Acceptance"); + Directory.CreateDirectory(directory); + File.WriteAllLines(Path.Combine(directory, "panel-lifecycle.tsv"), + new[] { "panel\tcompleted-open-close-cycles", "protocol\tpublic EditorInteractions / actual product drawing" } + .Concat(m_panels.Select(static panel => panel + "\t100"))); + File.WriteAllLines(Path.Combine(directory, "panel-lifecycle-resources.tsv"), m_resources); + File.WriteAllLines(Path.Combine(directory, "panel-lifecycle-preparation.tsv"), m_preparationResources); + m_completed = true; + return; + } + foreach (string panel in m_panels) + { + bool changed = expectedOpen ? interactions.ClosePanel(panel) : interactions.OpenPanel(panel); + if (!changed) + throw new InvalidOperationException("The panel protocol rejected '" + panel + "'."); + } + } + + private void VerifyDocumentState() + { + IEditorHistory history = interactions.history; + if (!m_baselineCaptured) + { + m_undoName = history.undoName; + m_redoName = history.redoName; + m_historyResidentBytes = history.residentBytes; + m_historyDiskBytes = history.diskBytes; + m_selectionCount = interactions.selection.count; + m_sceneCount = workspace.scenes.Count; + m_baselineCaptured = true; + } + if (history.isFaulted || history.undoName != m_undoName || history.redoName != m_redoName || + history.residentBytes != m_historyResidentBytes || history.diskBytes != m_historyDiskBytes || + interactions.selection.count != m_selectionCount || workspace.scenes.Count != m_sceneCount) + throw new InvalidOperationException("Window-only lifecycle operations changed Edit selection, scenes or committed History state."); + } + + private void CaptureResources() + => m_resources.Add(CaptureResourceRow(m_cycles.ToString(System.Globalization.CultureInfo.InvariantCulture))); + + private string CaptureResourceRow(string observation) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + long managed = GC.GetTotalMemory(forceFullCollection: false); + using Process process = Process.GetCurrentProcess(); + return FormattableString.Invariant($"{observation}\t{managed}\t{process.PrivateMemorySize64}\t{interactions.selection.count}\t{workspace.scenes.Count}\t{interactions.history.residentBytes}\t{interactions.history.diskBytes}\t{interactions.events.pendingCount}"); + } +} diff --git a/tests/fixtures/editor-ui/WorkflowJourney.editor.cs.txt b/tests/fixtures/editor-ui/WorkflowJourney.editor.cs.txt new file mode 100644 index 000000000..b8123ee26 --- /dev/null +++ b/tests/fixtures/editor-ui/WorkflowJourney.editor.cs.txt @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using InnoEngine.Mathematics; +using InnoEngine.Scene; +using InnoEditor.Core; +using InnoEditor.Interactions; +using InnoEditor.PlayMode; +using InnoEditor.Scene; + +/// Drives ordinary feature protocols while the actual product draws every intermediate state. +[EditorModule("tests.editor-ui.workflow-journey", order: 10000)] +public sealed class WorkflowJourney( + IEditorSceneWorkspace workspace, + SceneEdits edits, + EditorInteractions interactions, + IEditorPlayMode play +) : EditorModule +{ + private readonly List m_verified = []; + private Guid m_sceneId; + private Guid m_firstId; + private Guid m_secondId; + private Guid m_componentId; + private Guid m_duplicateId; + private string m_scenePath = string.Empty; + private int m_step; + private int m_playFrames; + private bool m_finished; + + /// + protected override void OnUpdate(EditorContext context) + { + try + { + Advance(context); + } + catch (Exception failure) + { + string directory = Path.Combine(context.projectDirectory, "Library", "Acceptance"); + Directory.CreateDirectory(directory); + File.WriteAllText(Path.Combine(directory, "workflow-failure.txt"), failure.ToString()); + throw; + } + } + + private void Advance(EditorContext context) + { + if (m_finished) + return; + if (m_step > 0) + { + Require(interactions.TryGetPanelState("scene.inspector", out bool inspector) && inspector, + "The actual Inspector was quarantined or closed during the journey."); + Require(interactions.TryGetPanelState("scene.hierarchy", out bool hierarchy) && hierarchy, + "The actual Hierarchy was quarantined or closed during the journey."); + } + if (play.lastFailure is not null) + throw new InvalidOperationException(play.lastFailure); + if (m_step == 0) + { + foreach (string id in new[] { "scene.inspector", "scene.hierarchy", "asset.file-browser" }) + Require(interactions.OpenPanel(id), "The product could not open " + id); + _ = workspace.Open("FlappyBird/FlappyBird.iscene"); + GameScene created = edits.CreateScene(); + edits.RenameScene(created, "UI Workflow Acceptance"); + GameObject first = edits.CreateGameObject(created); + GameObject second = edits.CreateGameObject(created); + edits.RenameGameObject(first, "Workflow First"); + edits.RenameGameObject(second, "Workflow Second"); + first.transform.localPosition = new Vector3(0, 2, 3); + second.transform.localPosition = new Vector3(0, 5, 6); + m_sceneId = created.identity.persistentId; + m_firstId = first.identity.persistentId; + m_secondId = second.identity.persistentId; + m_componentId = edits.AddComponent(first, typeof(WorkflowState)).identity.persistentId; + m_duplicateId = edits.AddComponent(first, typeof(WorkflowState)).identity.persistentId; + interactions.SetSelection([first, second], first); + m_step++; + return; + } + if (m_step == 20) + { + Require(interactions.selection.selectedTarget is null, "Closing a document retained a live target."); + GameScene reopened = workspace.Open(m_scenePath); + GameObject first = reopened.GetObjects().Single(value => value.name == "Workflow First"); + GameObject second = reopened.GetObjects().Single(value => value.name == "Workflow Second"); + WorkflowState state = first.GetComponents().OfType().Single(value => value.number == 7); + Require(first.GetComponents().OfType().Count() == 2 && state.reference == second, + "Save and reopen lost component order, values or references."); + m_sceneId = reopened.identity.persistentId; + m_firstId = first.identity.persistentId; + m_secondId = second.identity.persistentId; + m_componentId = state.identity.persistentId; + interactions.SetSelection(first); + m_verified.Add("actual File Browser and Inspector: save, close, reopen and reference restoration"); + m_step++; + return; + } + if (m_step >= 21) + { + AdvancePlayJourney(context); + return; + } + GameScene scene = workspace.scenes.Single(value => value.identity.persistentId == m_sceneId); + GameObject? firstObject = scene.GetObjects().SingleOrDefault(value => value.identity.persistentId == m_firstId); + GameObject? secondObject = scene.GetObjects().SingleOrDefault(value => value.identity.persistentId == m_secondId); + WorkflowState? component = FindComponent(scene, m_componentId) as WorkflowState; + switch (m_step) + { + case 1: + Require(interactions.selection.count == 2, "Multi-selection was not preserved through actual drawing."); + m_verified.Add("actual Inspector and Hierarchy: mixed multi-selection, duplicate components"); + interactions.SetSelection(firstObject); + break; + case 2: + using (var gesture = new EditorEditGesture(interactions.history, "First drag")) + { + for (int sample = 1; sample <= 3; sample++) + { + int value = sample; + edits.ChangeProperty(component!, nameof(WorkflowState.number), () => component!.number = value, + "First drag", gesture.mergeKey); + } + gesture.Commit(); + } + break; + case 3: + Require(interactions.history.Undo().succeeded && component!.number == 0, "One drag did not undo as one entry."); + break; + case 4: + Require(interactions.history.Redo().succeeded && component!.number == 3, "Drag Redo failed."); + using (var gesture = new EditorEditGesture(interactions.history, "Second drag")) + { + edits.ChangeProperty(component!, nameof(WorkflowState.number), () => component!.number = 7, + "Second drag", gesture.mergeKey); + gesture.Commit(); + } + break; + case 5: + Require(interactions.history.Undo().succeeded && component!.number == 3, "Separate drags were merged."); + break; + case 6: + Require(interactions.history.Undo().succeeded && component!.number == 0, "The first drag was lost."); + Require(interactions.history.Redo().succeeded && interactions.history.Redo().succeeded, + "The two independent drags could not be redone."); + break; + case 7: + using (var gesture = new EditorEditGesture(interactions.history, "Cancelled drag")) + { + edits.ChangeProperty(component!, nameof(WorkflowState.number), () => component!.number = 99, + "Cancelled drag", gesture.mergeKey); + Require(gesture.Cancel().succeeded && component!.number == 7, "Cancellation did not restore the start."); + } + m_verified.Add("actual Inspector: continuous edits, independent Undo/Redo and gesture cancellation"); + break; + case 8: + edits.SetGameObjectActive(firstObject!, false); + edits.SetGameObjectActive(firstObject!, true); + Require(interactions.history.Undo().succeeded && !firstObject!.activeSelf, "Boolean edits were merged."); + Require(interactions.history.Undo().succeeded && firstObject!.activeSelf, "Boolean Undo lost the initial value."); + break; + case 9: + edits.ChangeProperty(component!, nameof(WorkflowState.reference), () => component!.reference = secondObject, + "Assign reference"); + interactions.SetSelection(component); + break; + case 10: + Require(component!.reference == secondObject, "The reference was not assigned."); + Require(interactions.history.Undo().succeeded && component.reference is null, "Reference clearing through Undo failed."); + Require(interactions.history.Redo().succeeded && component.reference == secondObject, "Reference Redo failed."); + m_verified.Add("actual Inspector: discrete booleans, assigned and unassigned references"); + break; + case 11: + interactions.SetSelection([firstObject!, secondObject!], firstObject); + edits.ChangeProperties([firstObject!.transform, secondObject!.transform], "localPosition", + () => firstObject.transform.localPosition = new Vector3(11, 2, 3), "Move X", componentName: "x"); + break; + case 12: + Require(firstObject!.transform.localPosition.x == 11 && secondObject!.transform.localPosition.x == 11, + "The common X edit was not applied."); + Require(firstObject.transform.localPosition.y == 2 && secondObject!.transform.localPosition.y == 5, + "A single-axis edit overwrote a mixed axis."); + Require(interactions.history.Undo().succeeded && firstObject.transform.localPosition.x == 0 && + secondObject!.transform.localPosition.x == 0, "Multi-selection Undo failed."); + m_verified.Add("actual Inspector: mixed transforms preserve unedited axes"); + break; + case 13: + interactions.SetSelection(firstObject); + edits.SetComponentIndex(FindComponent(scene, m_duplicateId)!, 1); + break; + case 14: + Require(firstObject!.GetComponentIndex(FindComponent(scene, m_duplicateId)!) == 1, + "Duplicate component ordering selected the wrong instance."); + Require(interactions.history.Undo().succeeded && + firstObject.GetComponentIndex(FindComponent(scene, m_duplicateId)!) == 2, "Component order Undo failed."); + Require(edits.RemoveComponent(FindComponent(scene, m_duplicateId)!), "Component removal failed."); + break; + case 15: + Require(FindComponent(scene, m_duplicateId) is null, "The removed component remained attached."); + Require(interactions.history.Undo().succeeded && FindComponent(scene, m_duplicateId) is WorkflowState, + "Component restoration lost identity or type."); + m_verified.Add("actual Inspector: duplicate instance order, removal and identity-preserving restoration"); + break; + case 16: + edits.DeleteGameObjects([firstObject!, secondObject!]); + break; + case 17: + Require(firstObject is null && secondObject is null, "Batch delete was incomplete."); + Require(interactions.history.Undo().succeeded, "Batch delete Undo failed."); + break; + case 18: + Require(firstObject is not null && secondObject is not null && component!.reference == secondObject, + "Batch recovery lost an object or cross-object reference."); + bool rejected = false; + try + { + edits.ChangeProperty(component!, nameof(WorkflowState.number), () => + { + component!.number = 99; + throw new InvalidOperationException("Expected workflow rejection."); + }, "Rejected mutation"); + } + catch (InvalidOperationException exception) when (exception.Message == "Expected workflow rejection.") + { + rejected = true; + } + Require(rejected && component!.number == 7, "A rejected mutation was not rolled back."); + m_verified.Add("actual Hierarchy and Inspector: atomic batch delete/recovery and failed mutation rollback"); + m_scenePath = workspace.Save(scene, string.Empty); + break; + case 19: + Require(!workspace.IsDirty(scene), "The saved document remained dirty."); + Require(edits.CloseScene(scene), "The saved scene could not close."); + m_step++; + return; + } + m_step++; + } + + private void AdvancePlayJourney(EditorContext context) + { + switch (m_step) + { + case 21: + Require(play.EnterPlayMode(), "The product refused Play."); + break; + case 22: + if (!play.isPlaying) + return; + if (++m_playFrames < 60) + return; + Require(workspace.scenes.SelectMany(static scene => scene.GetObjects()) + .SelectMany(static value => value.GetComponents()).OfType().Any(static value => value.ticks > 0), + "Play did not advance the ordinary script behavior."); + Require(play.ExitPlayMode(), "The product refused Stop."); + break; + case 23: + if (play.isPlaying) + return; + Require(workspace.canPersist, "Stop did not restore Edit persistence."); + Require(workspace.scenes.Single(value => value.identity.persistentId == m_sceneId) + .GetObjects().SelectMany(static value => value.GetComponents()).OfType() + .All(static value => value.ticks == 0), "Play mutations leaked into the Edit document."); + m_verified.Add("actual Game view: Play, frame advancement, Stop and restored Edit"); + string directory = Path.Combine(context.projectDirectory, "Library", "Acceptance"); + Directory.CreateDirectory(directory); + File.WriteAllLines(Path.Combine(directory, "workflow-journey.tsv"), m_verified); + m_finished = true; + break; + } + m_step++; + } + + private static GameComponent? FindComponent( + GameScene scene, + Guid persistentId + ) => scene.GetObjects().SelectMany(static value => value.GetComponents()) + .SingleOrDefault(value => value.identity.persistentId == persistentId); + + private static void Require( + bool condition, + string message + ) { + if (!condition) + throw new InvalidOperationException(message); + } +} diff --git a/tests/fixtures/editor-ui/WorkflowState.cs.txt b/tests/fixtures/editor-ui/WorkflowState.cs.txt new file mode 100644 index 000000000..9a33876d6 --- /dev/null +++ b/tests/fixtures/editor-ui/WorkflowState.cs.txt @@ -0,0 +1,15 @@ +using InnoEngine.Reflection; +using InnoEngine.Scene; +using InnoEngine.Serialization; + +[StableTypeId("e669c627-771a-4d3c-a09d-307dcd6b457e")] +[AllowMultipleComponent] +public sealed class WorkflowState : GameBehavior +{ + [SerializableProperty] public float number; + [SerializableProperty] public GameObject? reference; + [SerializableProperty] public int ticks; + + /// + protected override void Update() => ticks++; +} diff --git a/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.PropertyReferences.cs b/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.PropertyReferences.cs new file mode 100644 index 000000000..a5e0f5300 --- /dev/null +++ b/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.PropertyReferences.cs @@ -0,0 +1,23 @@ +using Inno.Assets; +using Xunit; + +namespace Inno.Scene.Tests; + +public sealed partial class EngineAssetIntegrationTests +{ + [Fact] + public void PropertyReferenceScopePreservesTheCanonicalAssetResolver() + { + WriteAsset("Text/property-reference.txt", "property source"); + Assert.True(m_assets.Import(AssetPath.Project("Text/property-reference.txt"))); + TextAsset asset = m_assets.Load(AssetPath.Project("Text/property-reference.txt")); + var scene = new GameScene("Asset property reference"); + EngineAssetReferenceComponent component = scene.CreateObject("Owner").AddComponent(); + component.asset = asset; + byte[] state = ScenePropertySerialization.CaptureProperties(component, m_fixture.serialization, m_assets); + component.asset = null; + _ = ScenePropertySerialization.RestoreProperties(component, state, m_fixture.serialization, m_assets); + Assert.Same(asset, component.asset); + DestroyScene(scene); + } +} diff --git a/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.cs b/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.cs index 8f8dcc6e6..d7226ca48 100644 --- a/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.cs +++ b/tests/scene/Inno.Scene.Tests/EngineAssetIntegrationTests.cs @@ -16,7 +16,7 @@ namespace Inno.Scene.Tests; [Collection(SceneTestsCollection.NAME)] -public sealed class EngineAssetIntegrationTests : IDisposable +public sealed partial class EngineAssetIntegrationTests : IDisposable { private readonly string m_root = Path.Combine( Path.GetTempPath(), @@ -45,8 +45,7 @@ public EngineAssetIntegrationTests(SceneTestsFixture fixture) fixture.diagnostics, fixture.logs, options); - m_assetSerializationContext = SerializationContext.empty - .With(m_assets); + m_assetSerializationContext = AssetSerializationContext.Create(m_assets); } public void Dispose() diff --git a/tests/scene/Inno.Scene.Tests/SceneFacadeTests.cs b/tests/scene/Inno.Scene.Tests/SceneFacadeTests.cs index 0a73e8915..99b515c6c 100644 --- a/tests/scene/Inno.Scene.Tests/SceneFacadeTests.cs +++ b/tests/scene/Inno.Scene.Tests/SceneFacadeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Inno.Scene; @@ -12,6 +13,89 @@ namespace Inno.Scene.Tests; [Collection(SceneTestsCollection.NAME)] public sealed class SceneFacadeTests : IDisposable { + [Fact] + public void RootSiblingPositionsStayAuthoritativeAcrossHierarchyMutations() + { + var scene = new GameScene("Indexed Roots"); + var destination = new GameScene("Transfer Target"); + SceneManager.LoadSceneAdditive(scene); + SceneManager.LoadSceneAdditive(destination); + Transform destinationRoot = destination.CreateObject("Existing").transform; + var roots = new List(); + for (int index = 0; index < 64; index++) + roots.Add(scene.CreateObject("Root " + index).transform); + var random = new Random(27102026); + for (int iteration = 0; iteration < 200; iteration++) + { + ulong before = scene.hierarchyRevision; + int selectedIndex = random.Next(roots.Count); + Transform selected = roots[selectedIndex]; + switch (iteration % 5) + { + case 0: + int target = random.Next(roots.Count); + selected.SetSiblingIndex(target); + roots.RemoveAt(selectedIndex); + roots.Insert(target, selected); + break; + case 1: + Transform parent = roots[(selectedIndex + 1) % roots.Count]; + selected.SetParent(parent); + roots.RemoveAt(selectedIndex); + Assert.Equal(0, selected.siblingIndex); + Verify(); + selected.SetParent(null); + roots.Add(selected); + break; + case 2: + SceneManager.MoveGameObjectToScene(selected.gameObject, destination); + roots.RemoveAt(selectedIndex); + Assert.Equal(0, destinationRoot.siblingIndex); + Assert.Equal(1, selected.siblingIndex); + Verify(); + SceneManager.MoveGameObjectToScene(selected.gameObject, scene); + roots.Add(selected); + break; + case 3: + Assert.True(scene.DestroyObject(selected.gameObject)); + roots.RemoveAt(selectedIndex); + roots.Add(scene.CreateObject("Replacement " + iteration).transform); + break; + default: + Assert.Throws(() => selected.SetParent(selected)); + Assert.Throws(() => selected.SetParent(destinationRoot)); + Assert.Equal(before, scene.hierarchyRevision); + break; + } + Verify(); + } + + void Verify() + { + for (int index = 0; index < roots.Count; index++) + Assert.Equal(index, roots[index].siblingIndex); + } + } + + [Fact] + public void ChildSiblingReorderInvalidatesTheSameSceneHierarchyView() + { + var scene = new GameScene("Child Order"); + Transform parent = scene.CreateObject("Parent").transform; + Transform first = scene.CreateObject("First").transform; + Transform second = scene.CreateObject("Second").transform; + first.SetParent(parent); + second.SetParent(parent); + ulong before = scene.hierarchyRevision; + second.SetSiblingIndex(0); + Assert.True(scene.hierarchyRevision > before); + Assert.Equal(0, second.siblingIndex); + Assert.Equal(1, first.siblingIndex); + before = scene.hierarchyRevision; + second.SetSiblingIndex(0); + Assert.Equal(before, scene.hierarchyRevision); + } + private readonly IDisposable m_sceneScope; public SceneFacadeTests(SceneTestsFixture fixture) diff --git a/tests/scene/Inno.Scene.Tests/SceneSerializationTests.CaptureKeys.cs b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.CaptureKeys.cs new file mode 100644 index 000000000..fdaafdaa9 --- /dev/null +++ b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.CaptureKeys.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using Inno.Core.Mathematics; +using Inno.Core.Serialization; +using Inno.Scene.Layers; +using Xunit; + +namespace Inno.Scene.Tests; + +public sealed partial class SceneSerializationTests +{ + [Fact] + public void StructuralCaptureDoesNotRetainRemovedSystemsWhileTheSceneRemainsAlive() + { + var scene = new GameScene("System capture lifetime"); + scene.CreateObject("Root"); + SceneManager.LoadScene(scene); + WeakReference removed = CaptureAndRemoveSystem(scene); + SceneManager.Update(0.016f); + SceneManager.LateUpdate(0.016f); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Assert.False(removed.IsAlive); + + var replacement = scene.AddSystem(); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(scene, m_serializationContext); + Assert.Same(replacement, Assert.Single(scene.GetSystems())); + Assert.Equal(m_serialization.Serialize(scene, m_serializationContext), snapshot.ToArray()); + GC.KeepAlive(scene); + } + + [Fact] + public void CompleteSceneCapturesObserveAllOwnedMetadataTransformAndStructureWrites() + { + var scene = new GameScene("Capture coverage"); + SceneManager.LoadScene(scene); + GameObject first = scene.CreateObject("First"); + GameObject second = scene.CreateObject("Second"); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(scene, m_serializationContext); + Action[] mutations = + [ + () => scene.name = "Changed scene", + () => first.name = "Changed object", + () => first.tag = "Player", + () => first.layer = new GameLayer(4), + () => first.SetActive(false), + () => first.transform.localPosition = new Vector3(2, 3, 4), + () => first.transform.localRotation = new Quaternion(0.2f, 0.1f, 0, 0.9f), + () => first.transform.localScale = new Vector3(2, 3, 4), + () => first.transform.worldPosition = new Vector3(5, 6, 7), + () => first.transform.worldRotation = new Quaternion(0, 0.3f, 0.1f, 0.8f), + () => first.transform.worldScale = new Vector3(3, 4, 5), + () => second.transform.SetParent(first.transform), + () => second.transform.SetWorldTransform(new Vector3(1, 2, 3), Quaternion.identity, Vector3.ONE), + () => second.transform.SetParent(null), + () => second.transform.SetSiblingIndex(0), + () => first.AddComponent(), + () => first.AddComponent(), + () => first.SetComponentIndex(first.GetComponent(), 1), + () => first.RemoveComponent(first.GetComponent()), + () => scene.AddSystem(), + () => scene.AddSystem(), + () => scene.GetSystems().OfType().Single().enabled = false, + () => scene.SetSystemIndex(scene.GetSystems().OfType().Single(), 0), + () => scene.RemoveSystem(scene.GetSystems().OfType().Single()), + () => scene.CreateObject("Added object"), + () => scene.DestroyObject(second) + ]; + foreach (Action mutate in mutations) + { + byte[] before = snapshot.ToArray(); + mutate(); + snapshot = m_serialization.CaptureSnapshot(scene, m_serializationContext, snapshot); + Assert.NotEqual(before, snapshot.ToArray()); + Assert.Equal(m_serialization.Serialize(scene, m_serializationContext), snapshot.ToArray()); + Assert.Same(snapshot, m_serialization.CaptureSnapshot(scene, m_serializationContext, snapshot)); + } + } + + [Fact] + public void PropertyAndCompleteSceneRestoreInvalidatePreviousCaptureInputs() + { + var scene = new GameScene("Restore capture"); + SceneManager.LoadScene(scene); + GameObject value = scene.CreateObject("Original"); + byte[] properties = m_serialization.CapturePropertiesData(value.transform, m_serializationContext); + byte[] source = m_serialization.Serialize(scene, m_serializationContext); + value.transform.localPosition = new Vector3(3, 4, 5); + SerializationSnapshot changed = m_serialization.CaptureSnapshot(scene, m_serializationContext); + m_serialization.RestorePropertiesData(value.transform, properties, context: m_serializationContext); + SerializationSnapshot restored = m_serialization.CaptureSnapshot(scene, m_serializationContext, changed); + Assert.Equal(source, restored.ToArray()); + Assert.NotEqual(changed.ToArray(), restored.ToArray()); + value.name = "Changed"; + changed = m_serialization.CaptureSnapshot(scene, m_serializationContext, restored); + m_serialization.Restore(scene, source, m_serializationContext); + restored = m_serialization.CaptureSnapshot(scene, m_serializationContext, changed); + Assert.Equal(source, restored.ToArray()); + Assert.Equal(m_serialization.Serialize(scene, m_serializationContext), restored.ToArray()); + Assert.Same(restored, m_serialization.CaptureSnapshot(scene, m_serializationContext, restored)); + } + + [Fact] + public void UntrackedPluginPropertiesAndObjectReferencesRemainAuthoritativelyCaptured() + { + var scene = new GameScene("Untracked state"); + SceneManager.LoadScene(scene); + GameObject first = scene.CreateObject("First"); + GameObject second = scene.CreateObject("Second"); + ReferenceComponent component = first.AddComponent(); + SerializationSnapshot before = m_serialization.CaptureSnapshot(scene, m_serializationContext); + component.value = 12; + component.targetObject = second; + SerializationSnapshot changed = m_serialization.CaptureSnapshot(scene, m_serializationContext, before); + Assert.NotEqual(before.ToArray(), changed.ToArray()); + Assert.Equal(m_serialization.Serialize(scene, m_serializationContext), changed.ToArray()); + component.value = 0; + component.targetObject = null; + SerializationSnapshot restored = m_serialization.CaptureSnapshot(scene, m_serializationContext, changed); + Assert.Equal(before.ToArray(), restored.ToArray()); + } + [MethodImpl(MethodImplOptions.NoInlining)] + private WeakReference CaptureAndRemoveSystem(GameScene scene) + { + var system = scene.AddSystem(); + SerializationSnapshot snapshot = m_serialization.CaptureSnapshot(scene, m_serializationContext); + Assert.Equal(m_serialization.Serialize(scene, m_serializationContext), snapshot.ToArray()); + Assert.True(scene.RemoveSystem(system)); + return new WeakReference(system); + } +} diff --git a/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyAliases.cs b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyAliases.cs new file mode 100644 index 000000000..7cf0551e6 --- /dev/null +++ b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyAliases.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; +using Inno.Assets; +using Inno.Core.Serialization; +using Xunit; + +namespace Inno.Scene.Tests; + +public sealed partial class SceneSerializationTests +{ + [Fact] + public void ElementPropertyAliasesResolveThroughTheCurrentSceneIdentityOwner() + { + var scene = new GameScene("Property aliases"); + PropertyReferenceProbe probe = scene.CreateObject("Owner").AddComponent(); + GameObject original = scene.CreateObject("Original"); + GameObject replacement = scene.CreateObject("Replacement"); + probe.targetObject = original; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + byte[] source = SceneElementSerialization.CaptureState(probe, m_serialization, assets); + // The fixture uses the ordinary serializer and verifies its complete current envelope before mutation. + PropertyElementEnvelope envelope = m_serialization.Deserialize(source, m_serializationContext); + Assert.Equal(m_types.GetTypeRef(typeof(PropertyReferenceProbe)).stableId, envelope.typeId); + probe.targetObject = null; + SceneElementSerialization.RestoreState(probe, m_serialization.Serialize(envelope, m_serializationContext), m_serialization, assets); + Assert.Same(original, probe.targetObject); + envelope.aliasIds = [original.identity.persistentId]; + envelope.aliasTargets = [replacement.identity.persistentId]; + scene.DestroyObject(original); + probe.targetObject = null; + SceneElementSerialization.RestoreState(probe, m_serialization.Serialize(envelope, m_serializationContext), m_serialization, assets); + Assert.Same(replacement, probe.targetObject); + Assert.NotSame(original, probe.targetObject); + } + + [Fact] + public void ElementPropertyAliasesRejectCollisionsMissingTargetsAndMalformedMetadata() + { + var scene = new GameScene("Invalid property aliases"); + PropertyReferenceProbe probe = scene.CreateObject("Owner").AddComponent(); + GameObject original = scene.CreateObject("Original"); + GameObject replacement = scene.CreateObject("Replacement"); + probe.targetObject = original; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + PropertyElementEnvelope envelope = m_serialization.Deserialize( + SceneElementSerialization.CaptureState(probe, m_serialization, assets), m_serializationContext); + envelope.aliasIds = [original.identity.persistentId]; + envelope.aliasTargets = [replacement.identity.persistentId]; + probe.targetObject = null; + Exception collision = Assert.ThrowsAny(Restore); + Assert.Contains("Duplicate scene graph local identity", collision.ToString()); + Assert.Null(probe.targetObject); + envelope.aliasIds = [Guid.NewGuid()]; + envelope.aliasTargets = [Guid.NewGuid()]; + Exception missing = Assert.ThrowsAny(Restore); + Assert.Contains("targets missing object", missing.ToString()); + Assert.Null(probe.targetObject); + envelope.aliasIds = [Guid.Empty]; + envelope.aliasTargets = [replacement.identity.persistentId]; + Exception malformed = Assert.ThrowsAny(Restore); + Assert.Contains("invalid type, dependency or alias metadata", malformed.ToString()); + Assert.Null(probe.targetObject); + + void Restore() + => SceneElementSerialization.RestoreState(probe, m_serialization.Serialize(envelope, m_serializationContext), m_serialization, assets); + } + + [Fact] + public void ElementPropertyRestoreRejectsReferencesOfTheWrongCurrentComponentType() + { + var scene = new GameScene("Wrong property reference type"); + GameObject owner = scene.CreateObject("Owner"); + PropertyReferenceProbe probe = owner.AddComponent(); + ReferenceComponent target = owner.AddComponent(); + probe.targetComponent = probe; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + PropertyElementEnvelope envelope = m_serialization.Deserialize( + SceneElementSerialization.CaptureState(target, m_serialization, assets), m_serializationContext); + envelope.properties = ScenePropertySerialization.CaptureProperty(probe, nameof(PropertyReferenceProbe.targetComponent), m_serialization, assets); + Exception failure = Assert.ThrowsAny(() => SceneElementSerialization.RestoreState( + target, m_serialization.Serialize(envelope, m_serializationContext), m_serialization, assets)); + Assert.Contains("targetComponent", failure.ToString()); + Assert.Null(target.targetComponent); + } + + [Fact] + public void PropertyRestoreRejectsAnInvalidReferenceKindBeforeAssigningTheTarget() + { + var scene = new GameScene("Invalid reference kind"); + GameObject owner = scene.CreateObject("Owner"); + PropertyReferenceProbe probe = owner.AddComponent(); + probe.targetComponent = owner.transform; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + SerializationPropertySnapshot property = ScenePropertySerialization.CapturePropertySnapshots(probe, m_serialization, assets) + .Single(value => value.name == nameof(PropertyReferenceProbe.targetComponent)); + byte[] original = property.data.ToArray(); + PropertyReferenceTokenFixture token = m_serialization.Deserialize(original, m_serializationContext); + Assert.Equal(owner.transform.identity.persistentId, token.sourceId); + token.kind = int.MaxValue; + byte[] invalid = m_serialization.Serialize(token, m_serializationContext); + Assert.Equal(original.Length, invalid.Length); + byte[] payload = ScenePropertySerialization.CaptureProperty(probe, nameof(PropertyReferenceProbe.targetComponent), m_serialization, assets); + int offset = payload.AsSpan().IndexOf(original); + Assert.True(offset >= 0, "The public property document did not contain its independently encoded value."); + invalid.CopyTo(payload.AsSpan(offset, invalid.Length)); + probe.targetComponent = null; + Exception failure = Assert.ThrowsAny(() => ScenePropertySerialization.RestoreProperties(probe, payload, m_serialization, assets)); + Assert.Contains("not a GameComponent reference", failure.ToString()); + Assert.Null(probe.targetComponent); + } +} + +internal sealed class PropertyReferenceTokenFixture : ISerializable +{ + [SerializableProperty] + public int kind { get; set; } + + [SerializableProperty] + public Guid sourceId { get; set; } +} + +/// +/// Creates valid and deliberately invalid current element envelopes through the public serializer. +/// +internal sealed class PropertyElementEnvelope : ISerializable +{ + [SerializableProperty] + public Guid typeId { get; set; } + + [SerializableProperty] + public string typeName { get; set; } = string.Empty; + + [SerializableProperty] + public byte[] properties { get; set; } = []; + + [SerializableProperty] + public Guid[] dependencyIds { get; set; } = []; + + [SerializableProperty] + public Guid[] dependencyTypes { get; set; } = []; + + [SerializableProperty] + public string[] dependencyPaths { get; set; } = []; + + [SerializableProperty] + public Guid[] aliasIds { get; set; } = []; + + [SerializableProperty] + public Guid[] aliasTargets { get; set; } = []; +} diff --git a/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyReferences.cs b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyReferences.cs new file mode 100644 index 000000000..bf0f24e98 --- /dev/null +++ b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.PropertyReferences.cs @@ -0,0 +1,164 @@ +using System; +using System.Linq; +using Inno.Assets; +using Inno.Core.Identity; +using Inno.Core.Serialization; +using Inno.Extensibility.Types; +using Xunit; + +namespace Inno.Scene.Tests; + +public sealed partial class SceneSerializationTests +{ + [Fact] + public void ScalarPropertyPreparationDoesNotAllocateAnIndexOfUnrelatedSceneObjects() + { + var scene = new GameScene("Property allocation"); + GameObject owner = scene.CreateObject("Owner"); + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + long small = Measure(); + for (int index = 0; index < 5_000; index++) + _ = scene.CreateObject("Unrelated " + index); + long large = Measure(); + Assert.True(large <= small + 64 * 1024, + $"Ten scalar property captures allocated {small} bytes in a small Scene and {large} with 5,000 unrelated objects."); + + long Measure() + { + for (int index = 0; index < 10; index++) + _ = ScenePropertySerialization.CapturePropertySnapshots(owner.transform, m_serialization, assets); + long before = GC.GetAllocatedBytesForCurrentThread(); + for (int index = 0; index < 10; index++) + Assert.Equal(3, ScenePropertySerialization.CapturePropertySnapshots(owner.transform, m_serialization, assets).Count); + return GC.GetAllocatedBytesForCurrentThread() - before; + } + } + + [Fact] + public void PropertyReferencesRestoreCurrentObjectComponentAndSystemInstancesAfterReplacement() + { + var scene = new GameScene("Property references"); + SceneManager.LoadScene(scene); + GameObject owner = scene.CreateObject("Owner"); + GameObject target = scene.CreateObject("Target"); + ReferenceComponent component = target.AddComponent(); + StateSystem system = scene.AddSystem(); + PropertyReferenceProbe probe = owner.AddComponent(); + probe.targetObject = target; + probe.targetComponent = component; + probe.targetSystem = system; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + byte[] properties = ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets); + byte[] graph = m_serialization.Serialize(scene, m_serializationContext); + Assert.True(SceneManager.UnloadScene(scene)); + GameScene replacement = m_serialization.Deserialize(graph, m_serializationContext); + SceneManager.LoadScene(replacement); + PropertyReferenceProbe restored = replacement.GetObjects().Single(value => value.name == "Owner").GetComponents() + .OfType().Single(); + GameObject current = replacement.GetObjects().Single(value => value.name == "Target"); + restored.targetObject = null; + restored.targetComponent = null; + restored.targetSystem = null; + _ = ScenePropertySerialization.RestoreProperties(restored, properties, m_serialization, assets); + Assert.Same(current, restored.targetObject); + Assert.Same(current.GetComponents().OfType().Single(), restored.targetComponent); + Assert.Same(replacement.GetSystems().OfType().Single(), restored.targetSystem); + Assert.Equal(target.identity.persistentId, current.identity.persistentId); + Assert.NotSame(target, current); + Assert.True(target.isDestroyed); + } + + [Fact] + public void PropertyReferencesRejectForeignScenesAndIdentityDomains() + { + var scene = new GameScene("Owner"); + PropertyReferenceProbe probe = scene.CreateObject("Owner").AddComponent(); + var other = new GameScene("Other"); + probe.targetObject = other.CreateObject("Foreign scene"); + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + Assert.ThrowsAny(() => ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets)); + var identities = new IdentityAllocator(); + using var world = new SceneWorld(identities, m_types); + using (world.EnterScope()) + { + var foreign = new GameScene("Foreign domain"); + probe.targetObject = foreign.CreateObject("Foreign domain"); + } + Assert.ThrowsAny(() => ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets)); + probe.targetObject = null; + Assert.NotEmpty(ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets)); + } + + [Fact] + public void MissingPropertyTargetRejectsRestoreWithoutAssigningTheDestroyedObject() + { + var scene = new GameScene("Missing property target"); + PropertyReferenceProbe probe = scene.CreateObject("Owner").AddComponent(); + GameObject target = scene.CreateObject("Target"); + probe.targetObject = target; + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + byte[] property = ScenePropertySerialization.CaptureProperty(probe, nameof(PropertyReferenceProbe.targetObject), m_serialization, assets); + scene.DestroyObject(target); + probe.targetObject = null; + Assert.ThrowsAny(() => ScenePropertySerialization.RestoreProperties(probe, property, m_serialization, assets)); + Assert.Null(probe.targetObject); + probe.targetObject = target; + Assert.ThrowsAny(() => ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets)); + probe.targetObject = null; + } + + [Fact] + public void NestedPropertyReadsAndGetterFailuresRestoreTheOuterReferenceScope() + { + var scene = new GameScene("Outer"); + PropertyReferenceProbe probe = scene.CreateObject("Owner").AddComponent(); + probe.targetObject = scene.CreateObject("Outer target"); + var nestedScene = new GameScene("Nested"); + ReferenceComponent nested = nestedScene.CreateObject("Nested owner").AddComponent(); + nested.targetObject = nestedScene.CreateObject("Nested target"); + IAssetReferenceResolver assets = m_serializationContext.GetRequired(); + probe.beforeRead = () => + { + byte[] value = ScenePropertySerialization.CaptureProperty(nested, nameof(ReferenceComponent.targetObject), m_serialization, assets); + nested.targetObject = null; + _ = ScenePropertySerialization.RestoreProperties(nested, value, m_serialization, assets); + Assert.NotNull(nested.targetObject); + }; + byte[] properties = ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets); + GameObject original = Assert.IsType(probe.targetObject); + probe.targetObject = null; + _ = ScenePropertySerialization.RestoreProperties(probe, properties, m_serialization, assets); + Assert.Same(original, probe.targetObject); + probe.beforeRead = () => throw new InvalidOperationException("Expected property getter failure."); + Assert.ThrowsAny(() => ScenePropertySerialization.CaptureProperties(probe, m_serialization, assets)); + probe.beforeRead = null; + // A leaked property scope would treat this independent prefab root as an out-of-bound reference. + Assert.NotEmpty(m_serialization.Serialize(nestedScene.CreateObject("Independent prefab"), m_serializationContext)); + } +} + +[StableTypeId("486290eb-6cc4-4a96-918a-c8022f51ad85")] +internal sealed class PropertyReferenceProbe : GameComponent +{ + internal Action? beforeRead { get; set; } + + [SerializableProperty(order = -1)] + public int trigger + { + get + { + beforeRead?.Invoke(); + return 0; + } + set { } + } + + [SerializableProperty] + public GameObject? targetObject { get; set; } + + [SerializableProperty] + public GameComponent? targetComponent { get; set; } + + [SerializableProperty] + public GameSystem? targetSystem { get; set; } +} diff --git a/tests/scene/Inno.Scene.Tests/SceneSerializationTests.cs b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.cs index 29ef35c7a..180d8b25a 100644 --- a/tests/scene/Inno.Scene.Tests/SceneSerializationTests.cs +++ b/tests/scene/Inno.Scene.Tests/SceneSerializationTests.cs @@ -15,7 +15,7 @@ namespace Inno.Scene.Tests; [Collection(SceneTestsCollection.NAME)] -public sealed class SceneSerializationTests : IDisposable +public sealed partial class SceneSerializationTests : IDisposable { private readonly TypeCatalog m_types; private readonly SerializationRegistry m_serialization; diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/EditorStyleMetricsTests.cs b/tests/scripting/Inno.Editor.Scripting.Tests/EditorStyleMetricsTests.cs index 750aa00d4..a887cd543 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/EditorStyleMetricsTests.cs +++ b/tests/scripting/Inno.Editor.Scripting.Tests/EditorStyleMetricsTests.cs @@ -1,3 +1,11 @@ +using ImGuiWindowFlags = Inno.Editor.ImGui.ImGuiWindowFlags; +using ImGuiTableRowFlags = Inno.Editor.ImGui.ImGuiTableRowFlags; +using ImGuiStyleVar = Inno.Editor.ImGui.ImGuiStyleVar; +using ImGuiMouseButton = Inno.Editor.ImGui.ImGuiMouseButton; +using ImGuiCond = Inno.Editor.ImGui.ImGuiCond; +using ImGuiCol = Inno.Editor.ImGui.ImGuiCol; +using ImGuiChildFlags = Inno.Editor.ImGui.ImGuiChildFlags; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; @@ -5,7 +13,6 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; - using Inno.Editor.ImGui; using Inno.Native.ImGui; using EditorImGui = Inno.Editor.ImGui.ImGui; @@ -14,18 +21,33 @@ using TreeNodeOptions = Inno.Editor.ImGui.ImGuiWidget.TreeNodeOptions; using TreeNodeResult = Inno.Editor.ImGui.ImGuiWidget.TreeNodeResult; using NativeImGui = Inno.Native.ImGui.ImGui; - using Xunit; namespace Inno.Editor.Scripting.Tests; - public sealed unsafe class EditorStyleMetricsTests { [Fact] - public void ZoomScalesDimensionsAndFontWhilePreservingRatios() + public void BrowserLayoutCapturesTheSharedScaleWithoutRetainingMutableMetrics() { var metrics = new EditorStyleMetrics(); + var browser = new Inno.Editor.Assets.ImGui.AssetBrowserStyle(metrics); + Assert.Equal(32f, browser.gridMinimumCellSize); + metrics.SetZoom(1.5f); + Assert.Equal(32f, browser.gridMinimumCellSize); + var scaled = new Inno.Editor.Assets.ImGui.AssetBrowserStyle(metrics); + Assert.Equal(48f, scaled.gridMinimumCellSize); + Assert.Equal(browser.gridDefaultScale, scaled.gridDefaultScale); + Assert.Equal(browser.listNameSeparatorPosition, scaled.listNameSeparatorPosition); + Assert.Equal(32f, default(Inno.Editor.Assets.ImGui.AssetBrowserStyle).gridMinimumCellSize); + Assert.Equal(7.5f, metrics.toolbarSectionSpacing); + Assert.Equal(24f, metrics.paneMinimumVisibleWidth); + Assert.Throws(() => new Inno.Editor.Assets.ImGui.AssetBrowserStyle(null!)); + } + [Fact] + public void ZoomScalesDimensionsAndFontWhilePreservingRatios() + { + var metrics = new EditorStyleMetrics(); Assert.True(metrics.SetZoom(1.5f)); Assert.Equal(1.5f, metrics.zoom, 3); Assert.Equal(12f, metrics.windowPadding.X, 3); @@ -33,7 +55,7 @@ public void ZoomScalesDimensionsAndFontWhilePreservingRatios() Assert.Equal(3f, metrics.frameRounding, 3); Assert.Equal(1.8f, metrics.fontScale, 3); Assert.Equal(0.4f, metrics.propertyLabelRatio, 3); - Assert.Equal(3f, metrics.assetGridDefaultScale, 3); + Assert.Equal(3f, new Inno.Editor.Assets.ImGui.AssetBrowserStyle(metrics).gridDefaultScale, 3); Assert.Equal(new Vector2(7.5f, 4.5f), metrics.cellPadding); } @@ -41,7 +63,6 @@ public void ZoomScalesDimensionsAndFontWhilePreservingRatios() public void ZoomCommandsClampAndResetToTheSupportedRange() { var metrics = new EditorStyleMetrics(); - Assert.True(metrics.SetZoom(100f)); Assert.Equal(EditorStyleMetrics.C_MAX_ZOOM, metrics.zoom); Assert.False(metrics.ZoomIn()); @@ -59,28 +80,25 @@ public void WrappedTextRendersThroughTheSafeLiteralTextPath() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(260f, 180f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated("Wrapped Text Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText0.pointer, null, 0); float lineHeight = NativeImGui.GetTextLineHeight(); - EditorWidget.WrappedText( - "Literal %s text remains safe and wraps. This deliberately long compilation status " + - "must stay inside the modal content width instead of extending beyond its right edge."); + EditorUI.current.widgets.WrappedText("Literal %s text remains safe and wraps. This deliberately long compilation status " + "must stay inside the modal content width instead of extending beyond its right edge."); Assert.True(NativeImGui.GetItemRectSize().Y > lineHeight); Inno.Native.ImGui.ImDrawList* windowDrawList = NativeImGui.Raw.GetWindowDrawList(); Inno.Native.ImGui.ImDrawList* foregroundDrawList = NativeImGui.Raw.GetForegroundDrawList(NativeImGui.Raw.GetMainViewport()); int windowVertexCount = windowDrawList->VtxBuffer.Size; int foregroundVertexCount = foregroundDrawList->VtxBuffer.Size; - EditorWidget.DropTargetHighlight( - new Vector2(20f, 20f), - new Vector2(120f, 80f)); + EditorUI.current.widgets.DropTargetHighlight(new Vector2(20f, 20f), new Vector2(120f, 80f)); Assert.Equal(windowVertexCount, windowDrawList->VtxBuffer.Size); Assert.True(foregroundDrawList->VtxBuffer.Size > foregroundVertexCount); NativeImGui.End(); @@ -98,12 +116,13 @@ public void CenteredWrappedTextStaysInsidePaddingAndUsesMultipleLines() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(420f, 260f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText1 = Utf8Codec.EncodeNullTerminated("Centered Wrapped Text Test", allowsNull: false); @@ -114,13 +133,11 @@ public void CenteredWrappedTextStaysInsidePaddingAndUsesMultipleLines() ImDrawList* drawList = NativeImGui.Raw.GetWindowDrawList(); int vertexStart = drawList->VtxBuffer.Size; uint textColor = NativeImGui.GetColorU32((int)(ImGuiCol.Text)); - - EditorWidget.CenteredWrappedText( - "A deliberately long viewport diagnostic wraps into several centered lines " + - "without touching either horizontal edge of the presentation area.", + EditorUI.current.widgets.CenteredWrappedText( + "A deliberately long viewport diagnostic wraps into several centered lines " + "without touching either horizontal edge of the presentation area.", area, - padding); - + padding + ); Assert.Equal(area, NativeImGui.GetItemRectSize()); Vector2 minimum = new(float.MaxValue); Vector2 maximum = new(float.MinValue); @@ -129,7 +146,6 @@ public void CenteredWrappedTextStaysInsidePaddingAndUsesMultipleLines() ImDrawVert vertex = drawList->VtxBuffer.Data[i]; if (vertex.Col != textColor) continue; - minimum = Vector2.Min(minimum, Unsafe.BitCast(vertex.Pos)); maximum = Vector2.Max(maximum, Unsafe.BitCast(vertex.Pos)); } @@ -155,6 +171,8 @@ public void InlineRenameIsCenteredAndSelectsTheCompleteValueWhenFocused() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; @@ -162,18 +180,12 @@ public void InlineRenameIsCenteredAndSelectsTheCompleteValueWhenFocused() io->Fonts->RendererHasTextures = 1; string value = "Scene"; bool requestFocus = true; - NativeImGui.NewFrame(); using NativeStringLease nativeTestText2 = Utf8Codec.EncodeNullTerminated("Inline Rename Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText2.pointer, null, 0); Vector2 rowMin = NativeImGui.GetCursorScreenPos(); float rowHeight = NativeImGui.GetFrameHeight() + 8f; - _ = EditorWidget.InlineRename( - "scene", - ref value, - ref requestFocus, - rowHeight, - width: 180f); + _ = EditorUI.current.widgets.InlineRename("scene", ref value, ref requestFocus, rowHeight, width: 180f); Vector2 fieldMin = NativeImGui.GetItemRectMin(); Vector2 fieldMax = NativeImGui.GetItemRectMax(); Assert.True(fieldMin.Y >= rowMin.Y); @@ -184,36 +196,27 @@ public void InlineRenameIsCenteredAndSelectsTheCompleteValueWhenFocused() Assert.NotEqual(0u, NativeImGui.Raw.GetCurrentWindow()->ID); NativeImGui.End(); NativeImGui.Render(); - NativeImGui.NewFrame(); using NativeStringLease nativeTestText3 = Utf8Codec.EncodeNullTerminated("Inline Rename Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText3.pointer, null, 0); - _ = EditorWidget.InlineRename( - "scene", - ref value, - ref requestFocus, - rowHeight, - width: 180f); + _ = EditorUI.current.widgets.InlineRename("scene", ref value, ref requestFocus, rowHeight, width: 180f); Vector2 focusedFieldMin = NativeImGui.GetItemRectMin(); Vector2 focusedFieldMax = NativeImGui.GetItemRectMax(); uint inputId = NativeImGui.GetItemID(); ImGuiInputTextState* inputState = NativeImGui.Raw.GetInputTextState(inputId); - Assert.False(requestFocus); Assert.False((inputState == null)); Assert.Equal(0, NativeImGui.Raw.GetSelectionStart(inputState)); Assert.Equal(value.Length, NativeImGui.Raw.GetSelectionEnd(inputState)); uint navCursorColor = NativeImGui.GetColorU32((int)(ImGuiCol.NavCursor)); - AssertDrawListDoesNotContainColor( - NativeImGui.Raw.GetWindowDrawList(), - navCursorColor, - "The inline rename focus outline was submitted to the window draw list."); + AssertDrawListDoesNotContainColor(NativeImGui.Raw.GetWindowDrawList(), navCursorColor, "The inline rename focus outline was submitted to the window draw list."); AssertColorBoundsStayCloseToItem( NativeImGui.Raw.GetForegroundDrawList(NativeImGui.Raw.GetMainViewport()), navCursorColor, focusedFieldMin, focusedFieldMax, - 1f + EditorWidget.style.interactionOverlayThickness); + 1f + EditorUI.current.widgets.style.interactionOverlayThickness + ); NativeImGui.End(); NativeImGui.Render(); } @@ -229,6 +232,8 @@ public void InlineRenameReleasesFocusBeforeAContextMenuCanOpen() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; @@ -238,7 +243,6 @@ public void InlineRenameReleasesFocusBeforeAContextMenuCanOpen() bool requestFocus = true; InlineRenameResult result = InlineRenameResult.None; Vector2 center = default; - void Frame() { NativeImGui.NewFrame(); @@ -246,12 +250,7 @@ void Frame() NativeImGui.SetNextWindowSize(new Vector2(320f, 180f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText4 = Utf8Codec.EncodeNullTerminated("Inline Rename Context Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText4.pointer, null, 0); - result = EditorWidget.InlineRename( - "created_asset", - ref value, - ref requestFocus, - NativeImGui.GetFrameHeight(), - width: 220f); + result = EditorUI.current.widgets.InlineRename("created_asset", ref value, ref requestFocus, NativeImGui.GetFrameHeight(), width: 220f); center = (NativeImGui.GetItemRectMin() + NativeImGui.GetItemRectMax()) * 0.5f; NativeImGui.End(); NativeImGui.Render(); @@ -261,13 +260,11 @@ void Frame() Frame(); Assert.False(requestFocus); Assert.NotEqual(0u, NativeImGui.Raw.GetActiveID()); - NativeImGui.Raw.AddMousePosEvent(io, center.X, center.Y); NativeImGui.Raw.AddMouseButtonEvent(io, (int)ImGuiMouseButton.Right, (true ? (byte)1 : (byte)0)); Frame(); NativeImGui.Raw.AddMouseButtonEvent(io, (int)ImGuiMouseButton.Right, (false ? (byte)1 : (byte)0)); Frame(); - Assert.Equal(InlineRenameResult.FocusLost, result); Assert.Equal(0u, NativeImGui.Raw.GetActiveID()); } @@ -283,6 +280,8 @@ public void InlineRenameCentersWithinTheNativeTableRowBounds() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; @@ -290,35 +289,23 @@ public void InlineRenameCentersWithinTheNativeTableRowBounds() io->Fonts->RendererHasTextures = 1; string value = "Scene"; bool requestFocus = false; - NativeImGui.NewFrame(); using NativeStringLease nativeTestText5 = Utf8Codec.EncodeNullTerminated("Inline Rename Table Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText5.pointer, null, 0); NativeImGui.PushStyleVar((int)(ImGuiStyleVar.CellPadding), new Vector2(6f, 3f)); Assert.True(NativeImGui.BeginTable("##rename_table", 1, 0, default(Vector2), 0f)); - float rowHeight = NativeImGui.GetTextLineHeight() + - NativeImGui.Raw.GetStyle()->CellPadding.Y * 2f; + float rowHeight = NativeImGui.GetTextLineHeight() + NativeImGui.Raw.GetStyle()->CellPadding.Y * 2f; NativeImGui.TableNextRow((int)(ImGuiTableRowFlags.None), rowHeight); _ = NativeImGui.TableSetColumnIndex(0); ImGuiTable* table = NativeImGui.Raw.GetCurrentTable(); Vector2 cursor = NativeImGui.GetCursorScreenPos(); NativeImGui.SetCursorScreenPos(new Vector2(cursor.X, table->RowPosY1)); - - _ = EditorWidget.InlineRename( - "table_scene", - ref value, - ref requestFocus, - table->RowPosY2 - table->RowPosY1, - width: 180f); + _ = EditorUI.current.widgets.InlineRename("table_scene", ref value, ref requestFocus, table->RowPosY2 - table->RowPosY1, width: 180f); Vector2 fieldMin = NativeImGui.GetItemRectMin(); Vector2 fieldMax = NativeImGui.GetItemRectMax(); - Assert.True(fieldMin.Y >= table->RowPosY1); Assert.True(fieldMax.Y <= table->RowPosY2); - Assert.Equal( - (table->RowPosY1 + table->RowPosY2) * 0.5f, - (fieldMin.Y + fieldMax.Y) * 0.5f, - 3); + Assert.Equal((table->RowPosY1 + table->RowPosY2) * 0.5f, (fieldMin.Y + fieldMax.Y) * 0.5f, 3); NativeImGui.EndTable(); NativeImGui.PopStyleVar(); NativeImGui.End(); @@ -336,23 +323,30 @@ public void TreeGuideDepthRemainsBalancedAcrossSupportedZoomLevels() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; + foreach (float zoom in new[] + { + 0.75f, + 1f, + 1.25f, + 1.5f + } - foreach (float zoom in new[] { 0.75f, 1f, 1.25f, 1.5f }) + ) { - _ = EditorWidget.style.SetZoom(zoom); - EditorWidget.SetupStyle(); + _ = EditorUI.current.widgets.style.SetZoom(zoom); + EditorUI.current.widgets.SetupStyle(); DrawTreeFrame(); } } finally { - _ = EditorWidget.style.ResetZoom(); - EditorWidget.SetupStyle(); NativeImGui.Raw.DestroyContext(context); } } @@ -363,12 +357,13 @@ public void MenuSelectorPopupIsBoundedAndScrollsLongContent() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - for (int frame = 0; frame < 2; frame++) { NativeImGui.NewFrame(); @@ -378,7 +373,7 @@ public void MenuSelectorPopupIsBoundedAndScrollsLongContent() _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText6.pointer, null, 0); if (frame == 0) NativeImGui.OpenPopup("##menu_selector_popup_test"); - Assert.True(EditorWidget.BeginMenuSelector("test", "Untagged", 180f, 240f)); + Assert.True(EditorUI.current.widgets.BeginMenuSelector("test", "Untagged", 180f, 240f)); ImGuiWindowFlags flags = (ImGuiWindowFlags)NativeImGui.Raw.GetCurrentWindow()->Flags; Assert.True(flags.HasFlag(ImGuiWindowFlags.AlwaysAutoResize)); Assert.False(flags.HasFlag(ImGuiWindowFlags.NoScrollbar)); @@ -387,17 +382,10 @@ public void MenuSelectorPopupIsBoundedAndScrollsLongContent() Assert.True(NativeImGui.GetWindowSize().X >= 240f); string newTag = string.Empty; NativeImGui.SetNextItemWidth(160f); - _ = EditorImGui.InputTextWithHint( - "##new_tag", - "Add tag...", - ref newTag, - 128); + _ = EditorImGui.InputTextWithHint("##new_tag", "Add tag...", ref newTag, 128); float inputCenterY = (NativeImGui.GetItemRectMin().Y + NativeImGui.GetItemRectMax().Y) * 0.5f; NativeImGui.SameLine(0f, -1f); - _ = EditorWidget.ClickableText( - "add_tag", - "+", - new Vector2(EditorWidget.GetCompactIconSize().X, NativeImGui.GetFrameHeight())); + _ = EditorUI.current.widgets.ClickableText("add_tag", "+", new Vector2(EditorUI.current.widgets.GetCompactIconSize().X, NativeImGui.GetFrameHeight())); float actionCenterY = (NativeImGui.GetItemRectMin().Y + NativeImGui.GetItemRectMax().Y) * 0.5f; Assert.Equal(inputCenterY, actionCenterY, 3); for (int index = 0; index < 64; index++) @@ -407,7 +395,8 @@ public void MenuSelectorPopupIsBoundedAndScrollsLongContent() Assert.True(NativeImGui.GetWindowSize().Y <= io->DisplaySize.Y + 1f); Assert.True(NativeImGui.GetScrollMaxY() > 0f); } - EditorWidget.EndMenuSelector(); + + EditorUI.current.widgets.EndMenuSelector(); NativeImGui.End(); NativeImGui.Render(); } @@ -424,12 +413,13 @@ public void SmallMenuSelectorDoesNotReserveAScrollbar() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - for (int frame = 0; frame < 3; frame++) { NativeImGui.NewFrame(); @@ -439,7 +429,7 @@ public void SmallMenuSelectorDoesNotReserveAScrollbar() _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText7.pointer, null, 0); if (frame == 0) NativeImGui.OpenPopup("##menu_selector_popup_small"); - Assert.True(EditorWidget.BeginMenuSelector("small", "Current", 180f, 240f)); + Assert.True(EditorUI.current.widgets.BeginMenuSelector("small", "Current", 180f, 240f)); for (int index = 0; index < 3; index++) NativeImGui.Selectable($"Entry {index}"); if (frame == 2) @@ -447,7 +437,8 @@ public void SmallMenuSelectorDoesNotReserveAScrollbar() Assert.False((NativeImGui.Raw.GetCurrentWindow()->ScrollbarY != 0)); Assert.Equal(0f, NativeImGui.GetScrollMaxY(), 3); } - EditorWidget.EndMenuSelector(); + + EditorUI.current.widgets.EndMenuSelector(); NativeImGui.End(); NativeImGui.Render(); } @@ -464,12 +455,13 @@ public void SmallContextMenuDoesNotReserveScrollbars() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - for (int frame = 0; frame < 3; frame++) { NativeImGui.NewFrame(); @@ -478,7 +470,7 @@ public void SmallContextMenuDoesNotReserveScrollbars() NativeImGui.Selectable("Target"); if (frame == 0) NativeImGui.OpenPopup("context"); - Assert.True(EditorWidget.BeginContextMenu("context")); + Assert.True(EditorUI.current.widgets.BeginContextMenu("context")); NativeImGui.MenuItem("Rename", string.Empty, false, true); NativeImGui.MenuItem("Delete", string.Empty, false, true); if (frame == 2) @@ -486,7 +478,8 @@ public void SmallContextMenuDoesNotReserveScrollbars() Assert.False((NativeImGui.Raw.GetCurrentWindow()->ScrollbarX != 0)); Assert.False((NativeImGui.Raw.GetCurrentWindow()->ScrollbarY != 0)); } - EditorWidget.EndContextMenu(); + + EditorUI.current.widgets.EndContextMenu(); NativeImGui.End(); NativeImGui.Render(); } @@ -503,12 +496,13 @@ public void BoundedComboPopupUsesTheSharedScrollingContract() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - var popupWidths = new List(); int openFrames = 0; for (int frame = 0; frame < 5; frame++) @@ -523,14 +517,15 @@ public void BoundedComboPopupUsesTheSharedScrollingContract() { NativeImGui.OpenPopup("##menu_selector_popup_##asset"); } + NativeImGui.SetNextItemWidth(180f); - bool open = EditorWidget.BeginBoundedCombo("##asset", "project:Material"); + bool open = EditorUI.current.widgets.BeginBoundedCombo("##asset", "project:Material"); if (open) { openFrames++; Assert.Equal(parentViewportId, NativeImGui.Raw.GetWindowViewport()->ID); string search = string.Empty; - _ = EditorWidget.SearchInput("assets", "Search assets...", ref search); + _ = EditorUI.current.widgets.SearchInput("assets", "Search assets...", ref search); for (int index = 0; index < 64; index++) NativeImGui.Selectable($"project:Material{index:D2}"); popupWidths.Add(NativeImGui.GetWindowSize().X); @@ -547,11 +542,14 @@ public void BoundedComboPopupUsesTheSharedScrollingContract() Assert.True(popupMaximum.X <= popupViewport->WorkPos.X + popupViewport->WorkSize.X + 1f); Assert.True(popupMaximum.Y <= popupViewport->WorkPos.Y + popupViewport->WorkSize.Y + 1f); } - EditorWidget.EndBoundedCombo(); + + EditorUI.current.widgets.EndBoundedCombo(); } + NativeImGui.End(); NativeImGui.Render(); } + Assert.True(openFrames >= 2); Assert.All(popupWidths, width => Assert.InRange(width, 179f, 181f)); } @@ -567,12 +565,13 @@ public void TreeRegionScrollsHorizontallyOnlyForRealContentOverflow() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - DrawTreeOverflowFrame(assertRanges: false); DrawTreeOverflowFrame(assertRanges: true); DrawHorizontallyScrolledTreeFrame(0f, assertGeometry: false); @@ -591,12 +590,13 @@ public void TreeRegionReleasesViewportWidthAfterItShrinks() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - DrawResizableTreeFrame(420f, 0f, assertNoOverflow: false); DrawResizableTreeFrame(220f, 48f, assertNoOverflow: false); DrawResizableTreeFrame(220f, 48f, assertNoOverflow: true); @@ -613,13 +613,14 @@ public void TreeGuideSegmentsRemainVisibleAcrossCompactRows() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); DrawTreeFrame(); DrawTreeFrame(assertContinuousGuides: true); } @@ -635,30 +636,24 @@ public void ChildGuideRetainsAVisualGapBelowTheParentWithoutChangingCompactRowHe var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(480f, 320f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText10 = Utf8Codec.EncodeNullTerminated("Gapped Parent Guide Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText10.pointer, null, 0); float expectedHeight = NativeImGui.GetTextLineHeight(); - EditorWidget.SetNextTreeNodeOpen(true); - TreeNodeResult parent = EditorWidget.TreeNode( - "connected_parent", - static _ => NativeImGui.TextUnformatted("Parent"), - new TreeNodeOptions()); + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult parent = EditorUI.current.widgets.TreeNode("connected_parent", static _ => NativeImGui.TextUnformatted("Parent"), new TreeNodeOptions()); Assert.True(parent.isOpen); - TreeNodeResult child = EditorWidget.TreeNode( - "connected_child", - static _ => NativeImGui.TextUnformatted("Child"), - new TreeNodeOptions { isLeaf = true }); + TreeNodeResult child = EditorUI.current.widgets.TreeNode("connected_child", static _ => NativeImGui.TextUnformatted("Child"), new TreeNodeOptions { isLeaf = true }); NativeImGui.TreePop(); - Assert.Equal(expectedHeight, parent.max.Y - parent.min.Y, 3); Assert.Equal(expectedHeight, child.max.Y - child.min.Y, 3); AssertCurrentTreeGuideDoesNotTouchY((parent.min.Y + parent.max.Y) * 0.5f); @@ -677,32 +672,28 @@ public void TreeGuidesAreDrawnFromTheCurrentFrameWhileDragging() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(480f, 320f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText11 = Utf8Codec.EncodeNullTerminated("Dragging Tree Guide Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText11.pointer, null, 0); ImGuiNativeContext* currentContext = NativeImGui.Raw.GetCurrentContext(); currentContext->DragDropActive = 1; - EditorWidget.SetNextTreeNodeOpen(true); - TreeNodeResult root = EditorWidget.TreeNode( - "drag_root", - static _ => NativeImGui.TextUnformatted("Assets"), - new TreeNodeOptions()); + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult root = EditorUI.current.widgets.TreeNode("drag_root", static _ => NativeImGui.TextUnformatted("Assets"), new TreeNodeOptions()); if (root.isOpen) { - _ = EditorWidget.TreeNode( - "drag_child", - static _ => NativeImGui.TextUnformatted("Scene"), - new TreeNodeOptions { isLeaf = true }); + _ = EditorUI.current.widgets.TreeNode("drag_child", static _ => NativeImGui.TextUnformatted("Scene"), new TreeNodeOptions { isLeaf = true }); NativeImGui.TreePop(); } + AssertCurrentDrawListContainsTreeGuideColor(); currentContext->DragDropActive = 0; NativeImGui.End(); @@ -720,19 +711,16 @@ public void TreeRowBackgroundsUseCurrentGeometryWhileTheWindowChanges() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - - DrawCurrentTreeBackgroundFrame( - new Vector2(20f, 20f), - new Vector2(480f, 320f)); - DrawCurrentTreeBackgroundFrame( - new Vector2(80f, 60f), - new Vector2(300f, 220f)); + EditorUI.current.widgets.SetupStyle(); + DrawCurrentTreeBackgroundFrame(new Vector2(20f, 20f), new Vector2(480f, 320f)); + DrawCurrentTreeBackgroundFrame(new Vector2(80f, 60f), new Vector2(300f, 220f)); } finally { @@ -746,22 +734,20 @@ public void TreeRowsRetainTheirCompactContentHeight() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(320f, 240f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText12 = Utf8Codec.EncodeNullTerminated("Compact Tree Row Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText12.pointer, null, 0); float expectedHeight = NativeImGui.GetTextLineHeight(); - TreeNodeResult row = EditorWidget.TreeNode( - "compact_row", - static _ => NativeImGui.TextUnformatted("GameObject"), - new TreeNodeOptions { isLeaf = true }); + TreeNodeResult row = EditorUI.current.widgets.TreeNode("compact_row", static _ => NativeImGui.TextUnformatted("GameObject"), new TreeNodeOptions { isLeaf = true }); Assert.Equal(expectedHeight, row.max.Y - row.min.Y, 3); NativeImGui.End(); NativeImGui.Render(); @@ -778,17 +764,16 @@ public void DoubleClickingTreeContentTogglesNonLeafExpansion() var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); TreeNodeResult initial = DrawDoubleClickTreeFrame(); - Vector2 contentCenter = new( - initial.contentMin.X + 20f, - (initial.min.Y + initial.max.Y) * 0.5f); + Vector2 contentCenter = new(initial.contentMin.X + 20f, (initial.min.Y + initial.max.Y) * 0.5f); NativeImGui.Raw.AddMousePosEvent(io, contentCenter.X, contentCenter.Y); NativeImGui.Raw.AddMouseButtonEvent(io, 0, (true ? (byte)1 : (byte)0)); _ = DrawDoubleClickTreeFrame(); @@ -798,7 +783,6 @@ public void DoubleClickingTreeContentTogglesNonLeafExpansion() TreeNodeResult doubleClicked = DrawDoubleClickTreeFrame(); NativeImGui.Raw.AddMouseButtonEvent(io, 0, (false ? (byte)1 : (byte)0)); TreeNodeResult expanded = DrawDoubleClickTreeFrame(); - Assert.True(doubleClicked.isDoubleClicked); Assert.True(expanded.isOpen); } @@ -814,13 +798,14 @@ public void InspectorContentPreservesStandardPaddingWithoutArtificialHorizontalO var context = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.SetupStyle(); - + EditorUI.current.widgets.SetupStyle(); DrawInspectorCardFrame(assertHorizontalRange: false); DrawInspectorCardFrame(assertHorizontalRange: true); } @@ -836,33 +821,22 @@ private static void DrawTreeFrame(bool assertContinuousGuides = false) NativeImGui.SetNextWindowSize(new Vector2(480f, 320f), (int)(Inno.Native.ImGui.ImGuiCond.Always)); using NativeStringLease nativeTestText13 = Utf8Codec.EncodeNullTerminated("Tree Zoom Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText13.pointer, null, 0); - EditorWidget.SetNextTreeNodeOpen(true); - TreeNodeResult root = EditorWidget.TreeNode( - "root", - static _ => NativeImGui.TextUnformatted("Assets"), - new TreeNodeOptions()); + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult root = EditorUI.current.widgets.TreeNode("root", static _ => NativeImGui.TextUnformatted("Assets"), new TreeNodeOptions()); if (root.isOpen) { - EditorWidget.SetNextTreeNodeOpen(true); - TreeNodeResult folder = EditorWidget.TreeNode( - "folder", - static _ => NativeImGui.TextUnformatted("Scene"), - new TreeNodeOptions()); + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult folder = EditorUI.current.widgets.TreeNode("folder", static _ => NativeImGui.TextUnformatted("Scene"), new TreeNodeOptions()); if (folder.isOpen) { - _ = EditorWidget.TreeNode( - "nested-file", - static _ => NativeImGui.TextUnformatted("TestScene1.iscene"), - new TreeNodeOptions { isLeaf = true }); + _ = EditorUI.current.widgets.TreeNode("nested-file", static _ => NativeImGui.TextUnformatted("TestScene1.iscene"), new TreeNodeOptions { isLeaf = true }); NativeImGui.TreePop(); } - _ = EditorWidget.TreeNode( - "sibling-folder", - static _ => NativeImGui.TextUnformatted("Settings"), - new TreeNodeOptions { isLeaf = true }); + _ = EditorUI.current.widgets.TreeNode("sibling-folder", static _ => NativeImGui.TextUnformatted("Settings"), new TreeNodeOptions { isLeaf = true }); NativeImGui.TreePop(); } + if (assertContinuousGuides) AssertCurrentDrawListContainsTreeGuideColor(); NativeImGui.End(); @@ -876,18 +850,13 @@ private static TreeNodeResult DrawDoubleClickTreeFrame() NativeImGui.SetNextWindowSize(new Vector2(320f, 240f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText14 = Utf8Codec.EncodeNullTerminated("Double Click Tree Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText14.pointer, null, 0); - TreeNodeResult result = EditorWidget.TreeNode( - "double_click_parent", - static _ => NativeImGui.TextUnformatted("Parent"), - new TreeNodeOptions()); + TreeNodeResult result = EditorUI.current.widgets.TreeNode("double_click_parent", static _ => NativeImGui.TextUnformatted("Parent"), new TreeNodeOptions()); if (result.isOpen) { - _ = EditorWidget.TreeNode( - "double_click_child", - static _ => NativeImGui.TextUnformatted("Child"), - new TreeNodeOptions { isLeaf = true }); + _ = EditorUI.current.widgets.TreeNode("double_click_child", static _ => NativeImGui.TextUnformatted("Child"), new TreeNodeOptions { isLeaf = true }); NativeImGui.TreePop(); } + NativeImGui.End(); NativeImGui.Render(); return result; @@ -899,16 +868,9 @@ private static void DrawTreeOverflowFrame(bool assertRanges) NativeImGui.SetNextWindowSize(new Vector2(560f, 320f), (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText15 = Utf8Codec.EncodeNullTerminated("Tree Overflow Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText15.pointer, null, 0); - if (NativeImGui.BeginChild( - "##short_tree", - new Vector2(220f, 100f), - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.HorizontalScrollbar))) + if (NativeImGui.BeginChild("##short_tree", new Vector2(220f, 100f), (int)(ImGuiChildFlags.None), (int)(ImGuiWindowFlags.HorizontalScrollbar))) { - _ = EditorWidget.TreeNode( - "short", - static _ => NativeImGui.TextUnformatted("Object"), - new TreeNodeOptions { isLeaf = true }); + _ = EditorUI.current.widgets.TreeNode("short", static _ => NativeImGui.TextUnformatted("Object"), new TreeNodeOptions { isLeaf = true }); if (assertRanges) { Assert.Equal(0f, NativeImGui.GetScrollMaxX(), 3); @@ -916,19 +878,15 @@ private static void DrawTreeOverflowFrame(bool assertRanges) Assert.True((NativeImGui.Raw.GetCurrentWindow()->Flags & (int)ImGuiWindowFlags.HorizontalScrollbar) != 0); } } - NativeImGui.EndChild(); - if (NativeImGui.BeginChild( - "##long_tree", - new Vector2(220f, 100f), - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.HorizontalScrollbar))) + NativeImGui.EndChild(); + if (NativeImGui.BeginChild("##long_tree", new Vector2(220f, 100f), (int)(ImGuiChildFlags.None), (int)(ImGuiWindowFlags.HorizontalScrollbar))) { - _ = EditorWidget.TreeNode( + _ = EditorUI.current.widgets.TreeNode( "long", - static _ => NativeImGui.TextUnformatted( - "Object_With_A_Name_That_Is_Intentionally_Wider_Than_The_Tree_Viewport"), - new TreeNodeOptions { isLeaf = true }); + static _ => NativeImGui.TextUnformatted("Object_With_A_Name_That_Is_Intentionally_Wider_Than_The_Tree_Viewport"), + new TreeNodeOptions { isLeaf = true } + ); if (assertRanges) { Assert.True(NativeImGui.GetScrollMaxX() > 0f); @@ -936,6 +894,7 @@ private static void DrawTreeOverflowFrame(bool assertRanges) Assert.True((NativeImGui.Raw.GetCurrentWindow()->Flags & (int)ImGuiWindowFlags.HorizontalScrollbar) != 0); } } + NativeImGui.EndChild(); NativeImGui.End(); NativeImGui.Render(); @@ -950,38 +909,24 @@ bool assertGeometry using NativeStringLease nativeTestText16 = Utf8Codec.EncodeNullTerminated("Scrolled Tree Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText16.pointer, null, 0); NativeImGui.SetNextWindowScroll(new Vector2(requestedScroll, 0f)); - if (NativeImGui.BeginChild( - "##scrolled_tree", - new Vector2(220f, 120f), - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.HorizontalScrollbar))) + if (NativeImGui.BeginChild("##scrolled_tree", new Vector2(220f, 120f), (int)(ImGuiChildFlags.None), (int)(ImGuiWindowFlags.HorizontalScrollbar))) { Vector2 contentCursor = default; - TreeNodeResult row = EditorWidget.TreeNode( - "scrolled", - _ => - { - contentCursor = NativeImGui.GetCursorScreenPos(); - NativeImGui.TextUnformatted( - "Scene_With_A_Name_That_Is_Intentionally_Wider_Than_The_Tree_Viewport"); - }, - new TreeNodeOptions - { - isLeaf = true, - showBackground = true, - backgroundColor = EditorPalette.hierarchySceneRow - }); + TreeNodeResult row = EditorUI.current.widgets.TreeNode("scrolled", _ => + { + contentCursor = NativeImGui.GetCursorScreenPos(); + NativeImGui.TextUnformatted("Scene_With_A_Name_That_Is_Intentionally_Wider_Than_The_Tree_Viewport"); + }, new TreeNodeOptions { isLeaf = true, showBackground = true, backgroundColor = EditorPalette.title }); if (assertGeometry) { ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); Assert.True(NativeImGui.GetScrollX() > 0f); Assert.True((window->ScrollbarX != 0)); Assert.Equal(row.contentMin.X, contentCursor.X, 3); - AssertCurrentTreeBackgroundSpansViewport( - window, - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.hierarchySceneRow)); + AssertCurrentTreeBackgroundSpansViewport(window, NativeImGui.ColorConvertFloat4ToU32(EditorPalette.title)); } } + NativeImGui.EndChild(); NativeImGui.End(); NativeImGui.Render(); @@ -997,32 +942,17 @@ bool assertNoOverflow using NativeStringLease nativeTestText17 = Utf8Codec.EncodeNullTerminated("Resizable Tree Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText17.pointer, null, 0); NativeImGui.SetNextWindowScroll(new Vector2(requestedScroll, 0f)); - if (NativeImGui.BeginChild( - "##resizable_tree", - new Vector2(width, 120f), - (int)(ImGuiChildFlags.None), - (int)(ImGuiWindowFlags.HorizontalScrollbar))) + if (NativeImGui.BeginChild("##resizable_tree", new Vector2(width, 120f), (int)(ImGuiChildFlags.None), (int)(ImGuiWindowFlags.HorizontalScrollbar))) { - _ = EditorWidget.TreeNode( - "resizable_row", - static _ => NativeImGui.TextUnformatted("GameObject"), - new TreeNodeOptions - { - isLeaf = true, - drawViewportOverlay = static () => - { - ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); - Vector2 overlaySize = new( - NativeImGui.GetFontSize(), - NativeImGui.GetTextLineHeight()); - NativeImGui.SameLine(0f, -1f); - Vector2 cursor = NativeImGui.GetCursorScreenPos(); - NativeImGui.SetCursorScreenPos(new Vector2( - MathF.Max(cursor.X, window->WorkRect.Max.X - overlaySize.X), - cursor.Y)); - NativeImGui.Dummy(overlaySize); - } - }); + _ = EditorUI.current.widgets.TreeNode("resizable_row", static _ => NativeImGui.TextUnformatted("GameObject"), new TreeNodeOptions { isLeaf = true, drawViewportOverlay = static () => + { + ImGuiWindow* window = NativeImGui.Raw.GetCurrentWindow(); + Vector2 overlaySize = new(NativeImGui.GetFontSize(), NativeImGui.GetTextLineHeight()); + NativeImGui.SameLine(0f, -1f); + Vector2 cursor = NativeImGui.GetCursorScreenPos(); + NativeImGui.SetCursorScreenPos(new Vector2(MathF.Max(cursor.X, window->WorkRect.Max.X - overlaySize.X), cursor.Y)); + NativeImGui.Dummy(overlaySize); + } }); if (assertNoOverflow) { Assert.Equal(0f, NativeImGui.GetScrollX(), 3); @@ -1030,6 +960,7 @@ bool assertNoOverflow Assert.False((NativeImGui.Raw.GetCurrentWindow()->ScrollbarX != 0)); } } + NativeImGui.EndChild(); NativeImGui.End(); NativeImGui.Render(); @@ -1038,9 +969,7 @@ bool assertNoOverflow private static void AssertCurrentDrawListContainsTreeGuideColor() { uint guideColor = NativeImGui.ColorConvertFloat4ToU32(EditorPalette.treeGuide); - AssertCurrentDrawListContainsColor( - guideColor, - "The current drag frame did not submit any tree-guide vertices."); + AssertCurrentDrawListContainsColor(guideColor, "The current drag frame did not submit any tree-guide vertices."); } private static void AssertCurrentTreeGuideDoesNotTouchY(float excludedY) @@ -1052,7 +981,8 @@ private static void AssertCurrentTreeGuideDoesNotTouchY(float excludedY) ImDrawVert vertex = drawList->VtxBuffer.Data[i]; Assert.False( vertex.Col == guideColor && MathF.Abs(vertex.Pos.Y - excludedY) <= 1f, - "The child guide overlapped the parent disclosure row instead of retaining its visual gap."); + "The child guide overlapped the parent disclosure row instead of retaining its visual gap." + ); } } @@ -1065,35 +995,24 @@ Vector2 size NativeImGui.SetNextWindowSize(size, (int)(ImGuiCond.Always)); using NativeStringLease nativeTestText18 = Utf8Codec.EncodeNullTerminated("Current Tree Background Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText18.pointer, null, 0); - EditorWidget.SetNextTreeNodeOpen(true); - TreeNodeResult root = EditorWidget.TreeNode( + EditorUI.current.widgets.SetNextTreeNodeOpen(true); + TreeNodeResult root = EditorUI.current.widgets.TreeNode( "current_background_root", static _ => NativeImGui.TextUnformatted("TestScene"), - new TreeNodeOptions - { - showBackground = true, - backgroundColor = EditorPalette.hierarchySceneRow, - suppressHoverHighlight = true - }); + new TreeNodeOptions { showBackground = true, backgroundColor = EditorPalette.title, suppressHoverHighlight = true } + ); if (root.isOpen) { - _ = EditorWidget.TreeNode( + _ = EditorUI.current.widgets.TreeNode( "current_background_child", static _ => NativeImGui.TextUnformatted("GameObject"), - new TreeNodeOptions - { - isLeaf = true, - selected = true - }); + new TreeNodeOptions { isLeaf = true, selected = true } + ); NativeImGui.TreePop(); } - AssertCurrentDrawListContainsColor( - NativeImGui.ColorConvertFloat4ToU32(EditorPalette.hierarchySceneRow), - "The current frame did not submit the Scene row background."); - AssertCurrentDrawListContainsColor( - NativeImGui.GetColorU32((int)(ImGuiCol.Header)), - "The current frame did not submit the selected row highlight."); + AssertCurrentDrawListContainsColor(NativeImGui.ColorConvertFloat4ToU32(EditorPalette.title), "The current frame did not submit the Scene row background."); + AssertCurrentDrawListContainsColor(NativeImGui.GetColorU32((int)(ImGuiCol.Header)), "The current frame did not submit the selected row highlight."); NativeImGui.End(); NativeImGui.Render(); } @@ -1138,7 +1057,6 @@ float maximumExpansion ImDrawVert vertex = drawList->VtxBuffer.Data[i]; if (vertex.Col != color) continue; - colorMin = Vector2.Min(colorMin, Unsafe.BitCast(vertex.Pos)); colorMax = Vector2.Max(colorMax, Unsafe.BitCast(vertex.Pos)); } @@ -1166,7 +1084,6 @@ uint color ImDrawVert vertex = drawList->VtxBuffer.Data[i]; if (vertex.Col != color) continue; - minimumX = MathF.Min(minimumX, vertex.Pos.X); maximumX = MathF.Max(maximumX, vertex.Pos.X); } @@ -1181,12 +1098,13 @@ private static void DrawInspectorCardFrame(bool assertHorizontalRange) NativeImGui.NewFrame(); NativeImGui.SetNextWindowSize(new Vector2(320f, 260f), (int)(ImGuiCond.Always)); bool isOpen = true; - EditorWidget.PanelWindow( + EditorUI.current.widgets.PanelWindow( "Inspector Width Test", ref isOpen, () => DrawInspectorCardBody(assertHorizontalRange), ImGuiWindowFlags.NoSavedSettings, - useWindowPadding: false); + useWindowPadding: false + ); NativeImGui.Render(); } @@ -1209,64 +1127,57 @@ private static void DrawInspectorCardBody(bool assertHorizontalRange) Assert.Equal(panelBodySize, NativeImGui.GetWindowSize()); } - EditorWidget.ConstrainedContent( - "##inspector_content", - () => + EditorUI.current.widgets.ConstrainedContent("##inspector_content", () => + { + if (assertHorizontalRange) { - if (assertHorizontalRange) - { - Assert.Equal( - EditorWidget.style.windowPadding, - Unsafe.BitCast(NativeImGui.Raw.GetCurrentWindow()->WindowPadding)); - Assert.Equal( - NativeImGui.GetWindowPos() + EditorWidget.style.windowPadding, - NativeImGui.GetCursorScreenPos()); - } - - EditorWidget.LabelChip("Tag", EditorPalette.inspectorTagLabel); - float tagMaximumX = NativeImGui.GetItemRectMax().X; - NativeImGui.SameLine(0f, 0f); - NativeImGui.Dummy(new Vector2(80f, NativeImGui.GetFrameHeight())); - if (assertHorizontalRange) - Assert.Equal(tagMaximumX, NativeImGui.GetItemRectMin().X, 3); - NativeImGui.SameLine(0f, EditorWidget.style.inspectorHeaderSectionSpacing); - EditorWidget.LabelChip("Layer", EditorPalette.inspectorLayerLabel); - float layerMaximumX = NativeImGui.GetItemRectMax().X; - NativeImGui.SameLine(0f, 0f); - NativeImGui.Dummy(new Vector2(80f, NativeImGui.GetFrameHeight())); - if (assertHorizontalRange) - Assert.Equal(layerMaximumX, NativeImGui.GetItemRectMin().X, 3); + Assert.Equal(EditorUI.current.widgets.style.windowPadding, Unsafe.BitCast(NativeImGui.Raw.GetCurrentWindow()->WindowPadding)); + Assert.Equal(NativeImGui.GetWindowPos() + EditorUI.current.widgets.style.windowPadding, NativeImGui.GetCursorScreenPos()); + } - bool open = EditorWidget.CollapsingCard( + EditorUI.current.widgets.LabelChip("Tag", new Vector4(0.34f, 0.29f, 0.44f, 1f)); + float tagMaximumX = NativeImGui.GetItemRectMax().X; + NativeImGui.SameLine(0f, 0f); + NativeImGui.Dummy(new Vector2(80f, NativeImGui.GetFrameHeight())); + if (assertHorizontalRange) + Assert.Equal(tagMaximumX, NativeImGui.GetItemRectMin().X, 3); + NativeImGui.SameLine(0f, EditorUI.current.widgets.style.headerSectionSpacing); + EditorUI.current.widgets.LabelChip("Layer", new Vector4(0.24f, 0.34f, 0.40f, 1f)); + float layerMaximumX = NativeImGui.GetItemRectMax().X; + NativeImGui.SameLine(0f, 0f); + NativeImGui.Dummy(new Vector2(80f, NativeImGui.GetFrameHeight())); + if (assertHorizontalRange) + Assert.Equal(layerMaximumX, NativeImGui.GetItemRectMin().X, 3); + bool open = EditorUI.current.widgets.CollapsingCard( + "long_behavior", + "BehaviorWithAnIntentionallyLongTypeNameThatMustBeClipped", + drawLeadingControl: static () => NativeImGui.Dummy(new Vector2(NativeImGui.GetFrameHeight())), + drawTrailingControl: static () => NativeImGui.Dummy(new Vector2(48f, NativeImGui.GetFrameHeight())), + trailingControlWidth: 48f + ); + if (open) + { + NativeImGui.Unindent(); + EditorUI.current.widgets.CardBody( "long_behavior", - "BehaviorWithAnIntentionallyLongTypeNameThatMustBeClipped", - drawLeadingControl: static () => NativeImGui.Dummy( - new Vector2(NativeImGui.GetFrameHeight())), - drawTrailingControl: static () => NativeImGui.Dummy( - new Vector2(48f, NativeImGui.GetFrameHeight())), - trailingControlWidth: 48f); - if (open) - { - NativeImGui.Unindent(); - EditorWidget.CardBody( - "long_behavior", - static () => EditorWidget.PropertyRow( - "long_property", - "Property With A Very Long Display Name", - static () => NativeImGui.Dummy(new Vector2( - MathF.Max(1f, NativeImGui.GetContentRegionAvail().X), - NativeImGui.GetFrameHeight())))); - NativeImGui.Indent(); - NativeImGui.TreePop(); - NativeImGui.Dummy(Vector2.Zero); - } - if (assertHorizontalRange) - Assert.Equal(0f, NativeImGui.GetScrollMaxX(), 3); - }); + static () => EditorUI.current.widgets.PropertyRow( + "long_property", + "Property With A Very Long Display Name", + static () => NativeImGui.Dummy(new Vector2(MathF.Max(1f, NativeImGui.GetContentRegionAvail().X), NativeImGui.GetFrameHeight())) + ) + ); + NativeImGui.Indent(); + NativeImGui.TreePop(); + NativeImGui.Dummy(Vector2.Zero); + } + + if (assertHorizontalRange) + Assert.Equal(0f, NativeImGui.GetScrollMaxX(), 3); + }); if (assertHorizontalRange) Assert.Equal(0f, NativeImGui.GetScrollMaxX(), 3); } + NativeImGui.EndChild(); } - } diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/Inno.Editor.Scripting.Tests.csproj b/tests/scripting/Inno.Editor.Scripting.Tests/Inno.Editor.Scripting.Tests.csproj index b8519a4b0..17c8dd369 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/Inno.Editor.Scripting.Tests.csproj +++ b/tests/scripting/Inno.Editor.Scripting.Tests/Inno.Editor.Scripting.Tests.csproj @@ -8,10 +8,18 @@ true true Editor + editor + $(InnoNativeTarget) $(DefineConstants);INNO_EDITOR + + + + + + diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/InspectionPipelineTests.cs b/tests/scripting/Inno.Editor.Scripting.Tests/InspectionPipelineTests.cs index 38cd1d4b5..20f8500c0 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/InspectionPipelineTests.cs +++ b/tests/scripting/Inno.Editor.Scripting.Tests/InspectionPipelineTests.cs @@ -1,3 +1,4 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; @@ -9,7 +10,6 @@ using System.IO; using System.Linq; using System.Numerics; - using Inno.Extensibility.Modules; using Inno.Core.Logging; using Inno.Extensibility.Types; @@ -21,35 +21,29 @@ using Inno.Editor.Inspection; using Inno.Editor.Interactions; using NativeImGui = Inno.Native.ImGui.ImGui; - using Xunit; namespace Inno.Editor.Scripting.Tests; - public sealed class InspectionPipelineTests : IDisposable { - private readonly string m_projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoInspectionPipelineTests", - Guid.NewGuid().ToString("N")); + private readonly string m_projectRoot = Path.Combine(Path.GetTempPath(), "InnoInspectionPipelineTests", Guid.NewGuid().ToString("N")); private readonly ModuleHost m_modules; private readonly TypeCatalog m_types; private readonly SerializationRegistry m_serialization; private readonly LogRouter m_logs = new(); private readonly EditorInteractionRuntime m_runtime; - public InspectionPipelineTests() { Directory.CreateDirectory(m_projectRoot); - m_modules = new ModuleHost(new ModuleHostOptions - { catalogSource = new DotNetAssemblyCatalogSource(typeof(InspectionPipelineTests).Assembly) }); + m_modules = new ModuleHost(new ModuleHostOptions { catalogSource = new DotNetAssemblyCatalogSource(typeof(InspectionPipelineTests).Assembly) }); m_types = new TypeCatalog(m_modules, new ReflectionTypeCatalogSource()); m_serialization = new SerializationRegistry(m_types, new ReflectionSerializationMetadataSource()); m_runtime = new EditorInteractionRuntime( new EditorContext(m_projectRoot, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_types, m_logs, - [m_types, m_serialization]); + [m_types, m_serialization] + ); m_runtime.Start(); } @@ -68,22 +62,9 @@ public void Dispose() public unsafe void InlineChildFailureIsConsumedAndReadonlyDisabledScopeRemainsBalanced() { EditorContext editor = m_runtime.context; - using var drawers = new PropertyDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - using var attributes = new InspectorAttributeDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - var renderer = new SerializedPropertyRenderer( - drawers, - attributes, - m_runtime.interactions, - new NoopEditService(), - m_logs); + using var drawers = new PropertyDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + using var attributes = new InspectorAttributeDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + var renderer = new SerializedPropertyRenderer(drawers, attributes, m_runtime.interactions, new NoopEditService(), m_logs); Assert.IsType(drawers.Resolve(typeof(InlineParent))); var owner = new InlineOwner(); SerializedProperty property = Assert.Single(m_serialization.GetProperties(owner)); @@ -91,13 +72,14 @@ public unsafe void InlineChildFailureIsConsumedAndReadonlyDisabledScopeRemainsBa var nativeContext = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); Inno.Native.ImGui.ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640f, 480f)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; float originalAlpha = NativeImGui.Raw.GetStyle()->Alpha; - NativeImGui.NewFrame(); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated("Inline Property Test", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText0.pointer, null, 0); @@ -110,8 +92,8 @@ public unsafe void InlineChildFailureIsConsumedAndReadonlyDisabledScopeRemainsBa { NativeImGui.End(); } - NativeImGui.Render(); + NativeImGui.Render(); Assert.True(InlineParentDrawer.drewAfterFailure); Assert.Equal(originalAlpha, NativeImGui.Raw.GetStyle()->Alpha); } @@ -127,13 +109,11 @@ public unsafe void PanelWindowFailureStillEndsTheWindowAndAllowsLaterContent() var nativeContext = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); PrepareNativeFrame(); bool isOpen = true; - - Assert.Throws(() => EditorWidget.PanelWindow( - "Throwing Panel", - ref isOpen, - static () => throw new InvalidOperationException("panel"))); + Assert.Throws(() => EditorUI.current.widgets.PanelWindow("Throwing Panel", ref isOpen, static () => throw new InvalidOperationException("panel"))); using NativeStringLease nativeTestText1 = Utf8Codec.EncodeNullTerminated("Content After Panel Failure", allowsNull: false); _ = NativeImGui.Raw.Begin((sbyte*)nativeTestText1.pointer, null, 0); NativeImGui.TextUnformatted("Still drawing."); @@ -150,36 +130,26 @@ public unsafe void PanelWindowFailureStillEndsTheWindowAndAllowsLaterContent() public unsafe void TextEditStateIsScopedByRendererOwnerAndPropertyPath() { EditorContext editor = m_runtime.context; - using var drawers = new PropertyDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - using var attributes = new InspectorAttributeDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - var firstRenderer = new SerializedPropertyRenderer( - drawers, - attributes, - m_runtime.interactions, - new NoopEditService(), - m_logs); - var secondRenderer = new SerializedPropertyRenderer( - drawers, - attributes, - m_runtime.interactions, - new NoopEditService(), - m_logs); - var firstOwner = new TextStateOwner { value = new TextStateValue("First") }; - var secondOwner = new TextStateOwner { value = new TextStateValue("Second") }; + using var drawers = new PropertyDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + using var attributes = new InspectorAttributeDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + var firstRenderer = new SerializedPropertyRenderer(drawers, attributes, m_runtime.interactions, new NoopEditService(), m_logs); + var secondRenderer = new SerializedPropertyRenderer(drawers, attributes, m_runtime.interactions, new NoopEditService(), m_logs); + var firstOwner = new TextStateOwner + { + value = new TextStateValue("First") + }; + var secondOwner = new TextStateOwner + { + value = new TextStateValue("Second") + }; SerializedProperty firstProperty = Assert.Single(m_serialization.GetProperties(firstOwner)); SerializedProperty secondProperty = Assert.Single(m_serialization.GetProperties(secondOwner)); TextStateDrawer.observations.Clear(); var nativeContext = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); DrawPropertyFrame(firstRenderer, editor, firstOwner, firstProperty); DrawPropertyFrame(firstRenderer, editor, firstOwner, firstProperty); DrawPropertyFrame(firstRenderer, editor, secondOwner, secondProperty); @@ -190,46 +160,28 @@ public unsafe void TextEditStateIsScopedByRendererOwnerAndPropertyPath() NativeImGui.Raw.DestroyContext(nativeContext); } - Assert.Equal( - ["First:", "First:First", "Second:", "First:"], - TextStateDrawer.observations); + Assert.Equal(["First:", "First:First", "Second:", "First:"], TextStateDrawer.observations); } [Fact] public unsafe void PresentationAttributesComposeAndCustomDrawersRemainExtensible() { EditorContext editor = m_runtime.context; - using var drawers = new PropertyDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - using var attributes = new InspectorAttributeDrawerRegistry( - m_runtime.interactions, - m_types, - m_serialization, - []); - var renderer = new SerializedPropertyRenderer( - drawers, - attributes, - m_runtime.interactions, - new NoopEditService(), - m_logs); + using var drawers = new PropertyDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + using var attributes = new InspectorAttributeDrawerRegistry(m_runtime.interactions, m_types, m_serialization, []); + var renderer = new SerializedPropertyRenderer(drawers, attributes, m_runtime.interactions, new NoopEditService(), m_logs); var owner = new AttributeOwner(); - SerializedProperty property = Assert.Single( - m_serialization.GetProperties(owner), - static candidate => candidate.name == nameof(AttributeOwner.value)); + SerializedProperty property = Assert.Single(m_serialization.GetProperties(owner), static candidate => candidate.name == nameof(AttributeOwner.value)); AttributeValueDrawer.observations.Clear(); ProbeInspectorAttributeDrawer.observations.Clear(); var nativeContext = NativeImGui.Raw.CreateContext(null); try { + using var uiSession = new Inno.Editor.ImGui.EditorUiSession(); + using IDisposable uiScope = uiSession.Enter(); DrawPropertyFrame(renderer, editor, owner, property); Assert.Empty(AttributeValueDrawer.observations); - Assert.Equal( - ["update:Friendly Value:False:2:8"], - ProbeInspectorAttributeDrawer.observations); - + Assert.Equal(["update:Friendly Value:False:2:8"], ProbeInspectorAttributeDrawer.observations); owner.showValue = true; DrawPropertyFrame(renderer, editor, owner, property); } @@ -238,14 +190,7 @@ public unsafe void PresentationAttributesComposeAndCustomDrawersRemainExtensible NativeImGui.Raw.DestroyContext(nativeContext); } - Assert.Equal( - [ - "update:Friendly Value:False:2:8", - "update:Friendly Value:False:2:8", - "before", - "after" - ], - ProbeInspectorAttributeDrawer.observations); + Assert.Equal(["update:Friendly Value:False:2:8", "update:Friendly Value:False:2:8", "before", "after"], ProbeInspectorAttributeDrawer.observations); Assert.Equal(["Friendly Value:False:2:8"], AttributeValueDrawer.observations); } @@ -276,6 +221,7 @@ SerializedProperty property { NativeImGui.End(); } + NativeImGui.Render(); } @@ -285,7 +231,9 @@ public bool ChangeProperty( object owner, string propertyName, Action mutation, - string historyName + string historyName, + string? mergeKey, + string? componentName ) { mutation(); return true; @@ -305,7 +253,6 @@ internal sealed class InlineParent } internal sealed class InlineFailure; - internal sealed class TextStateOwner : ISerializable { [SerializableProperty] @@ -313,7 +260,6 @@ internal sealed class TextStateOwner : ISerializable } internal sealed record TextStateValue(string id); - internal sealed class AttributeOwner : ISerializable { internal bool showValue { get; set; } @@ -330,19 +276,14 @@ internal sealed class AttributeOwner : ISerializable } internal sealed class AttributeValue; - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] internal sealed class ProbeInspectorAttribute : InspectorPresentationAttribute; - [InspectorAttributeDrawer(typeof(ProbeInspectorAttribute), priority: 10_000)] internal sealed class ProbeInspectorAttributeDrawer : IInspectorAttributeDrawer { internal static List observations { get; } = []; - public void Update(InspectorAttributeDrawContext context) - => observations.Add( - $"update:{context.label}:{context.isReadOnly}:{context.minimum}:{context.maximum}"); - + public void Update(InspectorAttributeDrawContext context) => observations.Add($"update:{context.label}:{context.isReadOnly}:{context.minimum}:{context.maximum}"); public void DrawBefore(InspectorAttributeDrawContext context) { _ = context; @@ -361,9 +302,7 @@ internal sealed class AttributeValueDrawer : IPropertyDrawer { internal static List observations { get; } = []; - public void Draw(PropertyDrawContext context) - => observations.Add( - $"{context.label}:{context.isReadOnly}:{context.minimum}:{context.maximum}"); + public void Draw(PropertyDrawContext context) => observations.Add($"{context.label}:{context.isReadOnly}:{context.minimum}:{context.maximum}"); } [PropertyDrawer(typeof(TextStateValue), priority: 10_000)] @@ -384,16 +323,12 @@ public void Draw(PropertyDrawContext context) internal sealed class InlineParentDrawer : IPropertyDrawer { internal static bool drewAfterFailure; - public void Draw(PropertyDrawContext context) { var parent = (InlineParent)context.GetValue()!; - context.DrawInlineChild( - "child", - typeof(InlineFailure), - () => parent.child, - static _ => { }, - readOnly: true); + context.DrawInlineChild("child", typeof(InlineFailure), () => parent.child, static _ => + { + }, readOnly: true); drewAfterFailure = true; } } @@ -401,6 +336,5 @@ public void Draw(PropertyDrawContext context) [PropertyDrawer(typeof(InlineFailure), priority: 10_000)] internal sealed class InlineFailureDrawer : IPropertyDrawer { - public void Draw(PropertyDrawContext context) - => throw new InvalidOperationException("Inline drawer failure."); + public void Draw(PropertyDrawContext context) => throw new InvalidOperationException("Inline drawer failure."); } diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/InspectorPresentationLayoutTests.cs b/tests/scripting/Inno.Editor.Scripting.Tests/InspectorPresentationLayoutTests.cs index fb909cae7..959f91fad 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/InspectorPresentationLayoutTests.cs +++ b/tests/scripting/Inno.Editor.Scripting.Tests/InspectorPresentationLayoutTests.cs @@ -1,3 +1,5 @@ +using ImGuiWindowFlags = Inno.Editor.ImGui.ImGuiWindowFlags; +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using NativeVector4 = Inno.Native.ImGui.ImVec4C; @@ -11,23 +13,30 @@ using Xunit; namespace Inno.Editor.Scripting.Tests; - +[Trait("Acceptance", "Visual")] public sealed unsafe class InspectorPresentationLayoutTests : IDisposable { private readonly ImGuiNativeContext* m_context = NativeImGui.Raw.CreateContext(null); - + private readonly Inno.Editor.ImGui.EditorUiSession m_ui = new(); + private readonly IDisposable m_uiScope; public InspectorPresentationLayoutTests() { + m_uiScope = m_ui.Enter(); ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(640, 480)); io->DeltaTime = 1f / 60f; io->BackendFlags |= (int)ImGuiBackendFlags.RendererHasTextures; io->Fonts->RendererHasTextures = 1; - EditorWidget.style.ResetZoom(); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.style.ResetZoom(); + EditorUI.current.widgets.SetupStyle(); } - public void Dispose() => NativeImGui.Raw.DestroyContext(m_context); + public void Dispose() + { + m_uiScope.Dispose(); + m_ui.Dispose(); + NativeImGui.Raw.DestroyContext(m_context); + } [Fact] public void FloatingPanelCloseButtonClosesOnMouseRelease() @@ -38,9 +47,10 @@ void Frame() NativeImGui.NewFrame(); NativeImGui.SetNextWindowPos(new Vector2(30, 30)); NativeImGui.SetNextWindowSize(new Vector2(320, 180)); - EditorWidget.PanelWindow("Floating acceptance", ref open, () => NativeImGui.TextUnformatted("Body")); + EditorUI.current.widgets.PanelWindow("Floating acceptance", ref open, () => NativeImGui.TextUnformatted("Body")); NativeImGui.Render(); } + Frame(); Frame(); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated("Floating acceptance", allowsNull: false); @@ -66,8 +76,8 @@ float zoom ) { ImGuiIO* io = NativeImGui.Raw.GetIO(); io->DisplaySize = Unsafe.BitCast(new Vector2(width, height)); - EditorWidget.style.SetZoom(zoom); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.style.SetZoom(zoom); + EditorUI.current.widgets.SetupStyle(); Vector2 hover = default; const string text = "The profile is evaluated after this camera stack is composed. The final words must remain readable."; void Frame() @@ -79,14 +89,16 @@ void Frame() NativeImGui.Raw.Begin((sbyte*)nativeTestText1.pointer, null, 0); NativeImGui.Button("Hover near edge"); hover = (NativeImGui.GetItemRectMin() + NativeImGui.GetItemRectMax()) * 0.5f; - EditorWidget.DrawItemTooltip(text); + EditorUI.current.widgets.DrawItemTooltip(text); NativeImGui.End(); NativeImGui.Render(); } + Frame(); Frame(); NativeImGui.Raw.AddMousePosEvent(io, hover.X, hover.Y); - for (int index = 0; index < 4; index++) Frame(); + for (int index = 0; index < 4; index++) + Frame(); using NativeStringLease nativeTestText2 = Utf8Codec.EncodeNullTerminated("##Tooltip_00", allowsNull: false); ImGuiWindow* tooltip = NativeImGui.Raw.FindWindowByName((sbyte*)nativeTestText2.pointer); Assert.False((tooltip == null)); @@ -109,7 +121,7 @@ public void HelpBoxUsesWrappedCardHeightWithoutHorizontalOverflow() using NativeStringLease nativeTestText3 = Utf8Codec.EncodeNullTerminated("HelpBox acceptance", allowsNull: false); NativeImGui.Raw.Begin((sbyte*)nativeTestText3.pointer, null, 0); float width = NativeImGui.GetContentRegionAvail().X; - EditorWidget.HelpBox("Assign a Sprite to enable atlas geometry, borders, pixel density, and sampling controls.", "i", EditorPalette.warning); + EditorUI.current.widgets.HelpBox("Assign a Sprite to enable atlas geometry, borders, pixel density, and sampling controls.", "i", EditorPalette.warning); Vector2 size = NativeImGui.GetItemRectSize(); Assert.Equal(width, size.X, 2); Assert.True(size.Y > NativeImGui.GetTextLineHeight() * 2); @@ -130,8 +142,8 @@ public void RoundedWindowTitleAndBodyHaveContinuousOpaqueCoverage( float framebufferScale ) { NativeImGui.Raw.GetIO()->DisplayFramebufferScale = Unsafe.BitCast(new Vector2(framebufferScale)); - EditorWidget.style.SetZoom(zoom); - EditorWidget.SetupStyle(); + EditorUI.current.widgets.style.SetZoom(zoom); + EditorUI.current.widgets.SetupStyle(); NativeImGui.Raw.GetStyle()->WindowRounding = 6f * zoom; NativeImGui.Raw.GetStyle()->FrameBorderSize = 0; for (int frame = 0; frame < 2; frame++) @@ -156,13 +168,14 @@ float framebufferScale float coverage = CoverageAt(window->DrawList, point); Assert.True(coverage >= 0.999f, $"Transparent title/body join: zoom={zoom}, scale={framebufferScale}, y={point.Y}, alpha={coverage}"); } + for (int pixel = -2; pixel <= 2; pixel++) { float y = (MathF.Floor(seam * framebufferScale) + pixel + 0.5f) / framebufferScale; Vector2 point = new(window->Pos.X + window->Size.X * 0.47f, y); - Assert.True(CoverageAt(window->DrawList, point) >= 0.999f, - $"Transparent device pixel: zoom={zoom}, scale={framebufferScale}, y={y}"); + Assert.True(CoverageAt(window->DrawList, point) >= 0.999f, $"Transparent device pixel: zoom={zoom}, scale={framebufferScale}, y={y}"); } + Assert.True(NativeImGui.Raw.GetStyle()->AntiAliasedFill != 0); Assert.Equal(6f * zoom, window->WindowRounding); } @@ -187,15 +200,18 @@ Vector2 point Vector2 bPosition = Unsafe.BitCast(b.Pos); Vector2 cPosition = Unsafe.BitCast(c.Pos); float area = Cross(bPosition - aPosition, cPosition - aPosition); - if (MathF.Abs(area) < 0.000001f) continue; + if (MathF.Abs(area) < 0.000001f) + continue; float wa = Cross(bPosition - point, cPosition - point) / area; float wb = Cross(cPosition - point, aPosition - point) / area; float wc = 1 - wa - wb; - if (wa < 0 || wb < 0 || wc < 0) continue; + if (wa < 0 || wb < 0 || wc < 0) + continue; float alpha = (wa * (a.Col >> 24) + wb * (b.Col >> 24) + wc * (c.Col >> 24)) / 255f; result = alpha + result * (1 - alpha); } } + return result; } diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.UiLifecycle.cs b/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.UiLifecycle.cs new file mode 100644 index 000000000..ad47add50 --- /dev/null +++ b/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.UiLifecycle.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using Inno.Core.Serialization; +using Inno.Core.Serialization.Converters; +using Inno.Editor.Core; +using Inno.Editor.Interactions; +using Inno.Scripting.Compiler; +using Inno.Scripting.Reload; +using Xunit; + +namespace Inno.Editor.Scripting.Tests; + +public sealed partial class ScriptingPipelineTests +{ + [Fact] + [Trait("Acceptance", "Lifecycle")] + [Trait("ProcessIsolation", "Required")] + public void NeutralSnapshotSurvivesRetirementWithoutKeepingTheScriptGenerationAlive() + { + m_fixture.Write("SnapshotPayload.cs", """ + using InnoEngine.Serialization; + public sealed class SnapshotPayload : ISerializable + { + [SerializableProperty] public int count = 42; + } + """); + using ScriptReloadHost reload = m_fixture.CreateReloadHost(); + reload.Start(); + ScriptCompilationResult first = m_fixture.CompilePending(reload); + Assert.True(first.success, FormatDiagnostics(first)); + Assert.True(reload.ApplyPendingReload()); + (SerializationSnapshot snapshot, WeakReference retired) = CaptureScriptSnapshot(m_fixture); + byte[] original = snapshot.ToArray(); + + m_fixture.Write("SnapshotPayload.cs", "public sealed class ReplacementPayload { }"); + ScriptCompilationResult replacement = m_fixture.CompilePending(reload); + Assert.True(replacement.success, FormatDiagnostics(replacement)); + Assert.True(reload.ApplyPendingReload()); + CompleteUnloadVerification(reload); + Assert.False(retired.IsAlive, "The neutral snapshot retained the old script type across retirement."); + Assert.Equal(original, snapshot.ToArray()); + Assert.NotEmpty(original); + GC.KeepAlive(snapshot); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static (SerializationSnapshot, WeakReference) CaptureScriptSnapshot(ScriptingFixture fixture) + { + Type type = fixture.ResolveActiveType("SnapshotPayload"); + var payload = (ISerializable)Activator.CreateInstance(type)!; + return (fixture.host.serialization.CaptureSnapshot(new CapturedScriptPayload(payload)), new WeakReference(type)); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + [Trait("ProcessIsolation", "Required")] + public void TenActionMenuGenerationsRetireTypesAndPreserveLastGoodAfterFailure() + { + m_fixture.WriteVersionedBehavior(1); + WriteAction(1); + var reloads = new EditorReloadCoordinator(); + using ScriptReloadHost reload = m_fixture.CreateReloadHost(reloads); + reload.Start(); + ScriptCompilationResult first = m_fixture.CompilePending(reload); + Assert.True(first.success, FormatDiagnostics(first)); + Assert.True(reload.ApplyPendingReload()); + using EditorInteractionRuntime runtime = m_fixture.CreateEditorRuntime(reloads); + runtime.Start(); + EditorMenuModel retainedMenu = runtime.interactions.For("tests/menu-lifecycle").BuildMenu(); + Assert.Equal("Generation 1", Assert.Single(retainedMenu.items).label); + WeakReference previous = CaptureActiveType(m_fixture, "ReloadedAction", 1); + + for (int version = 2; version <= 11; version++) + { + m_fixture.Write("Editor/LifecycleAction.editor.cs", "public sealed class ReloadedAction {"); + ScriptCompilationResult failed = m_fixture.CompilePending(reload); + Assert.False(failed.success); + Assert.False(reload.ApplyPendingReload()); + AssertMenu(runtime, version - 1); + + WriteAction(version); + ScriptCompilationResult replacement = m_fixture.CompilePending(reload); + Assert.True(replacement.success, FormatDiagnostics(replacement)); + Assert.True(reload.ApplyPendingReload()); + CompleteUnloadVerification(reload); + Assert.False(previous.IsAlive, "The retired Action type survived the public generation barrier."); + AssertMenu(runtime, version); + previous = CaptureActiveType(m_fixture, "ReloadedAction", version); + } + + Assert.Equal("Generation 1", Assert.Single(retainedMenu.items).label); + GC.KeepAlive(retainedMenu); + + void WriteAction(int version) + { + m_fixture.Write("Editor/LifecycleAction.editor.cs", $$""" + using InnoEditor.Interactions; + + [EditorAction("tests.lifecycle.action", "tests/menu-lifecycle")] + [EditorMenu("tests/menu-lifecycle", "Generation")] + public sealed class ReloadedAction : EditorAction + { + public int version => {{version}}; + protected override EditorActionState Query(EditorActionContext context) + => new(true, true, false, "Generation {{version}}", null); + protected override void Execute(EditorActionContext context) { } + } + """); + } + } + + private static void AssertMenu( + EditorInteractionRuntime runtime, + int version + ) { + EditorMenuItem item = Assert.Single(runtime.interactions.For("tests/menu-lifecycle").BuildMenu().items); + Assert.Equal("Generation " + version, item.label); + Assert.Equal("tests.lifecycle.action", item.actionId); + Assert.True(item.status.isEnabled); + } +} + +internal sealed class CapturedScriptPayload(ISerializable payload) : ISerializable +{ + internal ISerializable payload { get; } = payload; +} + +internal sealed class CapturedScriptPayloadConverter : SerializationConverter +{ + public override void Write( + SerializationWriter writer, + CapturedScriptPayload value + ) => writer.WritePropertiesData("state", value.payload); + + public override CapturedScriptPayload Read(SerializationReader reader) + => throw new NotSupportedException("This fixture only captures an existing generation."); +} diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs b/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs index 6a1277597..c1abee2b2 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs +++ b/tests/scripting/Inno.Editor.Scripting.Tests/ScriptingPipelineTests.cs @@ -17,7 +17,6 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; - using Inno.Assets; using Inno.Assets.Pipeline; using Inno.Extensibility.Modules; @@ -40,13 +39,10 @@ using Xunit; namespace Inno.Editor.Scripting.Tests; - -public sealed class ScriptingPipelineTests : IDisposable +public sealed partial class ScriptingPipelineTests : IDisposable { private readonly ScriptingFixture m_fixture = new(); - public void Dispose() => m_fixture.Dispose(); - [Theory] [InlineData(false)] [InlineData(true)] @@ -74,31 +70,20 @@ public void SourceLocalAssetPathsFollowInstalledPluginOwnership(bool runtimeDepl using var fixture = new ScriptingFixture(( root, serialization - ) => WritePluginPackage(root, "local-path.iplugin", serialization, - new PluginManifest { pluginId = "tests.local-path", displayName = "Source Ownership Fixture" }, - new Dictionary - { - ["Assets/Nested.imeta"] = serialization.Serialize(new ScriptingAssetSourceMeta - { - persistentId = Guid.NewGuid(), - sourceKind = (int)AssetSourceKind.Directory - }), - ["Assets/Nested/SourceLocalPathProbe.cs"] = System.Text.Encoding.UTF8.GetBytes(""" + ) => WritePluginPackage(root, "local-path.iplugin", serialization, new PluginManifest { + pluginId = "tests.local-path", + displayName = "Source Ownership Fixture" + }, new Dictionary { ["Assets/Nested.imeta"] = serialization.Serialize(new ScriptingAssetSourceMeta { persistentId = Guid.NewGuid(), sourceKind = (int)AssetSourceKind.Directory }), ["Assets/Nested/SourceLocalPathProbe.cs"] = System.Text.Encoding.UTF8.GetBytes(""" using InnoEngine.Assets; public static class SourceLocalPathProbe { public static AssetPath Resolve() => Assets.LocalPath("Materials/Default.imaterial"); } - """), - ["Assets/Nested/SourceLocalPathProbe.cs.imeta"] = CreateScriptSourceMeta(serialization, Guid.NewGuid()) - })); + """), ["Assets/Nested/SourceLocalPathProbe.cs.imeta"] = CreateScriptSourceMeta(serialization, Guid.NewGuid()) })); ScriptCompilationResult result = runtimeDeployment ? fixture.CompileRuntimeDeployment() : fixture.Compile(); Assert.True(result.success, FormatDiagnostics(result)); - string assemblyPath = Assert.Single(result.runtimeAssemblyPaths.Where(static path => - Path.GetFileName(path).StartsWith("Inno.Plugin.", StringComparison.Ordinal))); - + string assemblyPath = Assert.Single(result.runtimeAssemblyPaths.Where(static path => Path.GetFileName(path).StartsWith("Inno.Plugin.", StringComparison.Ordinal))); AssetPath path = InvokePublicPathProbe(assemblyPath); - Assert.Equal(new AssetSourceId("tests.local-path"), path.source); Assert.Equal("Materials/Default.imaterial", path.localPath); } @@ -119,8 +104,7 @@ public static class SourceLocalPathProbe string map = Assert.Single(project.Descendants("PathMap")).Value; Assert.Contains("Nested,,==Folder", map, StringComparison.Ordinal); Assert.Contains("project::./Nested,,==Folder", map, StringComparison.Ordinal); - Assert.Contains(project.Descendants("HintPath"), reference => ContainsCustomAttribute( - Path.GetFullPath(reference.Value, m_fixture.projectRoot), "CallerFilePathAttribute")); + Assert.Contains(project.Descendants("HintPath"), reference => ContainsCustomAttribute(Path.GetFullPath(reference.Value, m_fixture.projectRoot), "CallerFilePathAttribute")); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -131,9 +115,13 @@ private static AssetPath InvokePublicPathProbe(string assemblyPath) { using var stream = new MemoryStream(File.ReadAllBytes(assemblyPath)); var assembly = lifetime.LoadFromStream(stream); - Func resolve = assembly.GetType("SourceLocalPathProbe", throwOnError: true)! - .GetMethod("Resolve", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)! - .CreateDelegate>(); + Func resolve = assembly.GetType( + "SourceLocalPathProbe", + throwOnError: true + )!.GetMethod( + "Resolve", + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static + )!.CreateDelegate>(); return resolve(); } finally @@ -160,8 +148,7 @@ public sealed class FrameworkReferenceProbe : GameBehavior using FileStream stream = File.OpenRead(Path.Combine(result.outputDirectory!, "Inno.GameScripts.dll")); using var executable = new PEReader(stream); MetadataReader metadata = executable.GetMetadataReader(); - Assert.DoesNotContain(metadata.AssemblyReferences, handle => - metadata.GetString(metadata.GetAssemblyReference(handle).Name) == "System.Private.CoreLib"); + Assert.DoesNotContain(metadata.AssemblyReferences, handle => metadata.GetString(metadata.GetAssemblyReference(handle).Name) == "System.Private.CoreLib"); } [Theory] @@ -178,8 +165,7 @@ public static class IgnoredApiProbe """); ScriptCompilationResult result = runtimeDeployment ? m_fixture.CompileRuntimeDeployment() : m_fixture.Compile(); Assert.False(result.success); - Assert.Contains(result.diagnostics, static diagnostic => diagnostic.id == "CS1061" - && diagnostic.message.Contains("CaptureFrom", StringComparison.Ordinal)); + Assert.Contains(result.diagnostics, static diagnostic => diagnostic.id == "CS1061" && diagnostic.message.Contains("CaptureFrom", StringComparison.Ordinal)); } [Fact] @@ -207,8 +193,13 @@ Action observer ScriptCompilationResult compilation = m_fixture.CompileRuntimeDeployment(); Assert.True(compilation.success, FormatDiagnostics(compilation)); m_fixture.compiler.GenerateProjectFiles(); + foreach (string project in new[] + { + "Inno.GameScripts.csproj", + "Inno.EditorScripts.csproj" + } - foreach (string project in new[] { "Inno.GameScripts.csproj", "Inno.EditorScripts.csproj" }) + ) { XDocument document = XDocument.Load(Path.Combine(m_fixture.projectRoot, project)); bool found = false; @@ -220,13 +211,12 @@ Action observer foreach (TypeDefinitionHandle handle in metadata.TypeDefinitions) { TypeDefinition type = metadata.GetTypeDefinition(handle); - if (metadata.GetString(type.Namespace) != "InnoEngine.Events" - || metadata.GetString(type.Name) != "EventDispatcher") + if (metadata.GetString(type.Namespace) != "InnoEngine.Events" || metadata.GetString(type.Name) != "EventDispatcher") continue; - found = type.GetEvents().Any(eventHandle => - metadata.GetString(metadata.GetEventDefinition(eventHandle).Name) == "dispatched"); + found = type.GetEvents().Any(eventHandle => metadata.GetString(metadata.GetEventDefinition(eventHandle).Name) == "dispatched"); } } + Assert.True(found, $"The IDE reference set in {project} must contain the dispatch observation event."); } } @@ -339,7 +329,6 @@ public override void Draw(ShaderNodeDrawContext context) m_fixture.compiler.GenerateProjectFiles(); Assert.True(ContainsShaderApi("Inno.EditorScripts.csproj")); Assert.False(ContainsShaderApi("Inno.GameScripts.csproj")); - bool ContainsShaderApi(string project) { XDocument document = XDocument.Load(Path.Combine(m_fixture.projectRoot, project)); @@ -353,8 +342,10 @@ bool ContainsShaderApi(string project) { TypeDefinition type = metadata.GetTypeDefinition(handle); return metadata.GetString(type.Namespace) == "InnoEditor.Rendering.Shaders" && metadata.GetString(type.Name) == "IShaderNodeCompiler"; - })) return true; + })) + return true; } + return false; } } @@ -416,9 +407,7 @@ public sealed class ProjectTools public EditorContext? context { get; set; } } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.True(result.success, FormatDiagnostics(result)); Assert.NotNull(result.outputDirectory); Assert.True(File.Exists(Path.Combine(result.outputDirectory!, "Inno.GameScripts.dll"))); @@ -443,9 +432,7 @@ internal static bool TryResolve(AssetPath path, out MaterialAsset? asset) => Assets.TryLoad(path, out asset); } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.True(result.success, FormatDiagnostics(result)); } @@ -469,9 +456,7 @@ public sealed class SettingsProbe : ISerializable { } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.True(result.success, FormatDiagnostics(result)); } @@ -480,19 +465,13 @@ public void RuntimeDeploymentCompilationDoesNotCompileOrValidateEditorSources() { m_fixture.Write("RuntimeOnly.cs", "public sealed class RuntimeOnly { }"); m_fixture.Write("BrokenTool.editor.cs", "this is deliberately invalid editor C#"); - ScriptCompilationResult result = m_fixture.CompileRuntimeDeployment(); - Assert.True(result.success, FormatDiagnostics(result)); Assert.NotNull(result.outputDirectory); Assert.True(File.Exists(Path.Combine(result.outputDirectory!, "Inno.GameScripts.dll"))); Assert.False(File.Exists(Path.Combine(result.outputDirectory!, "Inno.EditorScripts.dll"))); - Assert.DoesNotContain( - result.moduleDeployments, - static request => request.scope == AssemblyScope.Editor); - Assert.DoesNotContain( - result.compiledAssemblyNames.Concat(result.reusedAssemblyNames), - static assemblyName => assemblyName.Contains("Editor", StringComparison.Ordinal)); + Assert.DoesNotContain(result.moduleDeployments, static request => request.scope == AssemblyScope.Editor); + Assert.DoesNotContain(result.compiledAssemblyNames.Concat(result.reusedAssemblyNames), static assemblyName => assemblyName.Contains("Editor", StringComparison.Ordinal)); } [Fact] @@ -518,7 +497,6 @@ public sealed class AttributedBehavior : GameBehavior public int value { get; set; } } """); - using ScriptReloadHost reload = m_fixture.CreateReloadHost(); reload.Start(); ScriptCompilationResult authoring = m_fixture.CompilePending(reload); @@ -531,7 +509,6 @@ public sealed class AttributedBehavior : GameBehavior Assert.True(ContainsCustomAttribute(authoringAssembly, nameof(RangeAttribute))); Assert.True(ContainsCustomAttribute(authoringAssembly, "CustomPresentationAttribute")); Assert.True(ContainsCustomAttribute(authoringAssembly, nameof(SerializablePropertyAttribute))); - ScriptCompilationResult player = m_fixture.CompileRuntimeDeployment(); Assert.True(player.success, FormatDiagnostics(player)); string playerAssembly = Path.Combine(player.outputDirectory!, "Inno.GameScripts.dll"); @@ -544,10 +521,8 @@ public sealed class AttributedBehavior : GameBehavior using FileStream stream = File.OpenRead(playerAssembly); using var executable = new PEReader(stream); MetadataReader metadata = executable.GetMetadataReader(); - Assert.DoesNotContain(metadata.AssemblyReferences, handle => - metadata.GetString(metadata.GetAssemblyReference(handle).Name).Contains("Editor", StringComparison.Ordinal)); - Assert.DoesNotContain(metadata.TypeDefinitions, handle => - metadata.GetString(metadata.GetTypeDefinition(handle).Name) == "CustomPresentationAttribute"); + Assert.DoesNotContain(metadata.AssemblyReferences, handle => metadata.GetString(metadata.GetAssemblyReference(handle).Name).Contains("Editor", StringComparison.Ordinal)); + Assert.DoesNotContain(metadata.TypeDefinitions, handle => metadata.GetString(metadata.GetTypeDefinition(handle).Name) == "CustomPresentationAttribute"); } [Fact] @@ -561,17 +536,12 @@ public sealed class TargetBoundBehavior : GameBehavior } """); string targetRuntime = m_fixture.CreateDeploymentRuntime(); - ScriptCompilationResult first = m_fixture.CompileRuntimeDeployment(targetRuntime); - Assert.True(first.success, FormatDiagnostics(first)); File.Delete(Path.Combine(targetRuntime, "Inno.Scene.dll")); - ScriptCompilationResult second = m_fixture.CompileRuntimeDeployment(targetRuntime); - Assert.False(second.success); - Assert.Contains(second.diagnostics, static diagnostic => - diagnostic.severity == DiagnosticSeverity.Error); + Assert.Contains(second.diagnostics, static diagnostic => diagnostic.severity == DiagnosticSeverity.Error); } [Fact] @@ -584,9 +554,7 @@ public sealed class ForbiddenUsingBehavior : GameBehavior { } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.False(result.success); Assert.Contains(result.diagnostics, static diagnostic => diagnostic.id == "INNO2003"); } @@ -601,9 +569,7 @@ public sealed class RemovedBehaviorBase : Behavior { } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.False(result.success); Assert.Contains(result.diagnostics, static diagnostic => diagnostic.id == "CS0246"); } @@ -622,10 +588,8 @@ public sealed class EditorApiProbe m_fixture.Write("EditorApiProbe.cs", source); ScriptCompilationResult runtimeResult = m_fixture.Compile(); Assert.False(runtimeResult.success); - m_fixture.Move("EditorApiProbe.cs", "EditorApiProbe.editor.cs"); ScriptCompilationResult editorResult = m_fixture.Compile(); - Assert.True(editorResult.success, FormatDiagnostics(editorResult)); } @@ -643,31 +607,28 @@ public sealed class SecondaryProbe : GameBehavior { } """); - ScriptCompilationResult result = m_fixture.Compile(); - Assert.False(result.success); - Assert.Contains(result.diagnostics, static diagnostic => - diagnostic.id == "INNO2001" && - diagnostic.severity == DiagnosticSeverity.Error && - diagnostic.message.Contains("SecondaryProbe", StringComparison.Ordinal)); + Assert.Contains( + result.diagnostics, + static diagnostic => diagnostic.id == "INNO2001" + && diagnostic.severity == DiagnosticSeverity.Error + && diagnostic.message.Contains( + "SecondaryProbe", + StringComparison.Ordinal + ) + ); } [Fact] public void CachedCompilationReplaysWarningsAndReusesTheGeneration() { - m_fixture.Write( - "CachedWarningProbe.cs", - "public sealed class CachedWarningProbe { private int unused; }"); - + m_fixture.Write("CachedWarningProbe.cs", "public sealed class CachedWarningProbe { private int unused; }"); ScriptCompilationResult first = m_fixture.Compile(); ScriptCompilationResult second = m_fixture.Compile(); - Assert.True(first.success, FormatDiagnostics(first)); Assert.True(second.success, FormatDiagnostics(second)); - ScriptDiagnostic firstWarning = Assert.Single(first.diagnostics, static diagnostic => - diagnostic.id == "CS0169" && - diagnostic.severity == DiagnosticSeverity.Warning); + ScriptDiagnostic firstWarning = Assert.Single(first.diagnostics, static diagnostic => diagnostic.id == "CS0169" && diagnostic.severity == DiagnosticSeverity.Warning); Assert.Contains(second.diagnostics, diagnostic => diagnostic == firstWarning); Assert.Equal(first.outputDirectory, second.outputDirectory); Assert.NotEmpty(second.reusedAssemblyNames); @@ -678,17 +639,14 @@ public void CompilerProgressIsMonotonicAndIncludesStageTimings() { m_fixture.Write("ProgressProbe.cs", "public sealed class ProgressProbe { }"); var progress = new ProgressRecorder(); - ScriptCompilationResult result = m_fixture.Compile(progress); - Assert.True(result.success, FormatDiagnostics(result)); Assert.NotEmpty(progress.values); Assert.All(progress.values, static value => Assert.InRange(value.fraction, 0f, 1f)); Assert.True(progress.values.Zip(progress.values.Skip(1), static ( left, right - ) => - right.fraction >= left.fraction).All(static value => value)); + ) => right.fraction >= left.fraction).All(static value => value)); Assert.NotEmpty(result.stageTimings); Assert.All(result.stageTimings, static timing => Assert.True(timing.elapsed >= TimeSpan.Zero)); } @@ -699,18 +657,9 @@ public void PreCanceledCompilationDoesNotPublishAnArtifactGeneration() m_fixture.Write("CanceledProbe.cs", "public sealed class CanceledProbe { }"); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); - - Assert.Throws(() => - m_fixture.Compile(cancellationToken: cancellation.Token)); - - string artifactRoot = Path.Combine( - m_fixture.projectRoot, - "Library", - "Artifacts", - "ScriptAssemblies"); - Assert.True( - !Directory.Exists(artifactRoot) || - !Directory.EnumerateDirectories(artifactRoot).Any()); + Assert.Throws(() => m_fixture.Compile(cancellationToken: cancellation.Token)); + string artifactRoot = Path.Combine(m_fixture.projectRoot, "Library", "Artifacts", "ScriptAssemblies"); + Assert.True(!Directory.Exists(artifactRoot) || !Directory.EnumerateDirectories(artifactRoot).Any()); } [Fact] @@ -718,39 +667,30 @@ public void LogicalDocumentationProjectsNamespacesWithoutLosingMemberDescription { m_fixture.compiler.GenerateProjectFiles(); XDocument project = XDocument.Load(Path.Combine(m_fixture.projectRoot, "Inno.GameScripts.csproj")); - XElement[] members = project.Descendants("HintPath") - .Select(reference => Path.ChangeExtension(Path.GetFullPath(reference.Value, m_fixture.projectRoot), ".xml")) - .Where(File.Exists) - .SelectMany(path => XDocument.Load(path).Descendants("member")) - .ToArray(); - - XElement manager = Assert.Single(members.Where(member => - member.Attribute("name")?.Value == "T:InnoEngine.Scene.SceneManager")); + XElement[] members = project.Descendants("HintPath").Select(reference => Path.ChangeExtension(Path.GetFullPath(reference.Value, m_fixture.projectRoot), ".xml")).Where(File.Exists).SelectMany(path => XDocument.Load(path).Descendants("member")).ToArray(); + XElement manager = Assert.Single(members.Where(member => member.Attribute("name")?.Value == "T:InnoEngine.Scene.SceneManager")); Assert.Contains("scene operations", manager.Element("summary")!.Value, StringComparison.Ordinal); - Assert.Contains(manager.Descendants("see"), reference => - reference.Attribute("cref")?.Value == "T:InnoEngine.Scene.SceneWorld"); - Assert.Contains(members, member => - member.Attribute("name")?.Value == "P:InnoEngine.Scene.SceneManager.activeScene" && - member.Element("summary")?.Value.Contains("active scene", StringComparison.Ordinal) == true); - Assert.DoesNotContain(members, member => - member.Attribute("name")?.Value.StartsWith("T:Inno.Scene.", StringComparison.Ordinal) == true); + Assert.Contains(manager.Descendants("see"), reference => reference.Attribute("cref")?.Value == "T:InnoEngine.Scene.SceneWorld"); + Assert.Contains( + members, + member => member.Attribute("name")?.Value == "P:InnoEngine.Scene.SceneManager.activeScene" + && member.Element("summary")?.Value.Contains( + "active scene", + StringComparison.Ordinal + ) == true + ); + Assert.DoesNotContain(members, member => member.Attribute("name")?.Value.StartsWith("T:Inno.Scene.", StringComparison.Ordinal) == true); } [Fact] public void GenerateProjectFilesUsesTheSameRuntimeAndEditorClassification() { - m_fixture.Write( - "Runtime.cs", - "using InnoEngine.Scene; public sealed class RuntimeScript : GameBehavior { }"); + m_fixture.Write("Runtime.cs", "using InnoEngine.Scene; public sealed class RuntimeScript : GameBehavior { }"); m_fixture.Write("Tools.editor.cs", "public sealed class EditorScript { }"); m_fixture.Rescan(); - m_fixture.compiler.GenerateProjectFiles(); - - string gameProject = File.ReadAllText( - Path.Combine(m_fixture.projectRoot, "Inno.GameScripts.csproj")); - string editorProject = File.ReadAllText( - Path.Combine(m_fixture.projectRoot, "Inno.EditorScripts.csproj")); + string gameProject = File.ReadAllText(Path.Combine(m_fixture.projectRoot, "Inno.GameScripts.csproj")); + string editorProject = File.ReadAllText(Path.Combine(m_fixture.projectRoot, "Inno.EditorScripts.csproj")); Assert.Contains("Compile Include=\"Assets/Runtime.cs\"", gameProject); Assert.DoesNotContain("Tools.editor.cs", gameProject); Assert.Contains("Compile Include=\"Assets/Tools.editor.cs\"", editorProject); @@ -762,33 +702,19 @@ public void GenerateProjectFilesUsesTheSameRuntimeAndEditorClassification() [Fact] public void ProjectTildeDirectoriesRemainBrowsableAndParticipateInAuthoringCompilation() { - m_fixture.Write( - "Runtime.cs", - "using InnoEngine.Scene; public sealed class RuntimeScript : GameBehavior { }"); - m_fixture.Write( - "~Examples/Example.cs", - "using InnoEngine.Scene; public sealed class ExampleScript : GameBehavior { }"); + m_fixture.Write("Runtime.cs", "using InnoEngine.Scene; public sealed class RuntimeScript : GameBehavior { }"); + m_fixture.Write("~Examples/Example.cs", "using InnoEngine.Scene; public sealed class ExampleScript : GameBehavior { }"); m_fixture.Rescan(); - - Assert.True(m_fixture.assets.TryGetFileSystemEntry( - AssetPath.Project("~Examples"), - out AssetFileEntry tildeRoot)); + Assert.True(m_fixture.assets.TryGetFileSystemEntry(AssetPath.Project("~Examples"), out AssetFileEntry tildeRoot)); Assert.False(tildeRoot.isSample); Assert.False(tildeRoot.isSampleContent); - Assert.True(m_fixture.assets.TryGetFileSystemEntry( - AssetPath.Project("~Examples/Example.cs"), - out AssetFileEntry tildeSource)); + Assert.True(m_fixture.assets.TryGetFileSystemEntry(AssetPath.Project("~Examples/Example.cs"), out AssetFileEntry tildeSource)); Assert.False(tildeSource.isSampleContent); - Assert.True(m_fixture.assets.TryGetInfo( - AssetPath.Project("~Examples/Example.cs"), - out _)); - + Assert.True(m_fixture.assets.TryGetInfo(AssetPath.Project("~Examples/Example.cs"), out _)); ScriptCompilationResult compilation = m_fixture.Compile(); Assert.True(compilation.success, FormatDiagnostics(compilation)); - m_fixture.compiler.GenerateProjectFiles(); - string gameProject = File.ReadAllText( - Path.Combine(m_fixture.projectRoot, "Inno.GameScripts.csproj")); + string gameProject = File.ReadAllText(Path.Combine(m_fixture.projectRoot, "Inno.GameScripts.csproj")); Assert.Contains("Compile Include=\"Assets/Runtime.cs\"", gameProject); Assert.Contains("Compile Include=\"Assets/~Examples/Example.cs\"", gameProject); } @@ -806,48 +732,35 @@ public void InstalledPluginSampleImportsAsAnImmediatelyCompilableWritableCopy() Assert.True(fixture.assets.TryGetFileSystemEntry(sourceFile, out AssetFileEntry sampleContent)); Assert.True(sampleContent.isReadOnly); Assert.True(sampleContent.isSampleContent); - Assert.True(fixture.assets.TryGetInfo( - sourceFile, - out AssetInfo? templateInfo)); + Assert.True(fixture.assets.TryGetInfo(sourceFile, out AssetInfo? templateInfo)); Guid sourcePersistentId = Assert.IsType(templateInfo).persistentId; - ScriptCompilationResult authoring = fixture.Compile(); Assert.True(authoring.success, FormatDiagnostics(authoring)); - Assert.Contains(authoring.compiledAssemblyNames, - name => name.EndsWith(".Samples", StringComparison.Ordinal)); - Assert.DoesNotContain(authoring.runtimeAssemblyPaths, - path => Path.GetFileNameWithoutExtension(path).EndsWith(".Samples", StringComparison.Ordinal)); + Assert.Contains(authoring.compiledAssemblyNames, name => name.EndsWith(".Samples", StringComparison.Ordinal)); + Assert.DoesNotContain(authoring.runtimeAssemblyPaths, path => Path.GetFileNameWithoutExtension(path).EndsWith(".Samples", StringComparison.Ordinal)); ScriptCompilationResult player = fixture.CompileRuntimeDeployment(); Assert.True(player.success, FormatDiagnostics(player)); - Assert.DoesNotContain(player.compiledAssemblyNames, - name => name.EndsWith(".Samples", StringComparison.Ordinal)); - + Assert.DoesNotContain(player.compiledAssemblyNames, name => name.EndsWith(".Samples", StringComparison.Ordinal)); using AssetSampleImportTransaction import = PrepareSample(fixture, source); import.BeginValidation(async ( sources, cancellationToken ) => { - ScriptCompilationResult candidate = await fixture.compiler.CompileAuthoringGenerationAsync( - cancellationToken: cancellationToken, sourceSnapshot: sources); + ScriptCompilationResult candidate = await fixture.compiler.CompileAuthoringGenerationAsync(cancellationToken: cancellationToken, sourceSnapshot: sources); Assert.True(candidate.success, FormatDiagnostics(candidate)); }); Assert.True(SpinWait.SpinUntil(() => import.isValidationComplete, 60000)); import.Commit(); AssetPath imported = import.target; - Assert.Equal(AssetPath.Project("~Starter"), imported); - Assert.True(File.Exists(Path.Combine( - fixture.projectRoot, - "Assets", - "~Starter", - "StarterBehavior.cs"))); - Assert.DoesNotContain("ce3b52c6-2a07-42ea-b632-a307a0ef7407", + Assert.True(File.Exists(Path.Combine(fixture.projectRoot, "Assets", "~Starter", "StarterBehavior.cs"))); + Assert.DoesNotContain( + "ce3b52c6-2a07-42ea-b632-a307a0ef7407", File.ReadAllText(Path.Combine(fixture.projectRoot, "Assets", "~Starter", "StarterBehavior.cs")), - StringComparison.OrdinalIgnoreCase); - Assert.True(fixture.assets.TryGetInfo( - AssetPath.Project("~Starter/StarterBehavior.cs"), - out AssetInfo? importedInfo)); + StringComparison.OrdinalIgnoreCase + ); + Assert.True(fixture.assets.TryGetInfo(AssetPath.Project("~Starter/StarterBehavior.cs"), out AssetInfo? importedInfo)); Assert.NotEqual(sourcePersistentId, Assert.IsType(importedInfo).persistentId); Assert.True(fixture.assets.TryGetFileSystemEntry(imported, out AssetFileEntry importedDirectory)); Assert.False(importedDirectory.isReadOnly); @@ -856,8 +769,7 @@ public void InstalledPluginSampleImportsAsAnImmediatelyCompilableWritableCopy() ScriptCompilationResult compilation = fixture.Compile(); Assert.True(compilation.success, FormatDiagnostics(compilation)); fixture.compiler.GenerateProjectFiles(); - string gameProject = File.ReadAllText( - Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj")); + string gameProject = File.ReadAllText(Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj")); Assert.Contains("Compile Include=\"Assets/~Starter/StarterBehavior.cs\"", gameProject); } @@ -867,8 +779,7 @@ public void InvalidSampleScriptPreflightLeavesNoProjectCopy() using var fixture = new ScriptingFixture(( root, serialization - ) => - WriteSamplePlugin(root, serialization, "public sealed class BrokenSample { this is invalid; }")); + ) => WriteSamplePlugin(root, serialization, "public sealed class BrokenSample { this is invalid; }")); AssetPath source = new(new AssetSourceId("tests.samples"), "~Starter"); using AssetSampleImportTransaction import = PrepareSample(fixture, source); import.BeginValidation(async ( @@ -876,8 +787,7 @@ public void InvalidSampleScriptPreflightLeavesNoProjectCopy() cancellationToken ) => { - ScriptCompilationResult candidate = await fixture.compiler.CompileAuthoringGenerationAsync( - cancellationToken: cancellationToken, sourceSnapshot: sources); + ScriptCompilationResult candidate = await fixture.compiler.CompileAuthoringGenerationAsync(cancellationToken: cancellationToken, sourceSnapshot: sources); if (!candidate.success) throw new InvalidOperationException(FormatDiagnostics(candidate)); }); @@ -885,8 +795,7 @@ public void InvalidSampleScriptPreflightLeavesNoProjectCopy() Assert.Throws(() => import.Commit()); new Inno.Core.Execution.RetirementBarrier("Test sample import").Wait(import.Rollback); Assert.False(Directory.Exists(Path.Combine(fixture.projectRoot, "Assets", "~Starter"))); - Assert.False(fixture.assets.TryGetFileSystemEntry( - AssetPath.Project("~Starter"), out _)); + Assert.False(fixture.assets.TryGetFileSystemEntry(AssetPath.Project("~Starter"), out _)); } [Fact] @@ -898,8 +807,7 @@ public void SampleImportPreservesEveryLeadingTildeAndRejectsDestinationCollision Assert.False(AssetSample.Contains(AssetPath.Project("~~~Starter/File.cs"), false)); using var fixture = new ScriptingFixture(WriteSamplePlugin); fixture.assets.CreateDirectory(AssetPath.Project("~Starter")); - Assert.Throws(() => fixture.assets.PrepareSampleImport( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter"))); + Assert.Throws(() => fixture.assets.PrepareSampleImport(new AssetPath(new AssetSourceId("tests.samples"), "~Starter"))); fixture.host.modules.generations.EnsureReady("retry after rejected import"); } @@ -909,8 +817,7 @@ public void SampleValidationReturnsToOwnerWhileWorkIsPendingAndPublishesExactlyO using var fixture = new ScriptingFixture(WriteSamplePlugin); int notifications = 0; fixture.assets.Changed += _ => notifications++; - using AssetSampleImportTransaction import = PrepareSample( - fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = PrepareSample(fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); long revision = fixture.assets.revision; Assert.False(fixture.assets.TryGetFileSystemEntry(import.target, out _)); var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -925,6 +832,7 @@ public void SampleValidationReturnsToOwnerWhileWorkIsPendingAndPublishesExactlyO Assert.Equal(0, notifications); Assert.Equal(revision, fixture.assets.revision); } + Assert.Throws(() => import.Commit()); Assert.Throws(() => fixture.assets.CreateDirectory(AssetPath.Project("Concurrent"))); Assert.Throws(() => fixture.host.modules.generations.EnsureReady("reload while importing")); @@ -943,8 +851,7 @@ public void SampleCancellationRetainsDependenciesUntilUncooperativeValidationDra using var fixture = new ScriptingFixture(WriteSamplePlugin); int notifications = 0; fixture.assets.Changed += _ => notifications++; - using AssetSampleImportTransaction import = PrepareSample( - fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = PrepareSample(fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); import.BeginValidation(( _, @@ -970,8 +877,7 @@ public void SampleHistoryFinalizationFailureRollsBackWithoutPublication() using var fixture = new ScriptingFixture(WriteSamplePlugin); int notifications = 0; fixture.assets.Changed += _ => notifications++; - using AssetSampleImportTransaction import = PrepareSample( - fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = PrepareSample(fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); import.BeginValidation(( _, _ @@ -988,13 +894,19 @@ public void SampleHistoryFinalizationFailureRollsBackWithoutPublication() public void SampleCopyCancellationLeavesNoProjectOrTransactionDirectory() { using var fixture = new ScriptingFixture(WriteSamplePlugin); - using AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport(new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); import.Cancel(); Assert.True(SpinWait.SpinUntil(() => { - try { import.Rollback(); return true; } - catch (Inno.Core.Execution.RetirementPendingException) { return false; } + try + { + import.Rollback(); + return true; + } + catch (Inno.Core.Execution.RetirementPendingException) + { + return false; + } }, 15000)); Assert.False(Directory.Exists(Path.Combine(fixture.projectRoot, "Assets", "~Starter"))); string transactions = Path.Combine(fixture.assets.libraryRoot, "AssetDatabase", "Transactions"); @@ -1008,16 +920,22 @@ public void PendingAutomaticCompilationDefersUntilSampleRollback() using var fixture = new ScriptingFixture(WriteSamplePlugin); using ScriptReloadHost reload = fixture.CreateReloadHost(autoCompile: true); reload.Start(); - using AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport(new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); Assert.False(reload.TryCompilePending(out Task? pending)); Assert.Null(pending); Assert.True(reload.isCompilationPending); import.Cancel(); Assert.True(SpinWait.SpinUntil(() => { - try { import.Rollback(); return true; } - catch (Inno.Core.Execution.RetirementPendingException) { return false; } + try + { + import.Rollback(); + return true; + } + catch (Inno.Core.Execution.RetirementPendingException) + { + return false; + } }, 15000)); Assert.True(reload.TryCompilePending(out Task? compilation)); Assert.True(SpinWait.SpinUntil(() => compilation!.IsCompleted, 15000)); @@ -1033,8 +951,7 @@ public void EditorSampleImportUsesSharedModalAndUndoRedo() runtime.Start(); IEditorScriptCompilation scripting = Assert.IsAssignableFrom(ScriptingCompilationProbe.compilation); PumpEditorUntil(fixture, runtime, () => scripting.state == EditorScriptCompilationState.Ready, () => scripting.status); - Assert.True(fixture.assets.TryGetFileSystemEntry( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter"), out AssetFileEntry sample)); + Assert.True(fixture.assets.TryGetFileSystemEntry(new AssetPath(new AssetSourceId("tests.samples"), "~Starter"), out AssetFileEntry sample)); Assert.True(runtime.interactions.For("panel/asset.file-browser", sample).Execute("file-browser/import-sample")); EditorModalExtension modal = runtime.modals.Single(value => value.id == "asset-sample-import.progress"); Assert.True(modal.TryGetPresentation(out EditorModalExtension.Presentation presentation)); @@ -1044,9 +961,7 @@ public void EditorSampleImportUsesSharedModalAndUndoRedo() EditorModalExtension compilationModal = runtime.modals.Single(value => value.id == "scripting.compilation"); Assert.True(compilationModal.TryGetPresentation(out EditorModalExtension.Presentation compilationPolicy)); Assert.Equal(compilationPolicy.allowScrolling, presentation.allowScrolling); - PumpEditorUntil(fixture, runtime, - () => modal.TryGetPresentation(out EditorModalExtension.Presentation state) && !state.isVisible, - () => scripting.status); + PumpEditorUntil(fixture, runtime, () => modal.TryGetPresentation(out EditorModalExtension.Presentation state) && !state.isVisible, () => scripting.status); Assert.True(fixture.assets.TryGetFileSystemEntry(AssetPath.Project("~Starter"), out _)); Assert.True(runtime.interactions.history.Undo().succeeded); Assert.False(fixture.assets.TryGetFileSystemEntry(AssetPath.Project("~Starter"), out _)); @@ -1066,8 +981,7 @@ public void SampleTransformRunsOnWorkerAndEditorStopRetainsPendingImport() ControlledSampleSourceRewriter.current = control; try { - Assert.True(fixture.assets.TryGetFileSystemEntry( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter"), out AssetFileEntry sample)); + Assert.True(fixture.assets.TryGetFileSystemEntry(new AssetPath(new AssetSourceId("tests.samples"), "~Starter"), out AssetFileEntry sample)); Assert.True(runtime.interactions.For("panel/asset.file-browser", sample).Execute("file-browser/import-sample")); Assert.True(control.started.Wait(TimeSpan.FromSeconds(5))); Assert.NotEqual(Environment.CurrentManagedThreadId, control.workerThread); @@ -1097,10 +1011,18 @@ public void SampleTransformRunsOnWorkerAndEditorStopRetainsPendingImport() ControlledSampleSourceRewriter.current = null; Assert.True(SpinWait.SpinUntil(() => { - try { runtime.Dispose(); return true; } - catch (Inno.Core.Execution.RetirementPendingException) { return false; } + try + { + runtime.Dispose(); + return true; + } + catch (Inno.Core.Execution.RetirementPendingException) + { + return false; + } }, 15000)); } + Assert.False(Directory.Exists(Path.Combine(fixture.projectRoot, "Assets", "~Starter"))); fixture.host.modules.generations.Wait(); fixture.host.modules.generations.EnsureReady("continue after editor sample retirement"); @@ -1119,23 +1041,20 @@ public void SampleDirectoryMetadataRemapsNestedImportSettingsIdentities() string package = Path.Combine(root, "Plugins", "samples.iplugin"); using var archive = ZipFile.Open(package, ZipArchiveMode.Update); archive.GetEntry("Assets/~Starter.imeta")!.Delete(); - WritePluginPackageEntry(archive, "Assets/~Starter.imeta", serialization.Serialize(new ScriptingAssetSourceMeta - { - persistentId = Guid.Parse("7726b1d2-9aee-4d2c-a865-2fd53155095f"), - sourceKind = (int)AssetSourceKind.Directory, - importerSettingsBytes = serialization.Serialize(new SampleIdentitySettings { referenceId = sourceId }) - })); + WritePluginPackageEntry( + archive, + "Assets/~Starter.imeta", + serialization.Serialize(new ScriptingAssetSourceMeta { persistentId = Guid.Parse("7726b1d2-9aee-4d2c-a865-2fd53155095f"), sourceKind = (int)AssetSourceKind.Directory, importerSettingsBytes = serialization.Serialize(new SampleIdentitySettings { referenceId = sourceId }) }) + ); }); - using AssetSampleImportTransaction import = PrepareSample( - fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = PrepareSample(fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); import.BeginValidation(( sources, _ ) => { Assert.True(sources.TryGetInfo(AssetPath.Project("~Starter/StarterBehavior.cs"), out AssetInfo? info)); - ScriptingAssetSourceMeta metadata = fixture.host.serialization.Deserialize( - File.ReadAllBytes(Path.Combine(fixture.projectRoot, "Assets", "~Starter.imeta"))); + ScriptingAssetSourceMeta metadata = fixture.host.serialization.Deserialize(File.ReadAllBytes(Path.Combine(fixture.projectRoot, "Assets", "~Starter.imeta"))); SampleIdentitySettings settings = fixture.host.serialization.Deserialize(metadata.importerSettingsBytes); Assert.Equal(info!.persistentId, settings.referenceId); Assert.NotEqual(sourceId, settings.referenceId); @@ -1156,17 +1075,15 @@ public void SampleAssetPreimportRunsOnWorkerAndReceivesCancellation() WriteSamplePlugin(root, serialization); using var package = ZipFile.Open(Path.Combine(root, "Plugins", "samples.iplugin"), ZipArchiveMode.Update); WritePluginPackageEntry(package, "Assets/~Starter/Slow.samplebusy", System.Text.Encoding.UTF8.GetBytes("sample")); - WritePluginPackageEntry(package, "Assets/~Starter/Slow.samplebusy.imeta", serialization.Serialize(new ScriptingAssetSourceMeta - { - persistentId = Guid.Parse("587849fc-777c-4501-a1b2-ec042ca40314"), - sourceKind = (int)AssetSourceKind.File, - importerId = "tests.scripting.controlled-sample-asset" - })); + WritePluginPackageEntry( + package, + "Assets/~Starter/Slow.samplebusy.imeta", + serialization.Serialize(new ScriptingAssetSourceMeta { persistentId = Guid.Parse("587849fc-777c-4501-a1b2-ec042ca40314"), sourceKind = (int)AssetSourceKind.File, importerId = "tests.scripting.controlled-sample-asset" }) + ); }); using var control = new ControlledSampleAssetImporter.Control(); ControlledSampleAssetImporter.current = control; - AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport(new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); try { Assert.True(SpinWait.SpinUntil(() => @@ -1183,6 +1100,7 @@ public void SampleAssetPreimportRunsOnWorkerAndReceivesCancellation() Assert.False(fixture.assets.TryGetFileSystemEntry(import.target, out _)); Assert.Equal(revision, fixture.assets.revision); } + import.Cancel(); Assert.True(control.canceled.Wait(TimeSpan.FromSeconds(5))); } @@ -1191,6 +1109,7 @@ public void SampleAssetPreimportRunsOnWorkerAndReceivesCancellation() new Inno.Core.Execution.RetirementBarrier("Test sample preimport").Wait(import.Dispose); ControlledSampleAssetImporter.current = null; } + Assert.False(Directory.Exists(Path.Combine(fixture.projectRoot, "Assets", "~Starter"))); Assert.False(fixture.assets.TryGetFileSystemEntry(import.target, out _)); fixture.host.modules.generations.EnsureReady("continue after asset preimport cancellation"); @@ -1200,8 +1119,7 @@ public void SampleAssetPreimportRunsOnWorkerAndReceivesCancellation() public void SampleCompilerReferencePreparationRunsOnWorker() { using var fixture = new ScriptingFixture(WriteSamplePlugin); - using AssetSampleImportTransaction import = PrepareSample( - fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using AssetSampleImportTransaction import = PrepareSample(fixture, new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); using var progress = new CompilationGate(); try { @@ -1210,8 +1128,7 @@ public void SampleCompilerReferencePreparationRunsOnWorker() cancellationToken ) => { - ScriptCompilationResult result = await fixture.compiler.CompileAuthoringGenerationAsync( - progress, cancellationToken, sources).ConfigureAwait(false); + ScriptCompilationResult result = await fixture.compiler.CompileAuthoringGenerationAsync(progress, cancellationToken, sources).ConfigureAwait(false); Assert.True(result.success, FormatDiagnostics(result)); }); Assert.True(progress.started.Wait(TimeSpan.FromSeconds(5))); @@ -1228,6 +1145,7 @@ public void SampleCompilerReferencePreparationRunsOnWorker() progress.release.Set(); Assert.True(SpinWait.SpinUntil(() => import.isValidationComplete, 60000)); } + import.Commit(); Assert.True(fixture.assets.TryGetFileSystemEntry(import.target, out _)); } @@ -1235,7 +1153,7 @@ public void SampleCompilerReferencePreparationRunsOnWorker() /// /// Verifies nonblocking watcher suspension and reconciliation after successful or canceled imports. /// - /// + /// /// Whether the candidate is committed rather than rolled back. /// [Theory] @@ -1243,10 +1161,8 @@ public void SampleCompilerReferencePreparationRunsOnWorker() [InlineData(true)] public void SampleIndexingDoesNotWaitForWatcherQuietPeriod(bool commit) { - using var fixture = new ScriptingFixture( - WriteSamplePlugin, enableFileSystemWatcher: true, fileWatcherFlushDelayMs: 3000); - AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport( - new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); + using var fixture = new ScriptingFixture(WriteSamplePlugin, enableFileSystemWatcher: true, fileWatcherFlushDelayMs: 3000); + AssetSampleImportTransaction import = fixture.assets.PrepareSampleImport(new AssetPath(new AssetSourceId("tests.samples"), "~Starter")); try { TimeSpan maximumAdvance = TimeSpan.Zero; @@ -1259,8 +1175,7 @@ public void SampleIndexingDoesNotWaitForWatcherQuietPeriod(bool commit) return ready; }, 15000); Assert.True(indexed); - Assert.True(maximumAdvance < TimeSpan.FromSeconds(2), - $"Owner-thread Advance waited {maximumAdvance} with a three-second watcher quiet period."); + Assert.True(maximumAdvance < TimeSpan.FromSeconds(2), $"Owner-thread Advance waited {maximumAdvance} with a three-second watcher quiet period."); fixture.Write("External.txt", "Changed while watching was paused."); Assert.False(fixture.assets.TryGetFileSystemEntry(AssetPath.Project("External.txt"), out _)); if (commit) @@ -1307,18 +1222,14 @@ public sealed class UsesProjectionPlugin """); string staleProject = Path.Combine(fixture.projectRoot, "Inno.Plugin.Stale.csproj"); File.WriteAllText(staleProject, "stale"); - ScriptCompilationResult result = fixture.Compile(); fixture.compiler.GenerateProjectFiles(); - Assert.True(result.success, FormatDiagnostics(result)); Assert.False(File.Exists(staleProject)); - Assert.False(File.Exists(Path.Combine( - fixture.projectRoot, "Inno.Plugin.TestsProjection.csproj"))); + Assert.False(File.Exists(Path.Combine(fixture.projectRoot, "Inno.Plugin.TestsProjection.csproj"))); string solution = File.ReadAllText(Path.Combine(fixture.projectRoot, "InnoProject.sln")); Assert.DoesNotContain("Inno.Plugin.TestsProjection", solution, StringComparison.Ordinal); - string gameProject = File.ReadAllText( - Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj")); + string gameProject = File.ReadAllText(Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj")); Assert.Contains("Inno.Plugin.TestsProjection.dll", gameProject, StringComparison.Ordinal); Assert.DoesNotContain("ProjectReference Include=\"Inno.Plugin.TestsProjection.csproj\"", gameProject); } @@ -1326,62 +1237,49 @@ public sealed class UsesProjectionPlugin [Fact] public void SourceArtifactsAreCatalogedButRuntimeExportContainsNoSourceFiles() { - m_fixture.Write( - "Scripts/Tracked.cs", - "using InnoEngine.Scene; public sealed class Tracked : GameBehavior { }"); - + m_fixture.Write("Scripts/Tracked.cs", "using InnoEngine.Scene; public sealed class Tracked : GameBehavior { }"); ScriptCompilationResult result = m_fixture.Compile(); string contentRoot = Path.Combine(m_fixture.projectRoot, "RuntimeContent"); AssetRuntimeContentInfo exported = m_fixture.assets.ExportRuntimeArtifacts(contentRoot); - Assert.True(result.success, FormatDiagnostics(result)); - Assert.True(m_fixture.assets.TryGetInfo( - AssetPath.Project("Scripts/Tracked.cs"), - out AssetInfo? info)); + Assert.True(m_fixture.assets.TryGetInfo(AssetPath.Project("Scripts/Tracked.cs"), out AssetInfo? info)); Assert.NotNull(info); Assert.Equal("inno.editor.csharp-script", info.importerId); - Assert.True(m_fixture.assets.TryGetArtifact( - info.persistentId, - "source", - out AssetArtifactInfo? source)); + Assert.True(m_fixture.assets.TryGetArtifact(info.persistentId, "source", out AssetArtifactInfo? source)); Assert.NotNull(source); using (ArtifactLease encoded = m_fixture.assets.AcquireArtifact(info.persistentId, "source")) Assert.NotEmpty(encoded.ReadAllBytes()); Assert.Equal(0, exported.assetCount); - Assert.DoesNotContain( - Directory.EnumerateFiles(contentRoot, "*", SearchOption.AllDirectories), - static path => path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(Directory.EnumerateFiles(contentRoot, "*", SearchOption.AllDirectories), static path => path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)); } [Fact] + [Trait("Acceptance", "Lifecycle")] public void SuccessfulReloadActivatesCandidateAndFailedCompilationRetainsIt() { m_fixture.WriteVersionedBehavior(1); using ScriptReloadHost reload = m_fixture.CreateReloadHost(); reload.Start(); - ScriptCompilationResult firstResult = m_fixture.CompilePending(reload); Assert.True(firstResult.success, FormatDiagnostics(firstResult)); Assert.True(reload.ApplyPendingReload()); - WeakReference first = CaptureActiveType(m_fixture, "VersionedBehavior", expectedVersion: 1); - - m_fixture.WriteVersionedBehavior(2); - ScriptCompilationResult secondResult = m_fixture.CompilePending(reload); - Assert.True(secondResult.success, FormatDiagnostics(secondResult)); - Assert.True(reload.ApplyPendingReload()); - CompleteUnloadVerification(reload); - Assert.False(first.IsAlive, "The first script generation remained reachable after its reload barrier."); - Type second = m_fixture.ResolveActiveType("VersionedBehavior"); - Assert.Equal(2, ReadVersion(second)); - - m_fixture.Write( - "VersionedBehavior.cs", - "using InnoEngine.Scene; public sealed class VersionedBehavior : GameBehavior {"); - ScriptCompilationResult failed = m_fixture.CompilePending(reload); - - Assert.False(failed.success); - Assert.False(reload.ApplyPendingReload()); - Assert.Same(second, m_fixture.ResolveActiveType("VersionedBehavior")); + WeakReference active = CaptureActiveType(m_fixture, "VersionedBehavior", expectedVersion: 1); + for (int version = 2; version <= 11; version++) + { + m_fixture.Write("VersionedBehavior.cs", "using InnoEngine.Scene; public sealed class VersionedBehavior : GameBehavior {"); + ScriptCompilationResult failed = m_fixture.CompilePending(reload); + Assert.False(failed.success); + Assert.False(reload.ApplyPendingReload()); + AssertRetainedActiveType(m_fixture, active, version - 1); + + m_fixture.WriteVersionedBehavior(version); + ScriptCompilationResult replacement = m_fixture.CompilePending(reload); + Assert.True(replacement.success, FormatDiagnostics(replacement)); + Assert.True(reload.ApplyPendingReload()); + CompleteUnloadVerification(reload); + Assert.False(active.IsAlive, "The previous script generation remained reachable after its reload barrier."); + active = CaptureActiveType(m_fixture, "VersionedBehavior", version); + } } [Fact] @@ -1398,7 +1296,6 @@ public sealed class ProjectPresentation : GenericPresentation Assert.True(reload.ApplyPendingReload()); WeakReference runtime = CaptureActiveType(m_fixture, "VersionedBehavior", 1); WeakReference editor = CaptureActiveType(m_fixture, "ProjectPresentation", 1); - m_fixture.Write("Editor/GenericPresentation.editor.cs", """ public class GenericPresentation { public int version => 2; } public sealed class ProjectPresentation : GenericPresentation { } @@ -1432,7 +1329,6 @@ public sealed class ReloadableBehavior : GameBehavior ScriptCompilationResult firstResult = m_fixture.CompilePending(reload); Assert.True(firstResult.success, FormatDiagnostics(firstResult)); Assert.True(reload.ApplyPendingReload()); - using EditorInteractionRuntime runtime = m_fixture.CreateEditorRuntime(reloads); runtime.Start(); _ = Assert.IsAssignableFrom(SceneReloadProbe.workspace); @@ -1440,7 +1336,6 @@ public sealed class ReloadableBehavior : GameBehavior GameScene scene = m_fixture.editorSession.scenes.LoadNewSceneAdditive("Reload Test"); GameObject gameObject = scene.CreateObject("Reload Target"); RetiredScriptObject retired = AttachReloadableBehavior(m_fixture, gameObject); - ForceFullCollection(); m_fixture.Write("ReloadableBehavior.cs", """ using InnoEngine.Scene; @@ -1457,12 +1352,9 @@ public sealed class ReloadableBehavior : GameBehavior """); ScriptCompilationResult secondResult = m_fixture.CompilePending(reload); Assert.True(secondResult.success, FormatDiagnostics(secondResult)); - Assert.True(reload.ApplyPendingReload()); - Type currentType = m_fixture.ResolveActiveType("ReloadableBehavior"); - GameComponent current = Assert.Single(gameObject.GetComponents().Where(component => - string.Equals(component.GetType().Name, "ReloadableBehavior", StringComparison.Ordinal))); + GameComponent current = Assert.Single(gameObject.GetComponents().Where(component => string.Equals(component.GetType().Name, "ReloadableBehavior", StringComparison.Ordinal))); Assert.Same(currentType, current.GetType()); Assert.Equal(41, currentType.GetProperty("retained")!.GetValue(current)); Assert.Equal(2, currentType.GetProperty("added")!.GetValue(current)); @@ -1474,8 +1366,8 @@ public sealed class ReloadableBehavior : GameBehavior [Theory] [InlineData(false)] [InlineData(true)] - public void RemovedOrBrokenUpdatedPluginCommitsUnavailableGenerationAndRecovers( - bool updateInPlace) + [Trait("Acceptance", "Lifecycle")] + public void RemovedOrBrokenUpdatedPluginCommitsUnavailableGenerationAndRecovers(bool updateInPlace) { using var fixture = new ScriptingFixture(WriteUnavailableGenerationPlugin); fixture.Write("DependentBehavior.cs", """ @@ -1499,62 +1391,52 @@ public sealed class DependentBehavior : GameBehavior ScriptCompilationResult initial = fixture.CompilePending(reload); Assert.True(initial.success, FormatDiagnostics(initial)); Assert.True(reload.ApplyPendingReload()); - RuntimeSession session = fixture.CreateEditorSession(); using IDisposable executionScope = session.EnterExecutionScope(); - var sceneReload = new SceneReloadService( - session.scenes, - fixture.host.serialization, - fixture.assets); + var sceneReload = new SceneReloadService(session.scenes, fixture.host.serialization, fixture.assets); using IDisposable registration = reloads.Register(new TestSceneReloadParticipant(sceneReload)); GameScene scene = session.scenes.LoadNewScene("Plugin Availability"); AvailabilitySceneState state = CreateAvailabilitySceneState(fixture, scene); - string installedPlugin = Path.Combine(fixture.projectRoot, "Plugins", "unavailability.iplugin"); string detachedPlugin = Path.Combine(fixture.projectRoot, "UnavailabilityPlugin.detached"); byte[] validPluginPackage = File.ReadAllBytes(installedPlugin); - if (updateInPlace) + for (int cycle = 0; cycle < 10; cycle++) { - WriteUnavailableGenerationPlugin( - fixture.projectRoot, - fixture.host.serialization, - "this Plugin update is deliberately invalid C#"); - } - else - { - File.Move(installedPlugin, detachedPlugin); - } - Assert.True(fixture.RefreshPlugins()); - - ScriptCompilationResult unavailable = fixture.CompilePendingPluginReload(reload); - Assert.False(unavailable.success); - Assert.Contains(unavailable.diagnostics, static diagnostic => - diagnostic.severity == DiagnosticSeverity.Error); - Assert.True(reload.ApplyPendingReload()); - Assert.DoesNotContain(fixture.host.modules.modules, static module => - module.domain is AssemblyDomain.InnoPlugin or AssemblyDomain.InnoScripting); - Assert.Equal(updateInPlace ? 1 : 0, fixture.activePlugins.Count); - - MissingGameComponent missingPlugin = Assert.IsType( - state.pluginOwner.GetComponents().Single(component => component is not Transform)); - MissingGameComponent missingScript = Assert.IsType( - state.scriptOwner.GetComponents().Single(component => component is not Transform)); - Assert.Equal(state.pluginComponentId, missingPlugin.identity.persistentId); - Assert.Equal(state.scriptComponentId, missingScript.identity.persistentId); - Assert.Equal("UnavailabilityPlugin.PluginBehavior", missingPlugin.missingTypeName); - Assert.Equal("DependentBehavior", missingScript.missingTypeName); - CompleteUnloadVerification(reload); - - if (updateInPlace) - File.WriteAllBytes(installedPlugin, validPluginPackage); - else - File.Move(detachedPlugin, installedPlugin); - Assert.True(fixture.RefreshPlugins()); - ScriptCompilationResult recoveredCompilation = fixture.CompilePendingPluginReload(reload); - Assert.True(recoveredCompilation.success, FormatDiagnostics(recoveredCompilation)); - Assert.True(reload.ApplyPendingReload()); + if (updateInPlace) + { + WriteUnavailableGenerationPlugin(fixture.projectRoot, fixture.host.serialization, "this Plugin update is deliberately invalid C#"); + } + else + { + File.Move(installedPlugin, detachedPlugin); + } - AssertRecoveredAvailability(state); + Assert.True(fixture.RefreshPlugins()); + ScriptCompilationResult unavailable = fixture.CompilePendingPluginReload(reload); + Assert.False(unavailable.success); + Assert.Contains(unavailable.diagnostics, static diagnostic => diagnostic.severity == DiagnosticSeverity.Error); + Assert.True(reload.ApplyPendingReload()); + Assert.DoesNotContain(fixture.host.modules.modules, static module => module.domain is AssemblyDomain.InnoPlugin or AssemblyDomain.InnoScripting); + Assert.Equal(updateInPlace ? 1 : 0, fixture.activePlugins.Count); + MissingGameComponent missingPlugin = Assert.IsType(state.pluginOwner.GetComponents().Single(component => component is not Transform)); + MissingGameComponent missingScript = Assert.IsType(state.scriptOwner.GetComponents().Single(component => component is not Transform)); + Assert.Equal(state.pluginComponentId, missingPlugin.identity.persistentId); + Assert.Equal(state.scriptComponentId, missingScript.identity.persistentId); + Assert.Equal("UnavailabilityPlugin.PluginBehavior", missingPlugin.missingTypeName); + Assert.Equal("DependentBehavior", missingScript.missingTypeName); + CompleteUnloadVerification(reload); + if (updateInPlace) + File.WriteAllBytes(installedPlugin, validPluginPackage); + else + File.Move(detachedPlugin, installedPlugin); + Assert.True(fixture.RefreshPlugins()); + ScriptCompilationResult recoveredCompilation = fixture.CompilePendingPluginReload(reload); + Assert.True(recoveredCompilation.success, FormatDiagnostics(recoveredCompilation)); + Assert.True(reload.ApplyPendingReload()); + AssertRecoveredAvailability(state); + CompleteUnloadVerification(reload); + Assert.Equal(GenerationState.Ready, fixture.host.modules.generations.state); + } } [MethodImpl(MethodImplOptions.NoInlining)] @@ -1581,11 +1463,13 @@ public void NeutralSettingsCacheDoesNotPinRetiredScriptTypesWithoutAnExplicitReb Assert.True(initial.success, FormatDiagnostics(initial)); Assert.True(reload.ApplyPendingReload()); using var detachedSettings = new ProjectSettingsStore( - new FileByteDocumentStore(Path.GetFullPath(Path.Combine(fixture.projectRoot, "DetachedSettings.inno"))), fixture.host.types, - fixture.host.serialization, new ProjectId("tests.detached.settings"), - AssetSerializationContext.Create(fixture.assets)); + new FileByteDocumentStore(Path.GetFullPath(Path.Combine(fixture.projectRoot, "DetachedSettings.inno"))), + fixture.host.types, + fixture.host.serialization, + new ProjectId("tests.detached.settings"), + AssetSerializationContext.Create(fixture.assets) + ); WeakReference previousType = CaptureCachedSettingType(detachedSettings); - WriteSetting(21); ScriptCompilationResult replacement = fixture.CompilePending(reload); Assert.True(replacement.success, FormatDiagnostics(replacement)); @@ -1593,7 +1477,6 @@ public void NeutralSettingsCacheDoesNotPinRetiredScriptTypesWithoutAnExplicitReb CompleteUnloadVerification(reload); Assert.False(previousType.IsAlive, "A neutral settings cache must not retain the old script type."); Assert.True(CaptureCachedSettingType(detachedSettings).IsAlive); - void WriteSetting(int value) { fixture.Write("DetachedSetting.cs", $$""" @@ -1628,12 +1511,10 @@ public void AuthoringRuntimeSessionDoesNotPinRetiredPluginAndScriptContexts() ScriptCompilationResult initial = fixture.CompilePending(reload); Assert.True(initial.success, FormatDiagnostics(initial)); Assert.True(reload.ApplyPendingReload()); - _ = fixture.CreateEditorSession(); ScriptCompilationResult replacement = fixture.CompilePendingPluginReload(reload); Assert.True(replacement.success, FormatDiagnostics(replacement)); Assert.True(reload.ApplyPendingReload()); - CompleteUnloadVerification(reload); } @@ -1647,23 +1528,16 @@ public void PluginRemovalUnloadsTheCommittedMissingGenerationWithoutASecondReloa ScriptCompilationResult initial = fixture.CompilePending(reload); Assert.True(initial.success, FormatDiagnostics(initial)); Assert.True(reload.ApplyPendingReload()); - RuntimeSession session = fixture.CreateEditorSession(); using IDisposable executionScope = session.EnterExecutionScope(); - var sceneReload = new SceneReloadService( - session.scenes, - fixture.host.serialization, - fixture.assets); + var sceneReload = new SceneReloadService(session.scenes, fixture.host.serialization, fixture.assets); using IDisposable registration = reloads.Register(new TestSceneReloadParticipant(sceneReload)); MissingGenerationExpectation expectation = CommitPluginRemoval(fixture, reload, session); - CompleteUnloadVerification(reload); Assert.False(expectation.retiredComponent.IsAlive, "The retired Plugin component remained strongly reachable."); Assert.False(expectation.retiredType.IsAlive, "The retired Plugin runtime Type remained strongly reachable."); - Assert.DoesNotContain(fixture.host.modules.modules, static module => - module.domain == AssemblyDomain.InnoPlugin); - MissingGameComponent missing = Assert.IsType( - expectation.owner.GetComponents().Single(component => component is not Transform)); + Assert.DoesNotContain(fixture.host.modules.modules, static module => module.domain == AssemblyDomain.InnoPlugin); + MissingGameComponent missing = Assert.IsType(expectation.owner.GetComponents().Single(component => component is not Transform)); Assert.Equal(expectation.componentId, missing.identity.persistentId); Assert.Equal("UnavailabilityPlugin.PluginBehavior", missing.missingTypeName); } @@ -1678,33 +1552,28 @@ public void PluginReloadWaitsForPlaySessionRetirementBeforeActivatingItsAssembly ScriptCompilationResult initial = fixture.CompilePending(reload); Assert.True(initial.success, FormatDiagnostics(initial)); Assert.True(reload.ApplyPendingReload()); - CompleteUnloadVerification(reload); var scenes = new PluginPlayScene(fixture); var history = new CountingHistoryIsolation(); using var playMode = new EditorPlayModeController( fixture.host, - new RuntimeSessionOptions - { + new RuntimeSessionOptions { kind = RuntimeSessionKind.Play, applicationId = "tests.scripting.play-reload", - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine( - fixture.projectRoot, - "Persistent", - "tests.scripting.play-reload"), "Logs")), + createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(fixture.projectRoot, "Persistent", "tests.scripting.play-reload"), "Logs")), jobExecutionMode = RuntimeJobExecutionMode.SingleThread, referenceResolvers = [fixture.assets] }, scenes, new ReadyScriptCompilation(), history, - fixture.host.logs); + fixture.host.logs + ); using IDisposable registration = reloads.RegisterGuard(playMode); Assert.True(playMode.EnterPlayMode()); playMode.AdvanceTransition(); playMode.AdvanceTransition(); Assert.Equal(EditorPlayModeState.Playing, playMode.state); - reload.ReloadPlugins(); Assert.True(reload.isCompilationPending); Assert.False(reloads.CanRequestReload(out _)); @@ -1716,7 +1585,6 @@ public void PluginReloadWaitsForPlaySessionRetirementBeforeActivatingItsAssembly Assert.False(reloads.CanActivateReload(out _)); playMode.AdvanceTransition(); playMode.AdvanceTransition(); - Assert.Equal(EditorPlayModeState.Editing, playMode.state); Assert.Equal(1, scenes.restoreCount); Assert.Equal(1, history.disposeCount); @@ -1782,24 +1650,17 @@ protected override async ValueTask ImportAsync( """); fixture.Write("Content/value.shutdownasset", "last-good"); (Guid persistentId, Guid stableTypeId) = LoadScriptAssetAndUnloadScripts(fixture); - Assert.Empty(fixture.host.modules.modules); Assert.True(fixture.assets.TryGetInfo(persistentId, out AssetInfo? preserved)); Assert.Equal(AssetImportStatus.Imported, preserved!.status); Assert.Equal(stableTypeId, preserved.stableAssetTypeId); - AssetObject missing = ((IAssetReferenceResolver)fixture.assets).Resolve( - persistentId, - stableTypeId, - "Content/value.shutdownasset", - typeof(AssetObject), - "$test"); + AssetObject missing = ((IAssetReferenceResolver)fixture.assets).Resolve(persistentId, stableTypeId, "Content/value.shutdownasset", typeof(AssetObject), "$test"); Assert.True(missing.isMissing); Assert.Equal(persistentId, missing.identity.persistentId); } [MethodImpl(MethodImplOptions.NoInlining)] - private static (Guid persistentId, Guid stableTypeId) LoadScriptAssetAndUnloadScripts( - ScriptingFixture fixture) + private static (Guid persistentId, Guid stableTypeId) LoadScriptAssetAndUnloadScripts(ScriptingFixture fixture) { using ScriptReloadHost reload = fixture.CreateReloadHost(new EditorReloadCoordinator()); reload.Start(); @@ -1807,9 +1668,7 @@ private static (Guid persistentId, Guid stableTypeId) LoadScriptAssetAndUnloadSc Assert.True(compilation.success, FormatDiagnostics(compilation)); Assert.True(reload.ApplyPendingReload()); Type assetType = fixture.ResolveActiveType("ShutdownAsset"); - AssetObject loaded = fixture.assets.Load( - AssetPath.Project("Content/value.shutdownasset"), - assetType); + AssetObject loaded = fixture.assets.Load(AssetPath.Project("Content/value.shutdownasset"), assetType); Assert.True(fixture.assets.TryGetInfo(loaded.identity.persistentId, out AssetInfo? imported)); Assert.Equal(AssetImportStatus.Imported, imported!.status); Guid persistentId = loaded.identity.persistentId; @@ -1819,6 +1678,7 @@ private static (Guid persistentId, Guid stableTypeId) LoadScriptAssetAndUnloadSc } [Fact] + [Trait("Acceptance", "Lifecycle")] public void ContentPlayStopReloadPlaySoakRetiresEverySessionBeforeTheNextGeneration() { using var fixture = new ScriptingFixture(WriteUnavailableGenerationPlugin); @@ -1830,21 +1690,32 @@ public void ContentPlayStopReloadPlaySoakRetiresEverySessionBeforeTheNextGenerat CompleteUnloadVerification(reload); var scenes = new PluginPlayScene(fixture); var history = new CountingHistoryIsolation(); - using var play = new EditorPlayModeController(fixture.host, new RuntimeSessionOptions - { - kind = RuntimeSessionKind.Play, applicationId = "tests.content-soak", - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(fixture.projectRoot, "Persistent", "tests.content-soak"), "Logs")), - jobExecutionMode = RuntimeJobExecutionMode.SingleThread, referenceResolvers = [fixture.assets] - }, scenes, new ReadyScriptCompilation(), history, fixture.host.logs); + using var play = new EditorPlayModeController( + fixture.host, + new RuntimeSessionOptions { + kind = RuntimeSessionKind.Play, + applicationId = "tests.content-soak", + createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(fixture.projectRoot, "Persistent", "tests.content-soak"), "Logs")), + jobExecutionMode = RuntimeJobExecutionMode.SingleThread, + referenceResolvers = [fixture.assets] + }, + scenes, + new ReadyScriptCompilation(), + history, + fixture.host.logs + ); using IDisposable registration = reloads.RegisterGuard(play); - for (int iteration = 0; iteration < 8; iteration++) + for (int iteration = 0; iteration < 10; iteration++) { Assert.True(play.EnterPlayMode()); - play.AdvanceTransition(); play.AdvanceTransition(); + play.AdvanceTransition(); + play.AdvanceTransition(); Assert.True(play.state == EditorPlayModeState.Playing, play.lastFailure); - for (int frame = 0; frame < 60; frame++) play.Tick(1f / 60f); + for (int frame = 0; frame < 60; frame++) + play.Tick(1f / 60f); Assert.True(play.ExitPlayMode()); - play.AdvanceTransition(); play.AdvanceTransition(); + play.AdvanceTransition(); + play.AdvanceTransition(); Assert.Equal(EditorPlayModeState.Editing, play.state); Assert.True(fixture.CompilePendingPluginReload(reload).success); Assert.True(reload.ApplyPendingReload()); @@ -1853,8 +1724,10 @@ public void ContentPlayStopReloadPlaySoakRetiresEverySessionBeforeTheNextGenerat Assert.Equal(iteration + 1, scenes.restoreCount); Assert.Equal(iteration + 1, history.disposeCount); } + Assert.True(play.EnterPlayMode()); - play.AdvanceTransition(); play.AdvanceTransition(); + play.AdvanceTransition(); + play.AdvanceTransition(); Assert.Equal(EditorPlayModeState.Playing, play.state); } @@ -1866,12 +1739,10 @@ public void AutomaticPublicationDoesNotQueueItselfButExternalMountChangesStillDo reload.Start(); Assert.True(m_fixture.CompilePending(reload).success); Assert.True(reload.ApplyPendingReload()); - Assert.False(reload.isCompilationPending, - "Publishing this script generation must not queue another generation from its own catalog notification."); + Assert.False(reload.isCompilationPending, "Publishing this script generation must not queue another generation from its own catalog notification."); CompleteUnloadVerification(reload); m_fixture.assets.ReplaceSourceMounts(m_fixture.assets.sourceMounts); - Assert.True(reload.isCompilationPending, - "An external source-mount publication must still invalidate scripting inputs."); + Assert.True(reload.isCompilationPending, "An external source-mount publication must still invalidate scripting inputs."); } [Fact] @@ -1881,11 +1752,8 @@ public void EditorAutomaticCompilationClosesModalAndTicketWaitsForRetirement() m_fixture.Rescan(); using EditorInteractionRuntime runtime = m_fixture.CreateEditorRuntime(); runtime.Start(); - IEditorScriptCompilation scripting = Assert.IsAssignableFrom( - ScriptingCompilationProbe.compilation); - PumpEditorUntil(m_fixture, runtime, - () => scripting.state == EditorScriptCompilationState.Ready, - () => scripting.status); + IEditorScriptCompilation scripting = Assert.IsAssignableFrom(ScriptingCompilationProbe.compilation); + PumpEditorUntil(m_fixture, runtime, () => scripting.state == EditorScriptCompilationState.Ready, () => scripting.status); AssertCompilationModalHidden(runtime); ScriptCompilationResult initial = Assert.IsType(scripting.lastCompilation); for (int frame = 0; frame < 150; frame++) @@ -1895,24 +1763,20 @@ public void EditorAutomaticCompilationClosesModalAndTicketWaitsForRetirement() Assert.Same(initial, scripting.lastCompilation); Thread.Sleep(5); } + List retained = RetainActiveScriptType(m_fixture); try { m_fixture.WriteVersionedBehavior(2); m_fixture.Rescan(); IScriptCompilationTicket ticket = scripting.RequestCompilation(); - PumpEditorUntil(m_fixture, runtime, - () => m_fixture.host.modules.generations.state == GenerationState.AwaitingCollection, - () => scripting.status); + PumpEditorUntil(m_fixture, runtime, () => m_fixture.host.modules.generations.state == GenerationState.AwaitingCollection, () => scripting.status); Assert.Equal(ScriptCompilationTicketState.Compiling, ticket.state); Assert.False(ticket.isCompleted); retained.Clear(); // Publication readiness may be advanced by another host boundary before the next editor update. m_fixture.host.modules.generations.Wait(); - PumpEditorUntil(m_fixture, runtime, - () => scripting.state == EditorScriptCompilationState.Ready && ticket.isCompleted, - () => scripting.status, - focused: false); + PumpEditorUntil(m_fixture, runtime, () => scripting.state == EditorScriptCompilationState.Ready && ticket.isCompleted, () => scripting.status, focused: false); Assert.Equal(ScriptCompilationTicketState.Succeeded, ticket.state); AssertCompilationModalHidden(runtime); Assert.Equal(2, ReadVersion(m_fixture.ResolveActiveType("VersionedBehavior"))); @@ -1933,8 +1797,13 @@ public void EditorQueuesLatestAutomaticChangeWithoutReplacingAcceptedTicketWhile using EditorInteractionRuntime runtime = fixture.CreateEditorRuntime(reloads); runtime.Start(); IEditorScriptCompilation scripting = Assert.IsAssignableFrom(ScriptingCompilationProbe.compilation); - PumpEditorUntil(fixture, runtime, () => scripting.state == EditorScriptCompilationState.Ready, - () => $"{scripting.state}: {scripting.status}; ticket={scripting.currentTicket?.state}", timeoutMilliseconds: 120000); + PumpEditorUntil( + fixture, + runtime, + () => scripting.state == EditorScriptCompilationState.Ready, + () => $"{scripting.state}: {scripting.status}; ticket={scripting.currentTicket?.state}", + timeoutMilliseconds: 120000 + ); ScriptCompilationResult initial = Assert.IsType(scripting.lastCompilation); IScriptCompilationTicket? accepted = scripting.currentTicket; var guard = new DeferredReloadGuard(); @@ -1954,23 +1823,27 @@ public void EditorQueuesLatestAutomaticChangeWithoutReplacingAcceptedTicketWhile runtime.Update(new EditorFrame(1f / 60f, frame / 60f, true)); Thread.Sleep(5); } + Assert.Same(initial, scripting.lastCompilation); Assert.Same(accepted, scripting.currentTicket); Assert.Contains("fixture active Play", scripting.status); AssertCompilationModalHidden(runtime); } + guard.isBlocked = false; - PumpEditorUntil(fixture, runtime, + PumpEditorUntil( + fixture, + runtime, () => scripting.state == EditorScriptCompilationState.Ready && !ReferenceEquals(initial, scripting.lastCompilation), - () => $"{scripting.state}: {scripting.status}; ticket={scripting.currentTicket?.state}", timeoutMilliseconds: 120000); + () => $"{scripting.state}: {scripting.status}; ticket={scripting.currentTicket?.state}", + timeoutMilliseconds: 120000 + ); Assert.Equal(GenerationState.Ready, fixture.host.generations.state); Assert.Equal(3, ReadLatestVersion(fixture)); } [MethodImpl(MethodImplOptions.NoInlining)] - private static int ReadLatestVersion(ScriptingFixture fixture) - => ReadVersion(fixture.ResolveActiveType("VersionedBehavior")); - + private static int ReadLatestVersion(ScriptingFixture fixture) => ReadVersion(fixture.ResolveActiveType("VersionedBehavior")); private sealed class DeferredReloadGuard : IEditorReloadGuard { internal bool isBlocked { get; set; } = true; @@ -1980,9 +1853,7 @@ private sealed class DeferredReloadGuard : IEditorReloadGuard } [MethodImpl(MethodImplOptions.NoInlining)] - private static List RetainActiveScriptType(ScriptingFixture fixture) - => [fixture.ResolveActiveType("VersionedBehavior")]; - + private static List RetainActiveScriptType(ScriptingFixture fixture) => [fixture.ResolveActiveType("VersionedBehavior")]; private static void AssertCompilationModalHidden(EditorInteractionRuntime runtime) { EditorModalExtension modal = runtime.modals.Single(value => value.id == "scripting.compilation"); @@ -2016,12 +1887,9 @@ public void NewCompilationTicketSupersedesTheExactPreviousRequest() using EditorInteractionRuntime runtime = m_fixture.CreateEditorRuntime(); runtime.Start(); _ = runtime.panelCount; - IEditorScriptCompilation scripting = Assert.IsAssignableFrom( - ScriptingCompilationProbe.compilation); - + IEditorScriptCompilation scripting = Assert.IsAssignableFrom(ScriptingCompilationProbe.compilation); IScriptCompilationTicket first = scripting.RequestCompilation(); IScriptCompilationTicket second = scripting.RequestCompilation(); - Assert.Equal(ScriptCompilationTicketState.Superseded, first.state); Assert.True(first.isCompleted); Assert.True(second.requestId > first.requestId); @@ -2032,14 +1900,7 @@ public void NewCompilationTicketSupersedesTheExactPreviousRequest() [Fact] public void FailedResultHasNoActivationOrRuntimeArtifacts() { - ScriptCompilationResult result = ScriptCompilationResult.Failure(new ScriptDiagnostic( - "INNO-TEST", - DiagnosticSeverity.Error, - "Injected compilation failure.", - filePath: null, - line: 0, - column: 0)); - + ScriptCompilationResult result = ScriptCompilationResult.Failure(new ScriptDiagnostic("INNO-TEST", DiagnosticSeverity.Error, "Injected compilation failure.", filePath: null, line: 0, column: 0)); Assert.False(result.success); Assert.Null(result.outputDirectory); Assert.Empty(result.moduleDeployments); @@ -2048,8 +1909,18 @@ public void FailedResultHasNoActivationOrRuntimeArtifacts() Assert.Empty(result.reusedAssemblyNames); } - private static int ReadVersion(Type type) - => (int)type.GetProperty("version")!.GetValue(Activator.CreateInstance(type))!; + private static int ReadVersion(Type type) => (int)type.GetProperty("version")!.GetValue(Activator.CreateInstance(type))!; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AssertRetainedActiveType( + ScriptingFixture fixture, + WeakReference expected, + int expectedVersion + ) { + Type active = fixture.ResolveActiveType("VersionedBehavior"); + Assert.Same(expected.Target, active); + Assert.Equal(expectedVersion, ReadVersion(active)); + } [MethodImpl(MethodImplOptions.NoInlining)] private static WeakReference CaptureActiveType( @@ -2086,11 +1957,7 @@ GameScene scene GameComponent scriptComponent = scriptOwner.AddComponent(scriptType); pluginType.GetProperty("retained")!.SetValue(pluginComponent, 47); scriptType.GetProperty("retained")!.SetValue(scriptComponent, 73); - return new AvailabilitySceneState( - pluginOwner, - scriptOwner, - pluginComponent.identity.persistentId, - scriptComponent.identity.persistentId); + return new AvailabilitySceneState(pluginOwner, scriptOwner, pluginComponent.identity.persistentId, scriptComponent.identity.persistentId); } private static void CompleteUnloadVerification(ScriptReloadHost reload) @@ -2118,22 +1985,17 @@ RuntimeSession session Assert.True(fixture.RefreshPlugins()); ScriptCompilationResult unavailable = fixture.CompilePendingPluginReload(reload); Assert.True(unavailable.success, FormatDiagnostics(unavailable)); - Assert.DoesNotContain(unavailable.moduleDeployments, static request => - request.domain == AssemblyDomain.InnoPlugin); - Assert.DoesNotContain(unavailable.moduleDeployments.SelectMany(static request => request.upstreamModuleNames), - static moduleName => string.Equals( - moduleName, - "Plugin.tests.unavailability", - StringComparison.Ordinal)); + Assert.DoesNotContain(unavailable.moduleDeployments, static request => request.domain == AssemblyDomain.InnoPlugin); + Assert.DoesNotContain( + unavailable.moduleDeployments.SelectMany(static request => request.upstreamModuleNames), + static moduleName => string.Equals(moduleName, "Plugin.tests.unavailability", StringComparison.Ordinal) + ); Assert.True(reload.ApplyPendingReload()); - Assert.DoesNotContain(fixture.host.modules.modules, static module => - module.domain == AssemblyDomain.InnoPlugin || - module.upstreamModuleNames.Contains("Plugin.tests.unavailability", StringComparer.Ordinal)); - return new MissingGenerationExpectation( - owner, - componentId, - new WeakReference(component), - new WeakReference(pluginType)); + Assert.DoesNotContain( + fixture.host.modules.modules, + static module => module.domain == AssemblyDomain.InnoPlugin || module.upstreamModuleNames.Contains("Plugin.tests.unavailability", StringComparer.Ordinal) + ); + return new MissingGenerationExpectation(owner, componentId, new WeakReference(component), new WeakReference(pluginType)); } private static void ForceFullCollection() @@ -2149,19 +2011,16 @@ private readonly record struct MissingGenerationExpectation( WeakReference retiredComponent, WeakReference retiredType ); - private readonly record struct RetiredScriptObject( WeakReference type, WeakReference component ); - private readonly record struct AvailabilitySceneState( GameObject pluginOwner, GameObject scriptOwner, Guid pluginComponentId, Guid scriptComponentId ); - private static void WriteProjectionPlugin( string projectRoot, SerializationRegistry serialization @@ -2179,23 +2038,18 @@ public sealed class ProjectionRuntime : GameBehavior projectRoot, "projection.iplugin", serialization, - new PluginManifest - { - pluginId = "tests.projection", - displayName = "Projection Plugin" - }, - new Dictionary - { + new PluginManifest { pluginId = "tests.projection", displayName = "Projection Plugin" }, + new Dictionary { ["Assets/ProjectionRuntime.cs"] = System.Text.Encoding.UTF8.GetBytes(c_source), ["Assets/ProjectionRuntime.cs.imeta"] = CreateScriptSourceMeta(serialization, Guid.NewGuid()) - }); + } + ); } private static void WriteUnavailableGenerationPlugin( string projectRoot, SerializationRegistry serialization - ) - => WriteUnavailableGenerationPlugin(projectRoot, serialization, """ + ) => WriteUnavailableGenerationPlugin(projectRoot, serialization, """ using InnoEngine.Reflection; using InnoEngine.Scene; using InnoEngine.Serialization; @@ -2209,7 +2063,6 @@ public sealed class PluginBehavior : GameBehavior public int retained { get; set; } = 5; } """); - private static void WriteUnavailableGenerationPlugin( string projectRoot, SerializationRegistry serialization, @@ -2219,28 +2072,19 @@ string source projectRoot, "unavailability.iplugin", serialization, - new PluginManifest - { - pluginId = "tests.unavailability", - displayName = "Unavailability Test Plugin" - }, - new Dictionary - { + new PluginManifest { pluginId = "tests.unavailability", displayName = "Unavailability Test Plugin" }, + new Dictionary { ["Assets/PluginBehavior.cs"] = System.Text.Encoding.UTF8.GetBytes(source), - ["Assets/PluginBehavior.cs.imeta"] = CreateScriptSourceMeta( - serialization, - Guid.Parse("37f9751f-a7de-43c7-94ac-c8e854762d49")) - }); + ["Assets/PluginBehavior.cs.imeta"] = CreateScriptSourceMeta(serialization, Guid.Parse("37f9751f-a7de-43c7-94ac-c8e854762d49")) + } + ); } private static void WriteSamplePlugin( string projectRoot, SerializationRegistry serialization ) { - const string c_source = - "using InnoEngine.Reflection; using InnoEngine.Scene; " + - "[StableTypeId(\"ce3b52c6-2a07-42ea-b632-a307a0ef7407\")] " + - "public sealed class StarterBehavior : GameBehavior { }"; + const string c_source = "using InnoEngine.Reflection; using InnoEngine.Scene; " + "[StableTypeId(\"ce3b52c6-2a07-42ea-b632-a307a0ef7407\")] " + "public sealed class StarterBehavior : GameBehavior { }"; WriteSamplePlugin(projectRoot, serialization, c_source); } @@ -2253,38 +2097,21 @@ string source projectRoot, "samples.iplugin", serialization, - new PluginManifest - { - pluginId = "tests.samples", - displayName = "Sample Test Plugin" - }, - new Dictionary - { + new PluginManifest { pluginId = "tests.samples", displayName = "Sample Test Plugin" }, + new Dictionary { ["Assets/"] = [], ["Assets/~Starter/"] = [], - ["Assets/~Starter.imeta"] = serialization.Serialize(new ScriptingAssetSourceMeta - { - persistentId = Guid.Parse("7726b1d2-9aee-4d2c-a865-2fd53155095f"), - sourceKind = (int)AssetSourceKind.Directory - }), + ["Assets/~Starter.imeta"] = serialization.Serialize(new ScriptingAssetSourceMeta { persistentId = Guid.Parse("7726b1d2-9aee-4d2c-a865-2fd53155095f"), sourceKind = (int)AssetSourceKind.Directory }), ["Assets/~Starter/StarterBehavior.cs"] = System.Text.Encoding.UTF8.GetBytes(source), - ["Assets/~Starter/StarterBehavior.cs.imeta"] = CreateScriptSourceMeta( - serialization, - Guid.Parse("75f6a70b-93b2-47f0-8747-cc359474b7a3")) - }); + ["Assets/~Starter/StarterBehavior.cs.imeta"] = CreateScriptSourceMeta(serialization, Guid.Parse("75f6a70b-93b2-47f0-8747-cc359474b7a3")) + } + ); } private static byte[] CreateScriptSourceMeta( SerializationRegistry serialization, Guid persistentId - ) - => serialization.Serialize(new ScriptingAssetSourceMeta - { - persistentId = persistentId, - sourceKind = (int)AssetSourceKind.File, - importerId = "inno.editor.csharp-script" - }); - + ) => serialization.Serialize(new ScriptingAssetSourceMeta { persistentId = persistentId, sourceKind = (int)AssetSourceKind.File, importerId = "inno.editor.csharp-script" }); private static void WritePluginPackage( string projectRoot, string fileName, @@ -2296,7 +2123,7 @@ IReadOnlyDictionary entries using FileStream stream = File.Create(path); using var archive = new ZipArchive(stream, ZipArchiveMode.Create); WritePluginPackageEntry(archive, "Plugin.inno", serialization.Serialize(manifest)); - foreach ((string entryPath, byte[] bytes) in entries) + foreach ((string entryPath, byte[] bytes)in entries) WritePluginPackageEntry(archive, entryPath, bytes); } @@ -2312,10 +2139,10 @@ byte[] bytes output.Write(bytes); } - private static string FormatDiagnostics(ScriptCompilationResult result) - => string.Join(Environment.NewLine, result.diagnostics.Select(static diagnostic => - $"{diagnostic.id}: {diagnostic.message}")); - + private static string FormatDiagnostics(ScriptCompilationResult result) => string.Join( + Environment.NewLine, + result.diagnostics.Select(static diagnostic => $"{diagnostic.id}: {diagnostic.message}") + ); private static bool ContainsCustomAttribute( string assemblyPath, string attributeTypeName @@ -2328,17 +2155,14 @@ string attributeTypeName CustomAttribute attribute = metadata.GetCustomAttribute(handle); string? typeName = attribute.Constructor.Kind switch { - HandleKind.MemberReference => GetMemberReferenceDeclaringTypeName( - metadata, - (MemberReferenceHandle)attribute.Constructor), - HandleKind.MethodDefinition => metadata.GetString(metadata.GetTypeDefinition( - metadata.GetMethodDefinition((MethodDefinitionHandle)attribute.Constructor) - .GetDeclaringType()).Name), + HandleKind.MemberReference => GetMemberReferenceDeclaringTypeName(metadata, (MemberReferenceHandle)attribute.Constructor), + HandleKind.MethodDefinition => metadata.GetString(metadata.GetTypeDefinition(metadata.GetMethodDefinition((MethodDefinitionHandle)attribute.Constructor).GetDeclaringType()).Name), _ => null }; if (string.Equals(typeName, attributeTypeName, StringComparison.Ordinal)) return true; } + return false; } @@ -2349,10 +2173,8 @@ MemberReferenceHandle constructorHandle MemberReference constructor = metadata.GetMemberReference(constructorHandle); return constructor.Parent.Kind switch { - HandleKind.TypeReference => metadata.GetString( - metadata.GetTypeReference((TypeReferenceHandle)constructor.Parent).Name), - HandleKind.TypeDefinition => metadata.GetString( - metadata.GetTypeDefinition((TypeDefinitionHandle)constructor.Parent).Name), + HandleKind.TypeReference => metadata.GetString(metadata.GetTypeReference((TypeReferenceHandle)constructor.Parent).Name), + HandleKind.TypeDefinition => metadata.GetString(metadata.GetTypeDefinition((TypeDefinitionHandle)constructor.Parent).Name), _ => null }; } @@ -2366,7 +2188,7 @@ private sealed class CompilationGate : IProgress, IDi /// /// Suspends reference preparation to verify that no Editor frame waits for the compiler. /// - /// + /// /// The real compiler progress callback. /// public void Report(ScriptCompilationProgress value) @@ -2412,7 +2234,6 @@ public IDisposable BeginPlayMode(RuntimeSession runtimeSession) private sealed class RestoreLease(PluginPlayScene owner) : IDisposable { private bool m_disposed; - public void Dispose() { if (m_disposed) @@ -2427,13 +2248,10 @@ private sealed class CountingHistoryIsolation : IEditorHistoryIsolation { internal int disposeCount { get; private set; } - public IDisposable BeginHistoryIsolation() - => new HistoryLease(this); - + public IDisposable BeginHistoryIsolation() => new HistoryLease(this); private sealed class HistoryLease(CountingHistoryIsolation owner) : IDisposable { private bool m_disposed; - public void Dispose() { if (m_disposed) @@ -2446,61 +2264,45 @@ public void Dispose() private sealed class ReadyScriptCompilation : IEditorScriptCompilation { - private readonly ReadyCompilationTicket m_ticket = new(); + public bool requiresExclusiveProgress => false; + public float progress => 0f; - public IScriptCompilationTicket RequestCompilation() - => m_ticket; + public void CancelCompilation() + { + } + private readonly ReadyCompilationTicket m_ticket = new(); + public IScriptCompilationTicket RequestCompilation() => m_ticket; public IScriptCompilationTicket? currentTicket => m_ticket; - public EditorScriptCompilationState state => EditorScriptCompilationState.Ready; - public string status => "The test script generation is ready."; - public ScriptCompilationResult? lastCompilation => null; private sealed class ReadyCompilationTicket : IScriptCompilationTicket { public long requestId => 1; - public ScriptCompilationTicketState state => ScriptCompilationTicketState.Succeeded; - public string status => "The test compilation ticket succeeded."; - public ScriptCompilationResult? result => null; - public bool isCompleted => true; } } private sealed class TestSceneReloadParticipant(SceneReloadService reload) : IEditorReloadParticipant { - public IGenerationChange Capture(AssemblyReloadContext context) - => new TestSceneReloadTransaction(reload.Capture( - context.GetContext())); - + public IGenerationChange Capture(AssemblyReloadContext context) => new TestSceneReloadTransaction(reload.Capture(context.GetContext())); public void RefreshDiagnostics() { } } - private sealed class TestSceneReloadTransaction(ISceneReloadStateTransfer transfer) - : IGenerationChange + private sealed class TestSceneReloadTransaction(ISceneReloadStateTransfer transfer) : IGenerationChange { - public void PrepareForActivation() - => transfer.PrepareForActivation(); - - public void Apply() - => transfer.Apply(); - - public void Complete() - => transfer.Complete(); - - public void RollbackStructure() - => transfer.RollbackStructure(); - - public void RestorePreviousState() - => transfer.RestorePreviousState(); + public void PrepareForActivation() => transfer.PrepareForActivation(); + public void Apply() => transfer.Apply(); + public void Complete() => transfer.Complete(); + public void RollbackStructure() => transfer.RollbackStructure(); + public void RestorePreviousState() => transfer.RestorePreviousState(); } } @@ -2513,16 +2315,12 @@ internal sealed class ScriptingFixture : IDisposable private readonly PluginEnvironment m_plugins; private RuntimeSession? m_editorSession; private bool m_disposed; - internal ScriptingFixture( Action? configureProject = null, bool enableFileSystemWatcher = false, int fileWatcherFlushDelayMs = 100 ) { - projectRoot = Path.Combine( - Path.GetTempPath(), - "InnoScriptingPipelineTests", - Guid.NewGuid().ToString("N")); + projectRoot = Path.Combine(Path.GetTempPath(), "InnoScriptingPipelineTests", Guid.NewGuid().ToString("N")); string assetRoot = Path.Combine(projectRoot, "Assets"); string pluginRoot = Path.Combine(projectRoot, "Plugins"); string libraryRoot = Path.Combine(projectRoot, "Library"); @@ -2530,10 +2328,11 @@ internal ScriptingFixture( Directory.CreateDirectory(pluginRoot); Directory.CreateDirectory(libraryRoot); m_identityScope = m_identities.EnterScope(); - host = new EngineHostBuilder() - .UseMetadataSources(new DotNetAssemblyCatalogSource(typeof(ScriptingPipelineTests).Assembly), - new ReflectionTypeCatalogSource(), new ReflectionSerializationMetadataSource()) - .Build(); + host = new EngineHostBuilder().UseMetadataSources( + new DotNetAssemblyCatalogSource(typeof(ScriptingPipelineTests).Assembly), + new ReflectionTypeCatalogSource(), + new ReflectionSerializationMetadataSource() + ).Build(); m_diagnosticScope = host.diagnostics.EnterScope(); configureProject?.Invoke(projectRoot, host.serialization); var pluginSources = new PluginSourceService(host.serialization, pluginRoot, libraryRoot); @@ -2546,48 +2345,37 @@ internal ScriptingFixture( m_identities, host.diagnostics, host.logs, - options with - { + options with { enableFileSystemWatcher = enableFileSystemWatcher, fileWatcherFlushDelayMs = fileWatcherFlushDelayMs, - sourceMounts = - [ - .. options.sourceMounts!, - .. PluginSourceService.GetActivatableMounts(scan) - ] - }); + sourceMounts = [..options.sourceMounts!, ..PluginSourceService.GetActivatableMounts(scan)] + } + ); m_settings = new ProjectSettingsStore( new FileByteDocumentStore(Path.GetFullPath(Path.Combine(projectRoot, "Settings.Project.inno"))), host.types, host.serialization, new ProjectId("tests.scripting"), - AssetSerializationContext.Create(assets)); - m_plugins = new PluginEnvironment( - assets, - m_settings, - host.serialization, - pluginRoot, - libraryRoot, - scan, - host.modules.generations); + AssetSerializationContext.Create(assets) + ); + m_plugins = new PluginEnvironment(assets, m_settings, host.serialization, pluginRoot, libraryRoot, scan, host.modules.generations); + string dotnetHost = Path.Combine( + Path.GetFullPath("../../..", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); compiler = new ScriptCompiler( - new ScriptCompilerOptions { projectRootDirectory = projectRoot }, - assets, - m_plugins); + new ScriptCompilerOptions + { + projectRootDirectory = projectRoot, + compilationEnvironment = new Inno.Build.SdkScriptCompilationEnvironment(dotnetHost, projectRoot) + }, assets, m_plugins); } internal string projectRoot { get; } - internal EngineHost host { get; } - internal AssetPipeline assets { get; } - internal ScriptCompiler compiler { get; } - internal IReadOnlyList activePlugins => m_plugins.activePlugins; - - internal RuntimeSession editorSession - => m_editorSession ?? throw new InvalidOperationException("The Editor runtime session has not been created."); + internal RuntimeSession editorSession => m_editorSession ?? throw new InvalidOperationException("The Editor runtime session has not been created."); internal void Write( string relativePath, @@ -2611,8 +2399,7 @@ string destinationRelativePath File.Move(sourceMetadata, destination + ".imeta"); } - internal void WriteVersionedBehavior(int version) - => Write("VersionedBehavior.cs", $$""" + internal void WriteVersionedBehavior(int version) => Write("VersionedBehavior.cs", $$""" using InnoEngine.Scene; public sealed class VersionedBehavior : GameBehavior @@ -2620,9 +2407,7 @@ public sealed class VersionedBehavior : GameBehavior public int version => {{version}}; } """); - internal void Rescan() => assets.Rescan(); - internal ScriptCompilationResult Compile( IProgress? progress = null, CancellationToken cancellationToken = default @@ -2637,89 +2422,66 @@ internal ScriptCompilationResult CompileRuntimeDeployment( CancellationToken cancellationToken = default ) { Rescan(); - return compiler.CompileRuntimeDeploymentAsync( - targetRuntimeDirectory ?? AppContext.BaseDirectory, - progress, - cancellationToken) - .GetAwaiter() - .GetResult(); + return compiler.CompileRuntimeDeploymentAsync(targetRuntimeDirectory ?? AppContext.BaseDirectory, progress, cancellationToken).GetAwaiter().GetResult(); } internal string CreateDeploymentRuntime() { string directory = Path.Combine(projectRoot, "TargetRuntime"); Directory.CreateDirectory(directory); - foreach (string source in Directory.EnumerateFiles( - AppContext.BaseDirectory, - "Inno.*.dll", - SearchOption.TopDirectoryOnly)) + foreach (string source in Directory.EnumerateFiles(AppContext.BaseDirectory, "Inno.*.dll", SearchOption.TopDirectoryOnly)) { File.Copy(source, Path.Combine(directory, Path.GetFileName(source)), overwrite: true); } + return directory; } - private IModuleSource CreateScriptModuleSource(ScriptModuleDeployment deployment) - => new DotNetModuleSource - { - artifactRootDirectory = Path.Combine(projectRoot, "Library", "Assemblies"), - moduleName = deployment.moduleName, - mainAssemblyPath = deployment.mainAssemblyPath, - domain = deployment.domain, - scope = deployment.scope, - preloadAssemblyPaths = deployment.preloadAssemblyPaths, - upstreamModuleNames = deployment.upstreamModuleNames, - assemblyScopes = deployment.assemblyScopes, - collectible = true - }; - + private IModuleSource CreateScriptModuleSource(ScriptModuleDeployment deployment) => new DotNetModuleSource + { + artifactRootDirectory = Path.Combine(projectRoot, "Library", "Assemblies"), + moduleName = deployment.moduleName, + mainAssemblyPath = deployment.mainAssemblyPath, + domain = deployment.domain, + scope = deployment.scope, + preloadAssemblyPaths = deployment.preloadAssemblyPaths, + upstreamModuleNames = deployment.upstreamModuleNames, + assemblyScopes = deployment.assemblyScopes, + collectible = true + }; internal ScriptReloadHost CreateReloadHost( EditorReloadCoordinator? reloads = null, bool autoCompile = false - ) - => new( - new ScriptReloadOptions - { - autoCompile = autoCompile, - debounceMilliseconds = 0, - compilationWarningTimeout = Timeout.InfiniteTimeSpan - }, - compiler, - assets, - m_plugins, - host.modules, - m_settings, - reloads ?? new EditorReloadCoordinator(), - CreateScriptModuleSource); - + ) => new( + new ScriptReloadOptions { autoCompile = autoCompile, debounceMilliseconds = 0, compilationWarningTimeout = Timeout.InfiniteTimeSpan }, + compiler, + assets, + m_plugins, + host.modules, + m_settings, + reloads ?? new EditorReloadCoordinator(), + CreateScriptModuleSource + ); internal ScriptCompilationResult CompilePending(ScriptReloadHost reload) { Rescan(); reload.RecompileScripting(); Assert.True(reload.TryCompilePending(out Task? compilation)); - return Assert.IsAssignableFrom>(compilation) - .GetAwaiter() - .GetResult(); + return Assert.IsAssignableFrom>(compilation).GetAwaiter().GetResult(); } internal ScriptCompilationResult CompilePendingPluginReload(ScriptReloadHost reload) { reload.ReloadPlugins(); Assert.True(reload.TryCompilePending(out Task? compilation)); - return Assert.IsAssignableFrom>(compilation) - .GetAwaiter() - .GetResult(); + return Assert.IsAssignableFrom>(compilation).GetAwaiter().GetResult(); } - internal bool RefreshPlugins() - => m_plugins.Refresh(); - + internal bool RefreshPlugins() => m_plugins.Refresh(); internal Type ResolveActiveType(string name) { TypeCacheSnapshot snapshot = host.types.current; - return snapshot.types - .Select(typeRef => typeRef.Resolve(snapshot)) - .Single(type => string.Equals(type.Name, name, StringComparison.Ordinal)); + return snapshot.types.Select(typeRef => typeRef.Resolve(snapshot)).Single(type => string.Equals(type.Name, name, StringComparison.Ordinal)); } internal EditorInteractionRuntime CreateEditorRuntime(EditorReloadCoordinator? reloads = null) @@ -2735,6 +2497,8 @@ internal EditorInteractionRuntime CreateEditorRuntime(EditorReloadCoordinator? r [ host.types, host.serialization, + m_identities, + editorSession.identities, host.modules, assets, m_plugins, @@ -2743,18 +2507,13 @@ internal EditorInteractionRuntime CreateEditorRuntime(EditorReloadCoordinator? r new Func(CreateScriptModuleSource), editorSession, reloads - ]); + ] + ); } internal RuntimeSession CreateEditorSession() { - m_editorSession ??= host.CreateSession(new RuntimeSessionOptions - { - kind = RuntimeSessionKind.Edit, - applicationId = "tests.editor", - createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(projectRoot, "Persistent", "tests.editor"), "Logs")), - referenceResolvers = [assets] - }); + m_editorSession ??= host.CreateSession(new RuntimeSessionOptions { kind = RuntimeSessionKind.Edit, applicationId = "tests.editor", createLogSink = _ => new FileLogSink(Path.Combine(Path.Combine(projectRoot, "Persistent", "tests.editor"), "Logs")), referenceResolvers = [assets] }); return m_editorSession; } diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Performance.cs b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Performance.cs new file mode 100644 index 000000000..75fb232ad --- /dev/null +++ b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Performance.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Security.Cryptography; +using System.Text.Json; +using Inno.Assets; +using Inno.Assets.Pipeline; +using Inno.Core.Graphs; +using Inno.Core.Serialization; +using Inno.Native.ImGui; +using Inno.Rendering.Shaders; +using Xunit; +using NativeGui = Inno.Native.ImGui.ImGui; + +namespace Inno.Editor.Scripting.Tests; + +public sealed partial class ShaderEditorWorkflowTests +{ + [Fact] + [Trait("Acceptance", "Performance")] + public unsafe void TwoThousandShaderNodesProduceRepeatableFrameMeasurements() + { + NativeGui.Raw.GetIO()->BackendFlags |= (int)ImGuiBackendFlags.RendererHasVtxOffset; + string source = Path.Combine(m_root, "Assets"); + string? inputRoot = Environment.GetEnvironmentVariable("INNO_UI_PERFORMANCE_INPUT_ROOT"); + string? input = inputRoot is null ? null : Path.Combine(Path.GetFullPath(inputRoot), "shader-graph"); + const string name = "Performance.ishader"; + if (input is not null && Directory.Exists(input)) + CopyPerformanceInput(input, source); + else + { + GraphDocument graph = ShaderGraphTemplates.CreateRaster(m_serialization, AssetSerializationContext.Create(m_assets)); + int remaining = 2_000 - graph.nodes.Count; + for (int index = 0; index < remaining; index++) + { + bool constant = index % 2 == 0; + var node = new GraphNodeRecord(new("performance.node." + index.ToString("D4")), + constant ? "inno.shader.constant" : "inno.shader.construct") + { + position = new(index % 20 * 310f, index / 20 * 180f) + }; + if (!constant) + node.SetValue("type", ShaderGraphDocument.Encode("float3", m_serialization, SerializationContext.empty)); + graph.AddNode(node); + if (!constant) + graph.AddEdge(new(new("performance.edge." + index.ToString("D4")), + new(new("performance.node." + (index - 1).ToString("D4")), new("value")), + new(node.id, new("component.0")))); + } + File.WriteAllBytes(Path.Combine(source, name), GraphDocumentCodec.Encode(graph, m_serialization)); + } + Assert.True(m_assets.Import(AssetPath.Project(name))); + Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project(name), out AssetFileEntry entry)); + if (input is not null && !Directory.Exists(input)) + CopyPerformanceInput(source, input); + byte[] original = SHA256.HashData(File.ReadAllBytes(Path.Combine(source, name))); + SelectAndDraw(entry); + var controller = Controller(entry); + Assert.Equal(2_000, controller.document.nodes.Count); + Assert.True(controller.document.edges.Count >= 900); + for (int warmup = 0; warmup < 5; warmup++) + DrawPerformanceCanvas(); + string? requested = Environment.GetEnvironmentVariable("INNO_UI_PERFORMANCE_REPETITIONS"); + int repetitions = requested is null ? 5 : int.Parse(requested, System.Globalization.CultureInfo.InvariantCulture); + Assert.InRange(repetitions, 1, 5); + using Process process = Process.GetCurrentProcess(); + var samples = new List(); + for (int repetition = 0; repetition < repetitions; repetition++) + { + var milliseconds = new double[10]; + var vertices = new int[10]; + var indices = new int[10]; + long bytesBefore = GC.GetAllocatedBytesForCurrentThread(); + long totalBytesBefore = GC.GetTotalAllocatedBytes(precise: true); + TimeSpan cpuBefore = process.TotalProcessorTime; + int gen0 = GC.CollectionCount(0); + int gen1 = GC.CollectionCount(1); + int gen2 = GC.CollectionCount(2); + for (int frame = 0; frame < milliseconds.Length; frame++) + { + long start = Stopwatch.GetTimestamp(); + DrawPerformanceCanvas(); + milliseconds[frame] = Stopwatch.GetElapsedTime(start).TotalMilliseconds; + vertices[frame] = NativeGui.Raw.GetDrawData()->TotalVtxCount; + indices[frame] = NativeGui.Raw.GetDrawData()->TotalIdxCount; + } + long allocated = GC.GetAllocatedBytesForCurrentThread() - bytesBefore; + double[] sorted = milliseconds.Order().ToArray(); + samples.Add(new + { + repetition, milliseconds, vertices, indices, median = (sorted[4] + sorted[5]) / 2, + p95 = sorted[9], allocatedBytes = allocated, + totalAllocatedBytes = GC.GetTotalAllocatedBytes(precise: true) - totalBytesBefore, + cpuMilliseconds = (process.TotalProcessorTime - cpuBefore).TotalMilliseconds, + gen0Collections = GC.CollectionCount(0) - gen0, + gen1Collections = GC.CollectionCount(1) - gen1, + gen2Collections = GC.CollectionCount(2) - gen2 + }); + } + m_performanceOutput.WriteLine(JsonSerializer.Serialize(new + { + workload = "2000-shader-nodes", nodeCount = controller.document.nodes.Count, + edgeCount = controller.document.edges.Count, + configuration = typeof(ShaderEditorWorkflowTests).Assembly.GetCustomAttribute()?.Configuration + ?? throw new InvalidOperationException("The benchmark assembly has no SDK configuration metadata."), + samples, + framework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, + viewport = "1200x800", warmupFrames = 5, measuredFramesPerRepetition = 10, + sharedInput = input, consumer = "native-command-offset-validation", supportsVertexOffsets = true + })); + Assert.Equal(original, SHA256.HashData(File.ReadAllBytes(Path.Combine(source, name)))); + } + + private unsafe void DrawPerformanceCanvas() + { + Draw(new Vector2(1200f, 800f)); + ImDrawData* data = NativeGui.Raw.GetDrawData(); + for (int listIndex = 0; listIndex < data->CmdListsCount; listIndex++) + { + ImDrawList* list = data->CmdLists.Data[listIndex]; + for (int commandIndex = 0; commandIndex < list->CmdBuffer.Size; commandIndex++) + { + ImDrawCmd command = list->CmdBuffer.Data[commandIndex]; + ulong end = (ulong)command.IdxOffset + command.ElemCount; + if (end > (ulong)list->IdxBuffer.Size) + throw new InvalidOperationException("The shader canvas exceeded its native index buffer."); + for (ulong index = command.IdxOffset; index < end; index++) + if ((ulong)command.VtxOffset + list->IdxBuffer.Data[index] >= (ulong)list->VtxBuffer.Size) + throw new InvalidOperationException("The shader canvas exceeded its native vertex buffer."); + } + } + } + + private static void CopyPerformanceInput( + string source, + string destination + ) { + Directory.CreateDirectory(destination); + foreach (string directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory))); + foreach (string file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) + File.Copy(file, Path.Combine(destination, Path.GetRelativePath(source, file)), overwrite: false); + } +} diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Visual.cs b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Visual.cs new file mode 100644 index 000000000..80b006866 --- /dev/null +++ b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.Visual.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Inno.Editor.Scripting.Tests; + +public sealed partial class ShaderEditorWorkflowTests +{ + public static IEnumerable CanvasMetrics() + { + foreach (float zoom in new[] { 0.75f, 0.85f, 0.90f, 1f, 1.10f, 1.25f, 1.50f }) + foreach (float density in new[] { 1f, 1.25f, 1.50f, 2f }) + foreach ((float width, float height) in new[] { (1280f, 720f), (1920f, 1080f) }) + foreach (bool emptyGraph in new[] { false, true }) + yield return [zoom, density, width, height, emptyGraph]; + } +} diff --git a/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.cs b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.cs index e1db0e757..388871b60 100644 --- a/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.cs +++ b/tests/scripting/Inno.Editor.Scripting.Tests/ShaderEditorWorkflowTests.cs @@ -1,3 +1,4 @@ +using EditorUI = Inno.Editor.ImGui.EditorUiSession; using System.Runtime.CompilerServices; using NativeVector2 = Inno.Native.ImGui.ImVec2C; using BGCS.Runtime; @@ -24,8 +25,10 @@ using Inno.Editor.Core; using Inno.Editor.Graph; using Inno.Editor.Interactions; -using Inno.Editor.Panel.FileBrowser; +using Inno.Editor.Assets; +using Inno.Editor.Assets.ImGui; using Inno.Editor.Shaders; +using Inno.Editor.Shaders.ImGui; using Inno.Editor.Rendering; using Inno.Extensibility.Modules; using Inno.Extensibility.Reload; @@ -36,12 +39,15 @@ using Inno.Native.ImGui; using ImGuiApi = Inno.Native.ImGui.ImGui; using Xunit; +using Xunit.Abstractions; using Inno.Rendering.Assets.Authoring; namespace Inno.Editor.Scripting.Tests; - -public sealed class ShaderEditorWorkflowTests : IDisposable +public sealed partial class ShaderEditorWorkflowTests : IDisposable { + private readonly ITestOutputHelper m_performanceOutput; + + private readonly EditorUI m_ui = new(); private readonly string m_root = Path.Combine(Path.GetTempPath(), "InnoShaderEditorWorkflow", Guid.NewGuid().ToString("N")); private readonly IdentityAllocator m_identities = new(); private readonly IDisposable m_identityScope; @@ -62,23 +68,30 @@ public sealed class ShaderEditorWorkflowTests : IDisposable private Vector2 m_canvasMinimum; private Vector2 m_canvasMaximum; private Vector2 m_probeOffset; - - public unsafe ShaderEditorWorkflowTests() + public unsafe ShaderEditorWorkflowTests(ITestOutputHelper output) { + m_performanceOutput = output; Directory.CreateDirectory(Path.Combine(m_root, "Assets")); m_logs.RegisterSink(m_messages); m_identityScope = m_identities.EnterScope(); _ = typeof(ShaderNodeDrawer); _ = typeof(ShaderGraphSourceStore); _ = typeof(GraphEditorModule); - m_modules = new(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ShaderEditorWorkflowTests).Assembly)}); + m_modules = new(new() { catalogSource = new DotNetAssemblyCatalogSource(typeof(ShaderEditorWorkflowTests).Assembly) }); m_types = new(m_modules, new ReflectionTypeCatalogSource()); m_serialization = new(m_types, new ReflectionSerializationMetadataSource()); m_diagnostics = new DiagnosticHub(); m_diagnosticScope = m_diagnostics.EnterScope(); m_reporter = m_diagnostics.CreateReporter(new("tests.shader-editor", "Shader Editor workflow")); - m_assets = new(m_modules, m_types, m_serialization, m_identities, m_diagnostics, m_logs, - AssetPipelineOptions.Create(Path.Combine(m_root, "Assets"), Path.Combine(m_root, "Library")) with { enableFileSystemWatcher = false }); + m_assets = new( + m_modules, + m_types, + m_serialization, + m_identities, + m_diagnostics, + m_logs, + AssetPipelineOptions.Create(Path.Combine(m_root, "Assets"), Path.Combine(m_root, "Library"))with { enableFileSystemWatcher = false } + ); m_source = new(m_assets, m_serialization); m_artifacts = new(m_assets, m_serialization, m_types, new ShaderCompiler(new WorkflowCompiler()), new BgfxTextureTargetCompiler(), m_reporter); m_runtime = CreateRuntime(); @@ -100,10 +113,13 @@ public void ParameterPresentationRoundTripsWithoutChangingTheRuntimeDefinitionOr string beforeHash = ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(graph, new Dictionary(), m_serialization), m_serialization); ShaderPropertyId id = new("stable.tint"); Assert.True(ShaderParameterPresentation.Read(graph, id, m_serialization, context).visible); - ShaderParameterPresentation.Write(graph, id, new() - { - group = "Surface", description = "Linear tint", visible = false, hasRange = true, minimum = -2, maximum = 8 - }, m_serialization, context); + ShaderParameterPresentation.Write( + graph, + id, + new() { group = "Surface", description = "Linear tint", visible = false, hasRange = true, minimum = -2, maximum = 8 }, + m_serialization, + context + ); GraphDocument restored = GraphDocumentCodec.Decode(GraphDocumentCodec.Encode(graph, m_serialization), m_serialization); ShaderParameterPresentation presentation = ShaderParameterPresentation.Read(restored, id, m_serialization, context); Assert.Equal("Surface", presentation.group); @@ -115,7 +131,10 @@ public void ParameterPresentationRoundTripsWithoutChangingTheRuntimeDefinitionOr presentation.group = "Detached"; Assert.Equal("Surface", ShaderParameterPresentation.Read(restored, id, m_serialization, context).group); Assert.Equal(beforeDefinition, m_serialization.Serialize(ShaderGraphDocument.ReadDefinition(restored, m_serialization, context), context)); - Assert.Equal(beforeHash, ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(restored, new Dictionary(), m_serialization), m_serialization)); + Assert.Equal( + beforeHash, + ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(restored, new Dictionary(), m_serialization), m_serialization) + ); } [Fact] @@ -124,9 +143,17 @@ public void ParameterPresentationRejectsInvalidBoundsBeforeMutatingTheGraph() GraphDocument graph = m_source.Read(Create("Range.ishader").assetPath).document; byte[] before = GraphDocumentCodec.Encode(graph, m_serialization); SerializationContext context = AssetSerializationContext.Create(m_assets); - foreach (var range in new[] { (double.NaN, 1d), (0d, double.PositiveInfinity), (2d, 1d), (-1e100, 0d), (0d, 1e100) }) - Assert.Throws(() => ShaderParameterPresentation.Write(graph, new("range"), - new() { hasRange = true, minimum = range.Item1, maximum = range.Item2 }, m_serialization, context)); + foreach (var range in new[] + { + (double.NaN, 1d), + (0d, double.PositiveInfinity), + (2d, 1d), + (-1e100, 0d), + (0d, 1e100) + } + + ) + Assert.Throws(() => ShaderParameterPresentation.Write(graph, new("range"), new() { hasRange = true, minimum = range.Item1, maximum = range.Item2 }, m_serialization, context)); Assert.Equal(before, GraphDocumentCodec.Encode(graph, m_serialization)); } @@ -144,16 +171,26 @@ public unsafe void DiagnosticCheckUsesTheStandardTransientModalWithoutChangingFi Assert.False(presentation.canResize); for (int frame = 0; frame < 120 && presentation.isVisible; frame++) { + using IDisposable uiScope = m_ui.Enter(); ImGuiApi.NewFrame(); ImGuiApi.SetNextWindowPos(new(0, 0)); ImGuiApi.SetNextWindowSize(new(1200, 800)); using NativeStringLease nativeTestText0 = Utf8Codec.EncodeNullTerminated("Shader Check Workflow", allowsNull: false); _ = ImGuiApi.Raw.Begin((sbyte*)nativeTestText0.pointer, null, 0); - try { Assert.True(modal.Draw(m_runtime.context)); } - finally { ImGuiApi.End(); ImGuiApi.Render(); } + try + { + Assert.True(modal.Draw(m_runtime.context)); + } + finally + { + ImGuiApi.End(); + ImGuiApi.Render(); + } + Thread.Sleep(5); Assert.True(modal.TryGetPresentation(out presentation)); } + Assert.False(presentation.isVisible); Assert.Equal(before, File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath))); Assert.Single(m_runtime.interactions.documents.documents); @@ -196,12 +233,20 @@ public void SourceSettingsUndoRefusesToOverwriteAnExternalSidecarEdit() private AssetPath CreateFunction(params string[] exports) { - if (exports.Length == 0) exports = ["Evaluate"]; + if (exports.Length == 0) + exports = ["Evaluate"]; AssetPath path = AssetPath.Project("Function.ishadersource"); - File.WriteAllText(Path.Combine(m_root, "Assets", path.localPath), string.Join("\n", - exports.Select(static function => $"float {function}(float input) {{ return input; }}"))); + File.WriteAllText( + Path.Combine(m_root, "Assets", path.localPath), + string.Join("\n", exports.Select(static function => $"float {function}(float input) {{ return input; }}")) + ); _ = m_assets.Import(path); - var settings = new ShaderSourceImportSettings { languageId = "inno.shader-language.bgfx-sc", implementationId = "bgfx", exports = exports }; + var settings = new ShaderSourceImportSettings + { + languageId = "inno.shader-language.bgfx-sc", + implementationId = "bgfx", + exports = exports + }; Assert.True(m_assets.SaveImportSettings(path, settings, m_assets.GetImportSettings(path).fingerprint)); return path; } @@ -223,32 +268,28 @@ public void FileSelectionOpensOnlyTheShaderCanvasAndNonShaderSelectionKeepsItsDo } [Theory] - [InlineData(1f, false)] - [InlineData(1.1f, false)] - [InlineData(1.25f, false)] - [InlineData(1.5f, false)] - [InlineData(2f, false)] - [InlineData(1f, true)] - [InlineData(1.1f, true)] - [InlineData(1.25f, true)] - [InlineData(1.5f, true)] - [InlineData(2f, true)] + [MemberData(nameof(CanvasMetrics))] + [Trait("Acceptance", "Visual")] public unsafe void NativeCanvasResizeCompletesLayoutAtFractionalUiScales( - float scale, + float zoom, + float density, + float width, + float height, bool emptyGraph ) { AssetFileEntry entry = Create("Resize.ishader"); SelectAndDraw(entry); GraphDocumentController controller = Controller(entry); - if (emptyGraph) controller.ReplaceDocument(new GraphDocument(), "Prepare Empty Canvas"); + if (emptyGraph) + controller.ReplaceDocument(new GraphDocument(), "Prepare Empty Canvas"); ulong revision = controller.revision; byte[] disk = File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath)); - ImGuiApi.Raw.GetStyle()->ItemSpacing = Unsafe.BitCast(new Vector2(6, 4) * scale); - ImGuiApi.Raw.GetStyle()->WindowPadding = Unsafe.BitCast(new Vector2(8, 7) * scale); - ImGuiApi.Raw.GetIO()->DisplayFramebufferScale = Unsafe.BitCast(new Vector2(scale)); - - Vector2[] sizes = [new(1200, 800), new(640, 400), new(280, 220), new(96, 64), new(32, 32), - new(1200, 32), new(32, 800), new(1200, 800)]; + ImGuiApi.Raw.GetIO()->DisplaySize = Unsafe.BitCast(new(width, height)); + ImGuiApi.Raw.GetIO()->DisplayFramebufferScale = Unsafe.BitCast(new(density)); + m_ui.widgets.style.SetZoom(zoom); + using (m_ui.Enter()) + m_ui.widgets.SetupStyle(); + Vector2[] sizes = [new(width, height), new(640, 400), new(280, 220), new(96, 64), new(32, 32), new(width, 32), new(32, height), new(width, height)]; for (int cycle = 0; cycle < 3; cycle++) foreach (Vector2 size in sizes) { @@ -320,7 +361,10 @@ public void NativeMouseGesturesMoveNodesAndEachReleaseHasItsOwnUndo() SelectAndDraw(entry); GraphDocumentController controller = Controller(entry); var graph = new GraphDocument(); - var node = new GraphNodeRecord(new("probe"), "tests.shader-ui-probe") { position = new(0, 0) }; + var node = new GraphNodeRecord(new("probe"), "tests.shader-ui-probe") + { + position = new(0, 0) + }; graph.AddNode(node); controller.ReplaceDocument(graph, "Prepare Canvas Probe"); Assert.True(m_runtime.interactions.For("panel/rendering.shader-editor", entry).Execute("shader/focus")); @@ -353,20 +397,34 @@ public void FormatPlacesOutputsOnTheRightWithoutChangingShaderSemanticsAndIsUndo node.position = node.definitionId == ShaderGraphDocument.outputDefinitionId ? new(0, node.position.y) : new(900, node.position.y); controller.ReplaceDocument(reversed, "Reverse Shader Layout"); byte[] before = GraphDocumentCodec.Encode(controller.document, m_serialization); - string semantic = ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(controller.document, - new Dictionary(), m_serialization), m_serialization); + string semantic = ShaderGraphArtifact.GetSemanticHash( + ShaderGraphArtifact.Encode(controller.document, new Dictionary(), m_serialization), + m_serialization + ); Assert.True(m_runtime.interactions.For("panel/rendering.shader-editor", entry).Execute("shader/format")); GraphNodeRecord[] outputs = controller.document.nodes.Where(static node => node.definitionId == ShaderGraphDocument.outputDefinitionId).ToArray(); - Assert.All(outputs, output => Assert.True(output.position.x > controller.document.nodes - .Where(node => node.id != output.id && ShaderGraphDocument.Read(node, "stage", "", m_serialization, - AssetSerializationContext.Create(m_assets)) == output.id.value).Max(static node => node.position.x))); - Assert.Equal(semantic, ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(controller.document, - new Dictionary(), m_serialization), m_serialization)); + Assert.All( + outputs, + output => Assert.True(output.position.x > controller.document.nodes.Where(node => node.id != output.id && ShaderGraphDocument.Read(node, "stage", "", m_serialization, AssetSerializationContext.Create(m_assets)) == output.id.value).Max(static node => node.position.x)) + ); + Assert.Equal( + semantic, + ShaderGraphArtifact.GetSemanticHash(ShaderGraphArtifact.Encode(controller.document, new Dictionary(), m_serialization), m_serialization) + ); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(before, GraphDocumentCodec.Encode(controller.document, m_serialization)); } private unsafe void DragProbe(Vector2 distance) + { + BeginDragProbe(distance); + ImGuiApi.Raw.AddMouseButtonEvent(ImGuiApi.Raw.GetIO(), 0, 0); + Draw(); + Tick(); + m_probeOffset += distance; + } + + private unsafe void BeginDragProbe(Vector2 distance) { // A single framed summary-only node is centered in the canvas (82 px tall). Vector2 start = (m_canvasMinimum + m_canvasMaximum) * 0.5f + new Vector2(0, -25) + m_probeOffset; @@ -377,10 +435,56 @@ private unsafe void DragProbe(Vector2 distance) Draw(); ImGuiApi.Raw.AddMousePosEvent(ImGuiApi.Raw.GetIO(), start.X + distance.X, start.Y + distance.Y); Draw(); - ImGuiApi.Raw.AddMouseButtonEvent(ImGuiApi.Raw.GetIO(), 0, (false ? (byte)1 : (byte)0)); + } + + [Fact] + public unsafe void SaveDuringANativeDragCompletesOneDomainEditBeforeWriting() + { + AssetFileEntry entry = Create("SaveDrag.ishader"); + SelectAndDraw(entry); + GraphDocumentController controller = Controller(entry); + var graph = new GraphDocument(); + var node = new GraphNodeRecord(new("probe"), "tests.shader-ui-probe") { position = new(0f, 0f) }; + graph.AddNode(node); + controller.ReplaceDocument(graph, "Prepare Save Probe"); + Assert.True(m_runtime.interactions.For("panel/rendering.shader-editor", entry).Execute("shader/focus")); + Draw(); Draw(); - Tick(); - m_probeOffset += distance; + GraphPosition original = controller.document.FindNode(node.id)!.position; + BeginDragProbe(new(52f, 37f)); + Assert.Equal(original, controller.document.FindNode(node.id)!.position); + EditorDocumentContext document = Assert.Single(m_runtime.interactions.documents.documents); + Assert.True(m_runtime.interactions.documents.Save(document.documentId)); + GraphPosition saved = controller.document.FindNode(node.id)!.position; + Assert.NotEqual(original, saved); + Assert.Equal(saved, ReadPosition(entry, node.id)); + ImGuiApi.Raw.AddMouseButtonEvent(ImGuiApi.Raw.GetIO(), 0, 0); + Draw(); + Assert.Equal(saved, controller.document.FindNode(node.id)!.position); + Assert.True(m_runtime.interactions.history.Undo().succeeded); + Assert.Equal(original, controller.document.FindNode(node.id)!.position); + Assert.Equal(saved, ReadPosition(entry, node.id)); + } + + [Fact] + [Trait("Acceptance", "Lifecycle")] + public void OneHundredActualShaderDocumentCyclesRetireTheirGraphOwners() + { + AssetFileEntry entry = Create("Lifecycle.ishader"); + byte[] source = File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath)); + for (int iteration = 0; iteration < 100; iteration++) + { + SelectAndDraw(entry); + GraphDocumentController controller = Controller(entry); + controller.AddNode("missing.lifecycle", new(iteration, iteration)); + Tick(); + EditorDocumentContext document = Assert.Single(m_runtime.interactions.documents.documents); + Assert.True(m_runtime.interactions.documents.Close(document.documentId, EditorDocumentCloseMode.Discard)); + Assert.Empty(m_runtime.interactions.documents.documents); + Assert.False(controller.isAvailable); + Assert.False(m_sink.graphs!.TryOpenDocument(AssetId(entry), m_runtime.interactions.history, out _)); + } + Assert.Equal(source, File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath))); } [Fact] @@ -403,8 +507,10 @@ public void ExternalSourceConflictKeepsBothDiskAndUnsavedRecovery() private AssetFileEntry Create(string name) { - File.WriteAllBytes(Path.Combine(m_root, "Assets", name), GraphDocumentCodec.Encode( - ShaderGraphTemplates.CreateRaster(m_serialization, AssetSerializationContext.Create(m_assets)), m_serialization)); + File.WriteAllBytes( + Path.Combine(m_root, "Assets", name), + GraphDocumentCodec.Encode(ShaderGraphTemplates.CreateRaster(m_serialization, AssetSerializationContext.Create(m_assets)), m_serialization) + ); Assert.True(m_assets.Import(AssetPath.Project(name))); Assert.True(m_assets.TryGetFileSystemEntry(AssetPath.Project(name), out AssetFileEntry entry)); return entry; @@ -426,7 +532,13 @@ public void DeleteKeysRemoveTheSelectedComputeStageAndItsDeclarationsAsOneUndo(K input.SetValue("stage", ShaderGraphDocument.Encode(output.id.value, m_serialization, AssetSerializationContext.Create(m_assets))); GraphDocument graph = controller.document.Clone(); graph.AddNode(input); - graph = ShaderGraphBindings.ChangeInput(graph, input.id, new() { id = "computeParameter", kind = ShaderIrInputKind.Uniform }, m_serialization, AssetSerializationContext.Create(m_assets)); + graph = ShaderGraphBindings.ChangeInput( + graph, + input.id, + new() { id = "computeParameter", kind = ShaderIrInputKind.Uniform }, + m_serialization, + AssetSerializationContext.Create(m_assets) + ); controller.ReplaceDocument(graph, "Add Compute Parameter"); byte[] before = GraphDocumentCodec.Encode(controller.document, m_serialization); Assert.True(interaction.Query("shader/delete").isEnabled); @@ -438,14 +550,19 @@ public void DeleteKeysRemoveTheSelectedComputeStageAndItsDeclarationsAsOneUndo(K var keyEvent = new KeyPressedEvent(0, key); m_runtime.HandleKeyPressed(keyEvent); m_logs.Flush(); - Assert.True(controller.document.FindNode(output.id) is null, "Focus: " + m_runtime.interactions.focusedArea + "; cut: " + interaction.Query("shader/cut").isEnabled + "; undo: " + m_runtime.interactions.history.undoName + "; " + string.Join("\n", m_messages.messages)); + Assert.True( + controller.document.FindNode(output.id)is null, + "Focus: " + m_runtime.interactions.focusedArea + "; cut: " + interaction.Query("shader/cut").isEnabled + "; undo: " + m_runtime.interactions.history.undoName + "; " + string.Join( + "\n", + m_messages.messages + ) + ); Assert.Null(controller.document.FindNode(input.id)); ShaderDefinition definition = ShaderGraphDocument.ReadDefinition(controller.document, m_serialization, AssetSerializationContext.Create(m_assets)); Assert.Single(definition.passes); Assert.DoesNotContain(definition.properties, property => property.id.value == "computeParameter"); using var nodes = new ShaderNodeCompilerRegistry(m_types); - Assert.True(new ShaderGraphProgramCompiler(nodes).Lower(controller.document, "bgfx", new Dictionary(), - m_serialization, AssetSerializationContext.Create(m_assets)).succeeded); + Assert.True(new ShaderGraphProgramCompiler(nodes).Lower(controller.document, "bgfx", new Dictionary(), m_serialization, AssetSerializationContext.Create(m_assets)).succeeded); Tick(); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Equal(before, GraphDocumentCodec.Encode(controller.document, m_serialization)); @@ -462,22 +579,34 @@ public void CutAndPasteStageKeepsItsContentsAndDoesNotCreateDuplicatePassDeclara Assert.True(interaction.Execute("shader/create-output", "Compute")); GraphDocumentController controller = Controller(entry); GraphNodeId stage = controller.document.nodes.Last().id; - GraphNodeId child = controller.AddNode("inno.shader.constant", new(10, 20), new Dictionary - { ["stage"] = ShaderGraphDocument.Encode(stage.value, m_serialization, AssetSerializationContext.Create(m_assets)) }); + GraphNodeId child = controller.AddNode( + "inno.shader.constant", + new(10, 20), + new Dictionary { ["stage"] = ShaderGraphDocument.Encode(stage.value, m_serialization, AssetSerializationContext.Create(m_assets)) } + ); Assert.True(interaction.Execute("shader/cut")); Assert.Null(controller.document.FindNode(child)); Assert.True(interaction.Execute("shader/paste")); Assert.Null(controller.document.FindNode(child)); Assert.Equal(2, controller.document.nodes.Count(node => node.definitionId == "inno.shader.stage-output" && ReadStage(node).stage != ShaderStage.Compute)); GraphNodeRecord pasted = controller.document.nodes.Single(node => node.definitionId == "inno.shader.stage-output" && ReadStage(node).stage == ShaderStage.Compute); - Assert.Contains(controller.document.nodes, node => ShaderGraphDocument.Read(node, "stage", "", m_serialization, AssetSerializationContext.Create(m_assets)) == pasted.id.value); + Assert.Contains( + controller.document.nodes, + node => ShaderGraphDocument.Read(node, "stage", "", m_serialization, AssetSerializationContext.Create(m_assets)) == pasted.id.value + ); Assert.False(interaction.Execute("shader/duplicate")); ShaderDefinition definition = ShaderGraphDocument.ReadDefinition(controller.document, m_serialization, AssetSerializationContext.Create(m_assets)); Assert.Equal(2, definition.passes.Length); Assert.Equal(2, definition.passes.Select(pass => pass.name).Distinct().Count()); Assert.True(m_runtime.interactions.history.Undo().succeeded); Assert.Single(ShaderGraphDocument.ReadDefinition(controller.document, m_serialization, AssetSerializationContext.Create(m_assets)).passes); - ShaderGraphStageSettings ReadStage(GraphNodeRecord node) => ShaderGraphDocument.Read(node, "settings", new ShaderGraphStageSettings(), m_serialization, AssetSerializationContext.Create(m_assets)); + ShaderGraphStageSettings ReadStage(GraphNodeRecord node) => ShaderGraphDocument.Read( + node, + "settings", + new ShaderGraphStageSettings(), + m_serialization, + AssetSerializationContext.Create(m_assets) + ); } [Fact] @@ -531,9 +660,7 @@ public void SourceLibrariesOwnTheirNestedCreationCatalogWithoutLeakingSeparators Assert.True(m_assets.SaveImportSettings(source, settings, snapshot.fingerprint)); AssetFileEntry entry = Create("Catalog.ishader"); SelectAndDraw(entry); - - EditorMenuItem create = m_runtime.interactions.For("panel/rendering.shader-editor", entry) - .BuildMenu().items.Single(item => item.label == "Create"); + EditorMenuItem create = m_runtime.interactions.For("panel/rendering.shader-editor", entry).BuildMenu().items.Single(item => item.label == "Create"); EditorMenuItem functions = create.children.Single(item => item.label == "Functions"); EditorMenuItem rendering2D = functions.children.Single(item => item.label == "Rendering 2D"); Assert.True(rendering2D.separatorBefore); @@ -549,8 +676,7 @@ public void PluginNodePresentationOwnsItsCreationGroupAndSeparator() { AssetFileEntry entry = Create("NodeCatalog.ishader"); SelectAndDraw(entry); - EditorMenuItem create = m_runtime.interactions.For("panel/rendering.shader-editor", entry) - .BuildMenu().items.Single(item => item.label == "Create"); + EditorMenuItem create = m_runtime.interactions.For("panel/rendering.shader-editor", entry).BuildMenu().items.Single(item => item.label == "Create"); EditorMenuItem domain = create.children.Single(item => item.label == "Domain"); EditorMenuItem extension = domain.children.Single(item => item.label == "Test Extension"); Assert.True(extension.separatorBefore); @@ -566,13 +692,38 @@ public void FormatOrdersSourcesByDestinationPortsToAvoidInputWireCrossings() AssetFileEntry entry = Create("Crossings.ishader"); SelectAndDraw(entry); var graph = new GraphDocument(); - var top = new GraphNodeRecord(new("top"), "inno.shader.constant") { position = new(800, 600) }; - var middle = new GraphNodeRecord(new("middle"), "inno.shader.constant") { position = new(800, 400) }; - var bottom = new GraphNodeRecord(new("bottom"), "inno.shader.constant") { position = new(800, 200) }; - var evaluate = new GraphNodeRecord(new("evaluate"), "inno.shader.construct") { position = new(400, 300) }; + var top = new GraphNodeRecord(new("top"), "inno.shader.constant") + { + position = new(800, 600) + }; + var middle = new GraphNodeRecord(new("middle"), "inno.shader.constant") + { + position = new(800, 400) + }; + var bottom = new GraphNodeRecord(new("bottom"), "inno.shader.constant") + { + position = new(800, 200) + }; + var evaluate = new GraphNodeRecord(new("evaluate"), "inno.shader.construct") + { + position = new(400, 300) + }; evaluate.SetValue("type", ShaderGraphDocument.Encode("float3", m_serialization, SerializationContext.empty)); - var output = new GraphNodeRecord(new("output"), "tests-output") { position = new(0, 300) }; - foreach (GraphNodeRecord node in new[] { top, middle, bottom, evaluate, output }) graph.AddNode(node); + var output = new GraphNodeRecord(new("output"), "tests-output") + { + position = new(0, 300) + }; + foreach (GraphNodeRecord node in new[] + { + top, + middle, + bottom, + evaluate, + output + } + + ) + graph.AddNode(node); graph.AddEdge(new(new("top-edge"), new(top.id, new("value")), new(evaluate.id, new("component.0")))); graph.AddEdge(new(new("middle-edge"), new(middle.id, new("value")), new(evaluate.id, new("component.1")))); graph.AddEdge(new(new("bottom-edge"), new(bottom.id, new("value")), new(evaluate.id, new("component.2")))); @@ -598,7 +749,14 @@ public void SavingAppliesTheGraphWithoutDependingOnFileWatcherAndUndoStaysDraftO GraphDocument graph = controller.document.Clone(); ShaderDefinition definition = ShaderGraphDocument.ReadDefinition(graph, m_serialization, AssetSerializationContext.Create(m_assets)); definition.name = "Saved Change"; - graph.SetMetadata(ShaderGraphDocument.definitionKey, ShaderGraphDocument.Encode(m_serialization.Serialize(definition, AssetSerializationContext.Create(m_assets)), m_serialization, AssetSerializationContext.Create(m_assets))); + graph.SetMetadata( + ShaderGraphDocument.definitionKey, + ShaderGraphDocument.Encode( + m_serialization.Serialize(definition, AssetSerializationContext.Create(m_assets)), + m_serialization, + AssetSerializationContext.Create(m_assets) + ) + ); controller.ReplaceDocument(graph, "Rename Shader"); Tick(); Assert.Equal(before, asset.contentVersion); @@ -647,18 +805,19 @@ public void RestartRecoversOnlyTheLatestDraftAndNeverAppliesIt(bool undo) byte[] saved = File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath)); GraphNodeId node = Controller(entry).AddNode("tests.unsaved", new(0, 0)); Tick(); - if (undo) Assert.True(m_runtime.interactions.history.Undo().succeeded); + if (undo) + Assert.True(m_runtime.interactions.history.Undo().succeeded); // Shutdown must capture even the final undo that has not had an update frame yet. m_runtime.Dispose(); Assert.Equal(saved, File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath))); m_runtime = CreateRuntime(); SelectAndDraw(entry); - Assert.Equal(!undo, Controller(entry).document.FindNode(node) is not null); + Assert.Equal(!undo, Controller(entry).document.FindNode(node)is not null); Assert.Equal(saved, File.ReadAllBytes(Path.Combine(m_root, "Assets", entry.assetPath.localPath))); } [Fact] - public async Task DraftCompilationNeverReplacesTheCanonicalArtifactAndLayoutChangesDoNotRecompile() + public void DraftCompilationNeverReplacesTheCanonicalArtifactAndLayoutChangesDoNotRecompile() { AssetFileEntry entry = Create("Preview.ishader"); ShaderAsset shader = m_assets.Load(entry.assetPath); @@ -668,7 +827,11 @@ public async Task DraftCompilationNeverReplacesTheCanonicalArtifactAndLayoutChan var compilation = new EditorShaderCompilation(m_artifacts, capabilities); RenderShaderArtifact? canonical = null; for (int i = 0; i < 100 && canonical is null; i++) - { _ = m_artifacts.GetShaderArtifact(shader, RenderShaderVariant.empty, capabilities, out canonical); await Task.Yield(); } + { + _ = m_artifacts.GetShaderArtifact(shader, RenderShaderVariant.empty, capabilities, out canonical); + Thread.Sleep(1); + } + Assert.NotNull(canonical); GraphDocument draft = m_source.Read(entry.assetPath).document; var context = AssetSerializationContext.Create(m_assets); @@ -678,22 +841,27 @@ public async Task DraftCompilationNeverReplacesTheCanonicalArtifactAndLayoutChan definition.properties[0] = property; draft.SetMetadata(ShaderGraphDocument.definitionKey, ShaderGraphDocument.Encode(m_serialization.Serialize(definition, context), m_serialization, context)); Guid previewId = Guid.NewGuid(); - async Task Poll(ulong revision) + EditorShaderDraftCompilationSnapshot Poll(ulong revision) { EditorShaderDraftCompilationSnapshot result = compilation.RequestDraft(previewId, draft, revision, RenderShaderVariant.empty); for (int i = 0; i < 100 && result.state == EditorShaderCompilationState.Compiling; i++) - { await Task.Yield(); result = compilation.RequestDraft(previewId, draft, revision, RenderShaderVariant.empty); } + { + Thread.Sleep(1); + result = compilation.RequestDraft(previewId, draft, revision, RenderShaderVariant.empty); + } + return result; } - var preview = await Poll(1); + + var preview = Poll(1); Assert.Equal(EditorShaderCompilationState.Succeeded, preview.state); Assert.NotNull(preview.artifact); Assert.NotEqual(canonical!.contentHash, preview.artifact!.contentHash); draft.nodes[0].position = new(123, 456); - Assert.Same(preview.artifact, (await Poll(2)).artifact); + Assert.Same(preview.artifact, Poll(2).artifact); GraphNodeRecord output = draft.nodes.First(value => value.definitionId == ShaderGraphDocument.outputDefinitionId); draft.RemoveNode(output.id); - var failed = await Poll(3); + var failed = Poll(3); Assert.Equal(EditorShaderCompilationState.Failed, failed.state); Assert.True(failed.usingLastGood); Assert.Same(preview.artifact, failed.artifact); @@ -707,8 +875,12 @@ private EditorInteractionRuntime CreateRuntime() { RenderTextureFormat[] formats = Enum.GetValues(); var capabilities = new GraphicsCapabilities(GraphicsApi.Metal, GraphicsCapability.None, new(256, 8, 8192, 16), formats, formats, formats, formats, false, false); - var runtime = new EditorInteractionRuntime(new EditorContext(m_root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), m_types, m_logs, - [m_types, m_serialization, m_assets, m_sink, new EditorReloadCoordinator(), new EditorShaderCompilation(m_artifacts, capabilities), new EmptyPreviews()]); + var runtime = new EditorInteractionRuntime( + new EditorContext(m_root, new EditorKeyboardPolicy(Inno.Core.Input.KeyModifier.Control, "Super")), + m_types, + m_logs, + [m_types, m_serialization, m_identities, m_assets, m_sink, m_ui, new EditorReloadCoordinator(), new EditorShaderCompilation(m_artifacts, capabilities), new EmptyPreviews()] + ); runtime.Start(); return runtime; } @@ -723,14 +895,27 @@ public void MaterialGesturesAreIndependentAndOnlySavePublishesTheDraft() MaterialAsset canonical = m_assets.Load(path); void Set( float value, - bool finish = true + string? mergeKey = null ) { MaterialAsset candidate = documents.Read(id); candidate.Set(property, MaterialValue.FromColor(new Inno.Core.Mathematics.Color(value, 1, 1, 1))); - documents.Replace(id, candidate, finish); + documents.Replace(id, candidate, mergeKey); + } + + using (var gesture = new EditorEditGesture(m_runtime.interactions.history, "First Color")) + { + Set(0.1f, gesture.mergeKey); + Set(0.2f, gesture.mergeKey); + gesture.Commit(); } - Set(0.1f, false); Set(0.2f, false); documents.Commit(id); - Set(0.3f, false); Set(0.4f, true); + + using (var gesture = new EditorEditGesture(m_runtime.interactions.history, "Second Color")) + { + Set(0.3f, gesture.mergeKey); + Set(0.4f, gesture.mergeKey); + gesture.Commit(); + } + Assert.False(canonical.TryGet(property, out _)); Assert.Equal(original, File.ReadAllBytes(Path.Combine(m_root, "Assets", path.localPath))); Assert.True(m_runtime.interactions.history.Undo().succeeded); @@ -752,6 +937,24 @@ void Set( Assert.Equal(0.2f, value.vector.x); } + [Fact] + public void CancelledMaterialGestureRestoresDraftDirtyStateAndLeavesNoUndoEntry() + { + (AssetPath path, ShaderPropertyId property) = CreateMaterial(); + MaterialDocuments documents = Assert.IsType(m_sink.materials); + Guid id = documents.Open(path); + Assert.False(m_runtime.interactions.history.canUndo); + using var gesture = new EditorEditGesture(m_runtime.interactions.history, "Cancelled Material Edit"); + MaterialAsset candidate = documents.Read(id); + candidate.Set(property, MaterialValue.FromColor(new Inno.Core.Mathematics.Color(0.7f, 1, 1, 1))); + documents.Replace(id, candidate, gesture.mergeKey); + Assert.True(Assert.Single(m_runtime.interactions.documents.documents).isDirty); + Assert.True(gesture.Cancel().succeeded); + Assert.False(documents.Read(id).TryGet(property, out _)); + Assert.False(Assert.Single(m_runtime.interactions.documents.documents).isDirty); + Assert.False(m_runtime.interactions.history.canUndo); + } + [Fact] public void MaterialSaveConflictKeepsDiskAndRecoveredDraftDistinct() { @@ -795,6 +998,7 @@ void External(float value) Assert.True(m_assets.Import(path)); Tick(); } + External(0.2f); Assert.True(documents.Read(id).TryGet(property, out MaterialValue inherited)); Assert.Equal(0.2f, inherited.vector.x); @@ -820,32 +1024,75 @@ public void MultipleMaterialSamplesCommitAsOneIndependentUndoWithoutPublishing() MaterialDocuments documents = Assert.IsType(m_sink.materials); Guid first = documents.Open(firstPath), second = documents.Open(secondPath); byte[] original = sources.Read(firstPath).bytes; - void Sample(float value) - { + void Sample( + float value, + string? mergeKey + ) { var candidates = new Dictionary(); - foreach (Guid id in new[] { first, second }) + foreach (Guid id in new[] + { + first, + second + } + + ) { MaterialAsset candidate = documents.Read(id); candidate.Set(property, MaterialValue.FromColor(new Inno.Core.Mathematics.Color(value, 1, 1, 1))); candidates.Add(id, candidate); } - documents.ReplaceMany(candidates, false); + + documents.ReplaceMany(candidates, mergeKey); + } + + using (var gesture = new EditorEditGesture(m_runtime.interactions.history, "First Selection Color")) + { + Sample(0.2f, gesture.mergeKey); + Sample(0.4f, gesture.mergeKey); + gesture.Commit(); + } + + using (var gesture = new EditorEditGesture(m_runtime.interactions.history, "Second Selection Color")) + { + Sample(0.6f, gesture.mergeKey); + gesture.Commit(); } - Sample(0.2f); Sample(0.4f); documents.CommitMany([first, second]); - Sample(0.6f); documents.CommitMany([first, second]); + Assert.True(m_runtime.interactions.history.canUndo); + Assert.True(m_runtime.interactions.history.Undo().succeeded); - foreach (Guid id in new[] { first, second }) + foreach (Guid id in new[] + { + first, + second + } + + ) { Assert.True(documents.Read(id).TryGet(property, out MaterialValue value)); Assert.Equal(0.4f, value.vector.x); } + Assert.True(m_runtime.interactions.history.Undo().succeeded); - foreach (Guid id in new[] { first, second }) Assert.False(documents.Read(id).TryGet(property, out _)); + foreach (Guid id in new[] + { + first, + second + } + + ) + Assert.False(documents.Read(id).TryGet(property, out _)); Assert.All(m_runtime.interactions.documents.documents, document => Assert.False(document.isDirty)); Assert.Equal(original, sources.Read(firstPath).bytes); Assert.Equal(original, sources.Read(secondPath).bytes); Assert.True(m_runtime.interactions.history.Redo().succeeded); - foreach (Guid id in new[] { first, second }) Assert.True(documents.Read(id).TryGet(property, out _)); + foreach (Guid id in new[] + { + first, + second + } + + ) + Assert.True(documents.Read(id).TryGet(property, out _)); } [Fact] @@ -859,20 +1106,24 @@ public void CommonMaterialEditingPreservesUntouchedMixedComponentsAndTextureRefe var firstTexture = new TextureAsset(1, 1, TextureColorSpace.Linear, "first"); var secondTexture = new TextureAsset(1, 1, TextureColorSpace.Linear, "second"); first = MaterialValue.FromTexture(firstTexture); - edited = first; edited.sampler = new(RenderSamplerFilter.Point, RenderSamplerAddressMode.Clamp, RenderSamplerAddressMode.Clamp, RenderSamplerAddressMode.Clamp); + edited = first; + edited.sampler = new(RenderSamplerFilter.Point, RenderSamplerAddressMode.Clamp, RenderSamplerAddressMode.Clamp, RenderSamplerAddressMode.Clamp); other = MaterialValue.FromTexture(secondTexture); result = ShaderPropertyInspector.ApplyEdit(ShaderPropertyType.Texture2D, first, edited, other); Assert.Same(secondTexture, result.texture); Assert.Equal(RenderSamplerFilter.Point, result.sampler.filter); } - [Fact] public void PipelineSettingsDraftsPreserveReferencesAndPublishOnlyAfterSave() { (AssetPath materialPath, _) = CreateMaterial(); MaterialAsset material = m_assets.Load(materialPath); - var originalSettings = new WorkflowPipelineSettings { material = material, exposure = 1 }; + var originalSettings = new WorkflowPipelineSettings + { + material = material, + exposure = 1 + }; var initial = new RenderPipelineAsset { pipelineTypeId = "tests.workflow.pipeline", @@ -907,7 +1158,6 @@ public void PipelineSettingsDraftsPreserveReferencesAndPublishOnlyAfterSave() Assert.True(m_runtime.interactions.documents.Revert(document.documentId)); Assert.Same(material, Read(documents.Read(id)).material); Assert.Equal(3, Read(documents.Read(id)).exposure); - WorkflowPipelineSettings Read(RenderPipelineAsset pipeline) { var result = new WorkflowPipelineSettings(); @@ -920,7 +1170,11 @@ WorkflowPipelineSettings Read(RenderPipelineAsset pipeline) public void PipelineMissingSettingsKeepOpaqueBytesThroughHistoryAndRecovery() { var unknown = new SerializedRenderExtensionState(Guid.NewGuid(), new byte[] { 19, 42, 7 }); - var source = new RenderPipelineAsset { pipelineTypeId = "tests.missing-pipeline", pipelineState = unknown }; + var source = new RenderPipelineAsset + { + pipelineTypeId = "tests.missing-pipeline", + pipelineState = unknown + }; AssetPath path = AssetPath.Project("Missing.irenderpipeline"); AssetSourceStore sources = m_assets.CreateSourceStore(); sources.Save(path, sources.Encode(source), null); @@ -947,8 +1201,11 @@ public void PipelineMissingSettingsKeepOpaqueBytesThroughHistoryAndRecovery() [StableTypeId("8c096b6f-92c3-48ed-917e-955858d6a65c")] public sealed unsafe class WorkflowPipelineSettings : ISerializable { - [SerializableProperty] public MaterialAsset? material { get; set; } - [SerializableProperty] public float exposure { get; set; } + [SerializableProperty] + public MaterialAsset? material { get; set; } + + [SerializableProperty] + public float exposure { get; set; } } private (AssetPath, ShaderPropertyId) CreateMaterial() @@ -978,12 +1235,25 @@ private Guid AssetId(AssetFileEntry entry) Assert.True(m_assets.TryGetInfo(entry.assetPath, out AssetInfo? info)); return info!.persistentId; } - private void SelectAndDraw(AssetFileEntry entry) { m_runtime.interactions.SetSelection(entry); Draw(); } - private void Tick() { m_runtime.Update(new(1f / 60, 0, true)); Draw(); } + + private void SelectAndDraw(AssetFileEntry entry) + { + Assert.True(m_assets.TryGetFileSystemEntry(entry.assetPath, out AssetFileEntry current)); + m_runtime.interactions.SetSelection(current); + Draw(); + } + + private void Tick() + { + m_runtime.Update(new(1f / 60, 0, true)); + Draw(); + } + private unsafe void Draw(Vector2? size = null) { EditorPanelExtension panel = Assert.Single(m_runtime.panels, panel => panel.id == "rendering.shader-editor"); panel.isOpen = true; + using IDisposable uiScope = m_ui.Enter(); ImGuiApi.NewFrame(); ImGuiApi.SetNextWindowPos(new(0, 0)); ImGuiApi.SetNextWindowSize(size ?? new(1200, 800)); @@ -995,12 +1265,17 @@ private unsafe void Draw(Vector2? size = null) m_canvasMinimum = ImGuiApi.GetItemRectMin(); m_canvasMaximum = ImGuiApi.GetItemRectMax(); } - finally { ImGuiApi.End(); ImGuiApi.Render(); } + finally + { + ImGuiApi.End(); + ImGuiApi.Render(); + } } public unsafe void Dispose() { m_runtime.Dispose(); + m_ui.Dispose(); ImGuiApi.Raw.DestroyContext(m_imgui); m_artifacts.Dispose(); m_assets.Dispose(); @@ -1042,10 +1317,11 @@ public override void Draw(ShaderNodeDrawContext context) public sealed unsafe class CanvasProbeCompiler : IShaderNodeCompiler { public string definitionId => "tests.shader-ui-probe"; + public IReadOnlyList GetPorts(ShaderNodeDescriptionContext context) => []; - public IReadOnlyDictionary Lower(ShaderNodeLoweringContext context) - => new Dictionary(); + public IReadOnlyDictionary Lower(ShaderNodeLoweringContext context) => new Dictionary(); } + [EditorModule("tests.shader-workflow", order: 160)] public sealed unsafe class WorkflowProbe : EditorModule { @@ -1054,25 +1330,27 @@ public WorkflowProbe( MaterialDocuments materials, PipelineDocuments pipelines, WorkflowSink sink - ) { sink.graphs = graphs; sink.materials = materials; sink.pipelines = pipelines; } + ) { + sink.graphs = graphs; + sink.materials = materials; + sink.pipelines = pipelines; + } } private sealed class WorkflowCompiler : IShaderCompilerToolchain { public string implementationId => "tests.workflow"; public IReadOnlyList supportedSourceLanguages => []; + public ShaderCompileTarget CreateTarget( GraphicsCapabilities capabilities, bool optimize = true, bool debugInformation = false - ) - => new("tests:workflow", capabilities, optimize, debugInformation); + ) => new("tests:workflow", capabilities, optimize, debugInformation); public ValueTask CompileAsync( ShaderStageToolRequest request, CancellationToken cancellationToken - ) - => ValueTask.FromResult(new ShaderStageToolResult([1, 2, 3], request.stage.inputs.Where(input => input.kind == ShaderIrInputKind.Uniform) - .Select(input => new ShaderStageBinding(input.id, "native_" + input.id, 0)), [])); + ) => ValueTask.FromResult(new ShaderStageToolResult([1, 2, 3], request.stage.inputs.Where(input => input.kind == ShaderIrInputKind.Uniform).Select(input => new ShaderStageBinding(input.id, "native_" + input.id, 0)), [])); } private sealed class EmptyPreviews : IEditorPreviewService @@ -1080,24 +1358,42 @@ private sealed class EmptyPreviews : IEditorPreviewService public bool TryRender( EditorViewportComposition composition, out EditorPreviewHandle handle - ) { handle = default; return false; } - public void ReleaseRendered(string viewportId) { } + ) { + handle = default; + return false; + } + + public void ReleaseRendered(string viewportId) + { + } + public uint deviceGeneration => 1; + public bool TryGetTexture( TextureAsset texture, out EditorPreviewHandle handle - ) { handle = default; return false; } + ) { + handle = default; + return false; + } + public bool TryGetTextureArtifact( RenderTextureArtifactReference texture, int pixelWidth, int pixelHeight, out EditorPreviewHandle handle - ) { handle = default; return false; } + ) { + handle = default; + return false; + } + public void Draw( EditorPreviewHandle handle, Vector2 logicalSize ) => throw new InvalidOperationException("No texture was requested by this workflow."); public bool Release(EditorPreviewHandle handle) => false; - public void ReleaseAll() { } + public void ReleaseAll() + { + } } } diff --git a/tests/scripting/Inno.Scripting.Compiler.Tests/CSharp14ScriptingTests.cs b/tests/scripting/Inno.Scripting.Compiler.Tests/CSharp14ScriptingTests.cs index 24f7e9e22..0d39a5be1 100644 --- a/tests/scripting/Inno.Scripting.Compiler.Tests/CSharp14ScriptingTests.cs +++ b/tests/scripting/Inno.Scripting.Compiler.Tests/CSharp14ScriptingTests.cs @@ -24,6 +24,31 @@ namespace Inno.Scripting.Compiler.Tests; /// public sealed class CSharp14ScriptingTests { + [Theory] + [InlineData("LangVersion", "preview")] + [InlineData("ImplicitUsings", "enable")] + [InlineData("DefineConstants", "UNEXPECTED")] + [InlineData("DisableImplicitConfigurationDefines", "false")] + public async Task IdeRejectsOverridesOfTheFrozenCompilerContract( + string property, + string value + ) { + using var fixture = new CompilerFixture(); + fixture.Write("Probe.cs", "public sealed class Probe { }"); + fixture.Rescan(); + fixture.compiler.GenerateProjectFiles(); + string project = Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj"); + string host = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "..", "..", "..", + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet")); + DotNetSdkDescriptor sdk = await DotNetSdkResolver.ResolveAsync(host, project); + using var output = new StringWriter(); + using var errors = new StringWriter(); + await Assert.ThrowsAsync(() => ToolchainEnvironment.RunAsync(sdk.hostPath, + [sdk.cliPath, "build", project, "--disable-build-servers", "-m:1", "-nr:false", "-p:" + property + "=" + value], + fixture.projectRoot, CancellationToken.None, DotNetSdkEnvironment.Create(sdk.hostPath), output, errors)); + Assert.Contains("IDE compiler input '" + property + "' differs", output.ToString() + errors, StringComparison.Ordinal); + } + /// /// Projects installed extension properties and field-backed plugin properties into the IDE contract. /// @@ -81,9 +106,7 @@ public static class Consumer string projectPath = Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj"); string host = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); DotNetSdkDescriptor sdk = await DotNetSdkResolver.ResolveAsync(host, projectPath); - await ToolchainEnvironment.RunAsync(sdk.hostPath, - [sdk.cliPath, "build", projectPath, "--disable-build-servers", "-m:1", "-nr:false", "-v:quiet"], - fixture.projectRoot, CancellationToken.None, DotNetSdkEnvironment.Create(sdk.hostPath)); + _ = await BuildIdeAsync(sdk, projectPath, fixture.projectRoot); } /// @@ -206,12 +229,30 @@ int Produce() Assert.Equal("14.0", Assert.Single(project.Descendants("LangVersion")).Value); string host = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); DotNetSdkDescriptor sdk = await DotNetSdkResolver.ResolveAsync(host, projectPath); - string output = await ToolchainEnvironment.CaptureOutputAsync(sdk.hostPath, - [sdk.cliPath, "build", projectPath, "--disable-build-servers", "-m:1", "-nr:false", "-v:quiet"], - fixture.projectRoot, CancellationToken.None, DotNetSdkEnvironment.Create(sdk.hostPath)); + string output = await BuildIdeAsync(sdk, projectPath, fixture.projectRoot); Assert.DoesNotContain("CS8032", output, StringComparison.Ordinal); } + private static async Task BuildIdeAsync( + DotNetSdkDescriptor sdk, + string project, + string directory + ) { + using var output = new StringWriter(); + using var errors = new StringWriter(); + try + { + await ToolchainEnvironment.RunAsync(sdk.hostPath, + [sdk.cliPath, "build", project, "--disable-build-servers", "-m:1", "-nr:false", "-v:quiet"], + directory, CancellationToken.None, DotNetSdkEnvironment.Create(sdk.hostPath), output, errors); + return output.ToString(); + } + catch (InvalidOperationException failure) + { + throw new InvalidOperationException(output + Environment.NewLine + errors, failure); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] private static WeakReference InvokePublicSemantics(string assemblyPath) { @@ -236,40 +277,42 @@ private static WeakReference InvokePublicSemantics(string assemblyPath) /// Invalidates reference and script artifacts when framework bytes change without changing the public contract. /// [Fact] - [Trait("ProcessIsolation", "Required")] public void FrameworkContentChangesSelectNewArtifacts() { - using var fixture = new CompilerFixture(); + var environment = new MutableCompilationEnvironment(); + using var fixture = new CompilerFixture(compilationEnvironment: environment); fixture.Write("Counter.cs", "public sealed class Counter { public int count { get; set; } }"); fixture.Rescan(); - string dotnetRoot = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "..", "..", "..")); - string source = Directory.EnumerateDirectories(Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref")) - .Where(path => Path.GetFileName(path).StartsWith("10.0.", StringComparison.Ordinal)) - .OrderByDescending(path => Version.Parse(Path.GetFileName(path))).First(); - string selectedRoot = Path.Combine(fixture.projectRoot, "Framework"); - string destination = Path.Combine(selectedRoot, "packs", "Microsoft.NETCore.App.Ref", Path.GetFileName(source), "ref", "net10.0"); + string host = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + ScriptCompilationInputs selected = new Inno.Build.SdkScriptCompilationEnvironment(host, fixture.projectRoot).Capture(); + string destination = Path.Combine(fixture.projectRoot, "Framework"); Directory.CreateDirectory(destination); - foreach (string path in Directory.EnumerateFiles(Path.Combine(source, "ref", "net10.0"), "*.dll")) + foreach (string path in selected.frameworkReferences) File.Copy(path, Path.Combine(destination, Path.GetFileName(path))); - string? previous = Environment.GetEnvironmentVariable("DOTNET_ROOT"); - try - { - Environment.SetEnvironmentVariable("DOTNET_ROOT", selectedRoot); - ScriptCompilationResult first = fixture.Compile(); - Assert.True(first.success, string.Join(Environment.NewLine, first.diagnostics)); - string referenceRoot = Path.Combine(fixture.projectRoot, "Library", "ScriptApi", "Runtime"); - string[] references = Directory.GetDirectories(referenceRoot); - using (FileStream changed = new(Path.Combine(destination, "System.Runtime.dll"), FileMode.Append)) - changed.WriteByte(0); - ScriptCompilationResult second = fixture.Compile(); - Assert.True(second.success, string.Join(Environment.NewLine, second.diagnostics)); - Assert.NotEqual(first.outputDirectory, second.outputDirectory); - Assert.NotEmpty(second.compiledAssemblyNames); - Assert.NotEmpty(Directory.GetDirectories(referenceRoot).Except(references)); - } - finally + environment.inputs = new ScriptCompilationInputs(selected.sdkIdentity, + Directory.EnumerateFiles(destination, "*.dll"), selected.predefinedSymbols); + ScriptCompilationResult first = fixture.Compile(); + Assert.True(first.success, string.Join(Environment.NewLine, first.diagnostics)); + string referenceRoot = Path.Combine(fixture.projectRoot, "Library", "ScriptApi", "Runtime"); + string[] references = Directory.GetDirectories(referenceRoot); + using (FileStream changed = new(Path.Combine(destination, "System.Runtime.dll"), FileMode.Append)) + changed.WriteByte(0); + ScriptCompilationResult second = fixture.Compile(); + Assert.True(second.success, string.Join(Environment.NewLine, second.diagnostics)); + Assert.NotEqual(first.outputDirectory, second.outputDirectory); + Assert.NotEmpty(second.compiledAssemblyNames); + Assert.NotEmpty(Directory.GetDirectories(referenceRoot).Except(references)); + } + + private sealed class MutableCompilationEnvironment : IScriptCompilationEnvironmentProvider + { + internal ScriptCompilationInputs inputs { get; set; } = null!; + + public ScriptCompilationInputs Capture(CancellationToken cancellationToken = default) { - Environment.SetEnvironmentVariable("DOTNET_ROOT", previous); + cancellationToken.ThrowIfCancellationRequested(); + return inputs; } } } diff --git a/tests/scripting/Inno.Scripting.Compiler.Tests/CompilerFixture.cs b/tests/scripting/Inno.Scripting.Compiler.Tests/CompilerFixture.cs index 9babb8657..7eccaa4ca 100644 --- a/tests/scripting/Inno.Scripting.Compiler.Tests/CompilerFixture.cs +++ b/tests/scripting/Inno.Scripting.Compiler.Tests/CompilerFixture.cs @@ -24,7 +24,10 @@ internal sealed class CompilerFixture : IDisposable private readonly ProjectSettingsStore m_settings; private readonly PluginEnvironment m_plugins; - internal CompilerFixture(Action? configure = null) + internal CompilerFixture( + Action? configure = null, + IScriptCompilationEnvironmentProvider? compilationEnvironment = null + ) { projectRoot = Path.Combine(Path.GetTempPath(), "InnoCompilerTests", Guid.NewGuid().ToString("N")); string assetsRoot = Path.Combine(projectRoot, "Assets"); @@ -55,7 +58,15 @@ internal CompilerFixture(Action? configure = null AssetSerializationContext.Create(m_assets)); m_plugins = new PluginEnvironment(m_assets, m_settings, m_host.serialization, pluginsRoot, libraryRoot, scan, m_host.modules.generations); - compiler = new ScriptCompiler(new ScriptCompilerOptions { projectRootDirectory = projectRoot }, m_assets, m_plugins); + string dotnetHost = Path.Combine( + Path.GetFullPath("../../..", System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + compiler = new ScriptCompiler( + new ScriptCompilerOptions + { + projectRootDirectory = projectRoot, + compilationEnvironment = compilationEnvironment ?? new Inno.Build.SdkScriptCompilationEnvironment(dotnetHost, projectRoot) + }, m_assets, m_plugins); } internal string projectRoot { get; } diff --git a/tests/scripting/Inno.Scripting.Compiler.Tests/Inno.Scripting.Compiler.Tests.csproj b/tests/scripting/Inno.Scripting.Compiler.Tests/Inno.Scripting.Compiler.Tests.csproj index a8655ea44..cdbe68960 100644 --- a/tests/scripting/Inno.Scripting.Compiler.Tests/Inno.Scripting.Compiler.Tests.csproj +++ b/tests/scripting/Inno.Scripting.Compiler.Tests/Inno.Scripting.Compiler.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/scripting/Inno.Scripting.Compiler.Tests/ScriptCachePublicationTests.cs b/tests/scripting/Inno.Scripting.Compiler.Tests/ScriptCachePublicationTests.cs new file mode 100644 index 000000000..d731ff0a6 --- /dev/null +++ b/tests/scripting/Inno.Scripting.Compiler.Tests/ScriptCachePublicationTests.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Inno.Scripting.Compiler.Tests; + +/// +/// Verifies publication through the public compiler while independently observing its owned artifacts. +/// +public sealed class ScriptCachePublicationTests +{ + /// + /// Publishes a complete generation after a temporary filesystem reader releases its staging borrow. + /// + /// The completed public compiler and artifact-equivalence checks. + [WindowsDirectorySharingFact] + public async Task ReusedAssembliesPublishAfterATemporaryStagingReaderReleasesThem() + { + using var fixture = new CompilerFixture(); + fixture.Write("Probe.cs", "public sealed class Probe { public int value { get; set; } }"); + ScriptCompilationResult baseline = fixture.Compile(); + Assert.True(baseline.success, string.Join(Environment.NewLine, baseline.diagnostics)); + string previous = Assert.IsType(baseline.outputDirectory); + Assert.NotEmpty(baseline.compiledAssemblyNames); + Dictionary expected = Directory.EnumerateFiles(previous, "*.dll") + .ToDictionary(static path => Path.GetFileName(path)!, File.ReadAllBytes, StringComparer.Ordinal); + string root = Path.GetDirectoryName(previous)!; + Assert.StartsWith(Path.GetFullPath(fixture.projectRoot) + Path.DirectorySeparatorChar, + Path.GetFullPath(previous), StringComparison.OrdinalIgnoreCase); + Directory.Delete(previous, recursive: true); + HashSet existing = Directory.EnumerateFiles(root, "*.dll", SearchOption.AllDirectories) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + using var observer = new StagingReader(root, existing, + $"Reused cached {baseline.compiledAssemblyNames[^1]} assembly artifact."); + using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + Task compilation = fixture.compiler + .CompileAuthoringGenerationAsync(observer, cancellation.Token).AsTask(); + Exception? compilationFailure = null; + bool captured = false; + try + { + await observer.captured.WaitAsync(TimeSpan.FromSeconds(20)); + captured = true; + await Task.Delay(150); + Assert.False(compilation.IsCompleted, + compilation.Exception?.ToString() ?? "A staged assembly borrow must delay publication."); + } + finally + { + if (!captured) + cancellation.Cancel(); + observer.Dispose(); + compilationFailure = await Record.ExceptionAsync(async () => { _ = await compilation; }); + } + Assert.Null(compilationFailure); + ScriptCompilationResult result = await compilation; + Assert.True(result.success, string.Join(Environment.NewLine, result.diagnostics)); + Assert.Equal(previous, result.outputDirectory); + Assert.Empty(result.compiledAssemblyNames); + Assert.Equal(baseline.compiledAssemblyNames, result.reusedAssemblyNames); + foreach ((string name, byte[] bytes) in expected) + Assert.Equal(bytes, File.ReadAllBytes(Path.Combine(result.outputDirectory!, name))); + } + + /// + /// Cancels a blocked publication without changing another completed generation. + /// + /// The completed failure, previous-generation and explicit retry checks. + [WindowsDirectorySharingFact] + public Task CanceledPublicationPreservesThePreviousGenerationAndAllowsAnotherRequest() + => Task.Run(() => VerifyRejectedPublication(cancel: true)); + + /// + /// Reports a persistent filesystem block without publishing incomplete or replacing previous artifacts. + /// + /// The completed failure, previous-generation and explicit retry checks. + [WindowsDirectorySharingFact] + public Task BlockedPublicationPreservesThePreviousGenerationAndAllowsAnotherRequest() + => Task.Run(() => VerifyRejectedPublication(cancel: false)); + + /// + /// Preserves a rejected cache tree until its reader releases ownership and an explicit repair commits. + /// + /// The completed replacement failure, byte-preservation and repair checks. + [WindowsDirectorySharingFact] + public Task BlockedReplacementPreservesTheEntireExistingTreeAndAllowsRepair() + => Task.Run(VerifyRejectedReplacement); + + private static void VerifyRejectedReplacement() + { + using var fixture = new CompilerFixture(); + fixture.Write("Probe.cs", "public sealed class Probe { public int value { get; set; } }"); + ScriptCompilationResult baseline = fixture.Compile(); + Assert.True(baseline.success, string.Join(Environment.NewLine, baseline.diagnostics)); + string publication = Assert.IsType(baseline.outputDirectory); + Assert.StartsWith(Path.GetFullPath(fixture.projectRoot) + Path.DirectorySeparatorChar, + Path.GetFullPath(publication), StringComparison.OrdinalIgnoreCase); + Dictionary original = Directory.EnumerateFiles(publication, "*", SearchOption.AllDirectories) + .ToDictionary(path => Path.GetRelativePath(publication, path), File.ReadAllBytes, StringComparer.Ordinal); + string assembly = Directory.EnumerateFiles(publication, "*.dll").First(); + File.WriteAllBytes(assembly, [0, 1, 2, 3]); + Dictionary rejected = Directory.EnumerateFiles(publication, "*", SearchOption.AllDirectories) + .ToDictionary(path => Path.GetRelativePath(publication, path), File.ReadAllBytes, StringComparer.Ordinal); + using (var reader = new FileStream(assembly, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) + { + Exception? failure = Record.Exception(() => fixture.Compile()); + Assert.True(failure is IOException or UnauthorizedAccessException, failure?.ToString()); + Assert.Equal(rejected.Keys.Order(StringComparer.Ordinal), + Directory.EnumerateFiles(publication, "*", SearchOption.AllDirectories) + .Select(path => Path.GetRelativePath(publication, path)).Order(StringComparer.Ordinal)); + foreach ((string path, byte[] bytes) in rejected) + Assert.Equal(bytes, File.ReadAllBytes(Path.Combine(publication, path))); + } + + ScriptCompilationResult repaired = fixture.Compile(); + Assert.True(repaired.success, string.Join(Environment.NewLine, repaired.diagnostics)); + Assert.Equal(publication, repaired.outputDirectory); + Assert.Empty(repaired.compiledAssemblyNames); + Assert.Equal(original.Keys.Order(StringComparer.Ordinal), + Directory.EnumerateFiles(publication, "*", SearchOption.AllDirectories) + .Select(path => Path.GetRelativePath(publication, path)).Order(StringComparer.Ordinal)); + foreach ((string path, byte[] bytes) in original) + Assert.Equal(bytes, File.ReadAllBytes(Path.Combine(publication, path))); + } + + private static void VerifyRejectedPublication(bool cancel) + { + using var fixture = new CompilerFixture(); + fixture.Write("Probe.cs", "public sealed class Probe { public int value { get; set; } = 1; }"); + ScriptCompilationResult previous = fixture.Compile(); + Assert.True(previous.success, string.Join(Environment.NewLine, previous.diagnostics)); + string lastGood = Assert.IsType(previous.outputDirectory); + Dictionary saved = Directory.EnumerateFiles(lastGood, "*", SearchOption.AllDirectories) + .ToDictionary(path => Path.GetRelativePath(lastGood, path), File.ReadAllBytes, StringComparer.Ordinal); + fixture.Write("Probe.cs", "public sealed class Probe { public int value { get; set; } = 2; }"); + ScriptCompilationResult candidate = fixture.Compile(); + Assert.True(candidate.success, string.Join(Environment.NewLine, candidate.diagnostics)); + string publication = Assert.IsType(candidate.outputDirectory); + Assert.NotEqual(lastGood, publication); + Assert.NotEmpty(candidate.compiledAssemblyNames); + Assert.StartsWith(Path.GetFullPath(fixture.projectRoot) + Path.DirectorySeparatorChar, + Path.GetFullPath(publication), StringComparison.OrdinalIgnoreCase); + string root = Path.GetDirectoryName(publication)!; + Directory.Delete(publication, recursive: true); + HashSet existing = Directory.EnumerateFiles(root, "*.dll", SearchOption.AllDirectories) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + using var observer = new StagingReader(root, existing, + $"Reused cached {candidate.compiledAssemblyNames[^1]} assembly artifact."); + using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + Task compilation = fixture.compiler + .CompileAuthoringGenerationAsync(observer, cancellation.Token).AsTask(); + try + { + observer.captured.WaitAsync(TimeSpan.FromSeconds(20)).GetAwaiter().GetResult(); + if (cancel) + cancellation.Cancel(); + Exception? failure = Record.Exception(() => compilation.WaitAsync(TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); + if (cancel) + Assert.IsAssignableFrom(failure); + else + Assert.True(failure is IOException or UnauthorizedAccessException, failure?.ToString()); + } + finally + { + observer.Dispose(); + cancellation.Cancel(); + _ = Record.Exception(() => compilation.GetAwaiter().GetResult()); + } + + Assert.False(Directory.Exists(publication)); + foreach ((string path, byte[] bytes) in saved) + Assert.Equal(bytes, File.ReadAllBytes(Path.Combine(lastGood, path))); + ScriptCompilationResult recovered = fixture.Compile(); + Assert.True(recovered.success, string.Join(Environment.NewLine, recovered.diagnostics)); + Assert.Equal(publication, recovered.outputDirectory); + Assert.Empty(recovered.compiledAssemblyNames); + foreach ((string path, byte[] bytes) in saved) + Assert.Equal(bytes, File.ReadAllBytes(Path.Combine(lastGood, path))); + } + + private sealed class StagingReader : IProgress, IDisposable + { + private readonly object m_sync = new(); + private readonly string m_root; + private readonly IReadOnlySet m_existing; + private readonly string m_stage; + private readonly TaskCompletionSource m_captured = new(TaskCreationOptions.RunContinuationsAsynchronously); + private FileStream? m_reader; + private bool m_retired; + + internal StagingReader( + string root, + IReadOnlySet existing, + string stage + ) { + m_root = root; + m_existing = existing; + m_stage = stage; + } + + /// + public void Report(ScriptCompilationProgress value) + { + lock (m_sync) + { + if (m_retired || m_reader is not null || value.stage != m_stage) + return; + try + { + string[] candidates = Directory.EnumerateFiles(m_root, "*.dll", SearchOption.AllDirectories) + .Where(path => !m_existing.Contains(path)).ToArray(); + Assert.NotEmpty(candidates); + m_reader = new FileStream(candidates[0], FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); + m_captured.TrySetResult(true); + } + catch (Exception failure) + { + m_captured.TrySetException(failure); + throw; + } + } + } + + /// + public void Dispose() + { + lock (m_sync) + { + m_retired = true; + m_reader?.Dispose(); + m_reader = null; + } + } + + internal Task captured => m_captured.Task; + } + + private sealed class WindowsDirectorySharingFactAttribute : FactAttribute + { + /// Marks the Windows directory-sharing fixture as unexecuted on other hosts. + public WindowsDirectorySharingFactAttribute() + { + if (!OperatingSystem.IsWindows()) + Skip = "This fixture requires Windows directory rename sharing semantics."; + } + } +} diff --git a/tests/scripting/Inno.Scripting.Compiler.Tests/SdkCompilationInputsTests.cs b/tests/scripting/Inno.Scripting.Compiler.Tests/SdkCompilationInputsTests.cs new file mode 100644 index 000000000..a3da3069f --- /dev/null +++ b/tests/scripting/Inno.Scripting.Compiler.Tests/SdkCompilationInputsTests.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using Inno.Build; +using Xunit; + +namespace Inno.Scripting.Compiler.Tests; + +public sealed class SdkCompilationInputsTests +{ + [Fact] + public void SelectedSdkSuppliesFrameworkSymbolsAndIdeReferences() + { + using var fixture = new CompilerFixture(); + string host = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + ScriptCompilationInputs inputs = new SdkScriptCompilationEnvironment(host, fixture.projectRoot).Capture(); + Assert.Contains("NET10_0", inputs.predefinedSymbols); + Assert.Contains("NET10_0_OR_GREATER", inputs.predefinedSymbols); + Assert.DoesNotContain("DEBUG", inputs.predefinedSymbols); + Assert.DoesNotContain("TRACE", inputs.predefinedSymbols); + Assert.Contains(inputs.frameworkReferences, static path => Path.GetFileName(path) == "System.Runtime.dll"); + Assert.All(inputs.frameworkReferences, static path => Assert.True(File.Exists(path), path)); + Assert.DoesNotContain(inputs.frameworkReferences, static path => Path.GetFileName(path) == "System.Private.CoreLib.dll"); + fixture.Write("FrameworkSymbols.cs", """ + #if !NET10_0 || !NET10_0_OR_GREATER + #error SDK framework symbols must match runtime and IDE compilation. + #endif + public sealed class FrameworkSymbols { } + """); + fixture.Rescan(); + Assert.True(fixture.Compile().success); + fixture.compiler.GenerateProjectFiles(); + string project = File.ReadAllText(Path.Combine(fixture.projectRoot, "Inno.GameScripts.csproj")); + Assert.Contains("ValidateInnoScriptSdk", project, StringComparison.Ordinal); + Assert.Contains("true", project, StringComparison.Ordinal); + foreach (string path in inputs.frameworkReferences) + Assert.Contains(path.Replace("&", "&", StringComparison.Ordinal), project, StringComparison.Ordinal); + Assert.Empty(Directory.EnumerateFiles(Path.Combine(fixture.projectRoot, "Library", "Scripting"), "*.csproj", SearchOption.AllDirectories)); + } + + [Fact] + public void MissingSelectedSdkDoesNotPublishScriptArtifactsOrRetainEvaluationProjects() + { + using var fixture = new CompilerFixture(); + File.WriteAllText(Path.Combine(fixture.projectRoot, "global.json"), """ + { "sdk": { "version": "10.0.999", "rollForward": "disable", "allowPrerelease": false } } + """); + fixture.Write("Probe.cs", "public sealed class Probe { }"); + fixture.Rescan(); + Assert.Throws(() => fixture.Compile()); + Assert.Empty(Directory.EnumerateFiles(Path.Combine(fixture.projectRoot, "Library"), "*.dll", SearchOption.AllDirectories)); + Assert.Empty(Directory.EnumerateFiles(Path.Combine(fixture.projectRoot, "Library"), "*.csproj", SearchOption.AllDirectories)); + } + + [Fact] + public void CancelledSdkEvaluationCreatesNoPublishedInputs() + { + using var fixture = new CompilerFixture(); + using var cancellation = new CancellationTokenSource(); + cancellation.Cancel(); + string host = Path.Combine(Path.GetFullPath("../../..", RuntimeEnvironment.GetRuntimeDirectory()), + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + var provider = new SdkScriptCompilationEnvironment(host, fixture.projectRoot); + Assert.ThrowsAny(() => provider.Capture(cancellation.Token)); + Assert.Empty(Directory.EnumerateFiles(Path.Combine(fixture.projectRoot, "Library"), "*.csproj", SearchOption.AllDirectories)); + } + + [Fact] + public void InputContractCopiesCollectionsAndRejectsIncompleteReferences() + { + string[] references = [typeof(object).Assembly.Location]; + string[] symbols = ["NET10_0"]; + var inputs = new ScriptCompilationInputs("10.0.402", references, symbols); + references[0] = "changed"; + symbols[0] = "CHANGED"; + Assert.True(Path.IsPathFullyQualified(Assert.Single(inputs.frameworkReferences))); + Assert.Equal("NET10_0", Assert.Single(inputs.predefinedSymbols)); + Assert.Throws(() => new ScriptCompilationInputs("10.0.402", [], [])); + Assert.Throws(() => new ScriptCompilationInputs("10.0.402", ["relative.dll"], [])); + } +} diff --git a/tests/source/Inno.Testing.Source/TestRepository.cs b/tests/source/Inno.Testing.Source/TestRepository.cs index 67fb936d8..fbeae2be5 100644 --- a/tests/source/Inno.Testing.Source/TestRepository.cs +++ b/tests/source/Inno.Testing.Source/TestRepository.cs @@ -6,11 +6,39 @@ namespace Inno.Testing.Source; /// -/// Resolves a source checkout explicitly declared by a test assembly's SDK project. +/// Resolves source and SDK output inputs explicitly declared by a test assembly's project. /// public static class TestRepository { private const string C_ROOT_METADATA = "Inno.Testing.RepositoryRoot"; + private const string C_ARTIFACTS_METADATA = "Inno.Testing.ArtifactsPath"; + + /// + /// Reads the SDK output owner recorded when the test assembly was built. + /// + /// + /// The test assembly carrying the optional evaluated artifacts path. + /// + /// + /// The declared absolute artifacts directory, or null when normal SDK output paths were selected. + /// + /// + /// The test assembly is null. + /// + /// + /// The declaration is duplicated, empty or relative. + /// + public static string? GetArtifactsPath(Assembly testAssembly) + { + ArgumentNullException.ThrowIfNull(testAssembly); + AssemblyMetadataAttribute? declaration = testAssembly.GetCustomAttributes() + .SingleOrDefault(static attribute => attribute.Key == C_ARTIFACTS_METADATA); + if (declaration is null) + return null; + if (string.IsNullOrWhiteSpace(declaration.Value) || !Path.IsPathFullyQualified(declaration.Value)) + throw new InvalidOperationException("Tests require the absolute SDK artifacts owner recorded by their project."); + return Path.GetFullPath(declaration.Value); + } /// /// Reads and validates the checkout input independently of the test assembly's output directory. diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandFixture.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandFixture.cs index 706b09f7d..8846b0c69 100644 --- a/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandFixture.cs +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandFixture.cs @@ -10,8 +10,19 @@ internal static class ArchitectureCommandFixture internal static (int exitCode, string output) Run( string root, params string[] arguments + ) => RunBounded(root, TimeSpan.FromMinutes(2), arguments); + + internal static (int exitCode, string output) RunComplete( + string root, + params string[] arguments + ) => RunBounded(root, TimeSpan.FromMinutes(15), arguments); + + private static (int exitCode, string output) RunBounded( + string root, + TimeSpan budget, + string[] arguments ) { - using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + using var cancellation = new CancellationTokenSource(budget); using var output = new StringWriter(CultureInfo.InvariantCulture); TextWriter previousOutput = Console.Out; TextWriter previousError = Console.Error; diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs index a1b68e063..eb3ab9956 100644 --- a/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureCommandTests.cs @@ -88,6 +88,7 @@ string expected Assert.Contains(expected, output, StringComparison.Ordinal); Assert.True(File.Exists(graph)); Assert.False(Directory.Exists(Path.Combine(project, "bin"))); + Assert.False(Directory.Exists(Path.Combine(project, "obj"))); } finally { @@ -161,6 +162,14 @@ bool rejected [InlineData("src/content/assets/Probe.cs", "internal class Probe { void Read() { context.With(resolver); } }", "owner-complete", true)] [InlineData("src/services/audio/Probe.cs", "public class Probe { public MaEngine engine; }", "native implementation", true)] [InlineData("src/services/audio/Probe.cs", "internal class Probe { void Release() { OnCleanupFailed(phase, failure); } }", "diagnostic-only", true)] + [InlineData("src/composition/editor/panels/Probe.cs", "using Ui = Inno.Editor.ImGui.ImGui; internal class Probe { void Draw() { for (int i = 0; i < 2; i++) { Ui.PushId(i); DrawRow(); if (i == 1) break; Ui.PopId(); } } }", "presentation loop ID scopes", true)] + [InlineData("src/composition/editor/panels/Probe.cs", "using Ui = Inno.Editor.ImGui.ImGui; internal class Probe { void Draw() { for (int i = 0; i < 2; i++) { Ui.PushId(i); try { DrawRow(); if (i == 1) break; } finally { Ui.PopId(); } } } }", "presentation loop ID scopes", false)] + [InlineData("src/composition/editor/panels/Probe.cs", "using Ui = Inno.Editor.ImGui.ImGui; using Other = Inno.Editor.ImGui.ImGui; internal class Probe { }", "multiple presentation aliases", true)] + [InlineData("src/composition/editor/panels/Probe.cs", "using Ui = Inno.Editor.ImGui.ImGui; using NativeUi = Inno.Native.ImGui.ImGui; internal class Probe { }", "multiple presentation aliases", false)] + [InlineData("src/composition/editor/panels/Probe.cs", "/// \n/// Removed identity.\n/// \n/// \n/// Actual section.\n/// \ninternal class Probe { }", "multiple XML summary elements", true)] + [InlineData("src/composition/editor/panels/Probe.cs", "/// \n/// Actual section.\n/// \ninternal class Probe { }", "multiple XML summary elements", false)] + [InlineData("src/services/Probe.cs", "using Ui = Inno.Editor.ImGui.ImGui; internal class Probe { void Draw() { for (int i = 0; i < 2; i++) { Ui.PushId(i); DrawRow(); Ui.PopId(); } } }", "presentation loop ID scopes", false)] + [InlineData("src/composition/editor/panels/Probe.cs", "internal class Probe { void Draw() { for (int i = 0; i < 2; i++) { collector.PushId(i); DrawRow(); collector.PopId(); } } }", "presentation loop ID scopes", false)] [InlineData("src/services/audio/Probe.cs", "internal class Probe { void Release() { try { } catch (RetirementPendingException) { throw; } } }", "wrapped pending ownership", true)] [InlineData("src/services/audio/Probe.cs", "internal class Probe { void Release() { try { } catch (Inno.Core.Execution.RetirementTimeoutException failure) { throw; } } }", "wrapped pending ownership", true)] [InlineData("src/services/audio/Probe.cs", "internal class Probe { void Release() { try { } catch (Exception error) when (RetirementPendingException.Find(error) is not null) { throw; } } }", "wrapped pending ownership", false)] diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureOutputOwnerTests.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureOutputOwnerTests.cs new file mode 100644 index 000000000..450c01099 --- /dev/null +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/ArchitectureOutputOwnerTests.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Inno.Build.Toolchains; +using Xunit; + +namespace Inno.Tooling.Architecture.Tests; + +[Collection("Architecture diagnostics")] +public sealed class ArchitectureOutputOwnerTests +{ + [Fact] + public async Task PublicContractsUseTheEvaluatedOutputOwnerInsteadOfStaleDefaultAssemblies() + { + string root = Path.Combine(Path.GetTempPath(), "InnoArchitectureOutputTests", Guid.NewGuid().ToString("N")); + string host = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "..", "..", "..", + OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet")); + using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + try + { + string contract = Path.Combine(root, "src/foundation/core/Inno.Core.Contract"); + string editor = Path.Combine(root, "src/composition/editor/framework/Inno.Editor.Probe"); + Directory.CreateDirectory(contract); + Directory.CreateDirectory(editor); + File.WriteAllText(Path.Combine(root, "AGENTS.md"), "# Fixture"); + File.WriteAllText(Path.Combine(root, "InnoEngine.sln"), string.Empty); + File.WriteAllText(Path.Combine(contract, "Inno.Core.Contract.csproj"), """ + + net10.0 + + """); + File.WriteAllText(Path.Combine(contract, "Contract.cs"), "namespace Inno.Core.Contract; public sealed class Contract { }"); + string project = Path.Combine(editor, "Inno.Editor.Probe.csproj"); + File.WriteAllText(project, """ + + net10.0 + + + """); + string source = Path.Combine(editor, "Probe.cs"); + File.WriteAllText(source, "namespace Inno.Editor.Probe; public sealed class Probe { }"); + await Build([]); + File.WriteAllText(source, "namespace Inno.Editor.Probe; public sealed class Probe { public Inno.Core.Contract.Contract value { get; } = new(); }"); + string artifacts = Path.Combine(root, "owned-output"); + await Build(["-p:ArtifactsPath=" + artifacts]); + string graph = Path.Combine(root, "graph.json"); + + string correct = ArchitectureCommandFixture.Run(root, "--dotnet", host, + "--artifacts-path", artifacts, "--project-graph", graph).output; + Assert.DoesNotContain("Inno.Core.Contract is an implementation dependency", correct, StringComparison.Ordinal); + Assert.Contains(artifacts.Replace('\\', '/'), File.ReadAllText(graph).Replace('\\', '/').Replace("//", "/"), StringComparison.OrdinalIgnoreCase); + + File.WriteAllText(project, File.ReadAllText(project).Replace("Include=", "PrivateAssets=\"compile\" Include=", StringComparison.Ordinal)); + string rejected = ArchitectureCommandFixture.Run(root, "--dotnet", host, "--artifacts-path", artifacts).output; + Assert.Contains("public/protected API exposes Inno.Core.Contract", rejected, StringComparison.Ordinal); + + async Task Build(string[] properties) + { + await ToolchainEnvironment.RunAsync(host, ["build", project, "--disable-build-servers", "-m:1", "-nr:false", "-v:quiet", .. properties], + root, cancellation.Token, DotNetSdkEnvironment.Create(host)); + } + } + finally + { + if (Directory.Exists(root)) + Directory.Delete(root, recursive: true); + } + } +} diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/Inno.Tooling.Architecture.Tests.csproj b/tests/tooling/Inno.Tooling.Architecture.Tests/Inno.Tooling.Architecture.Tests.csproj index e366f32a3..1263262ea 100644 --- a/tests/tooling/Inno.Tooling.Architecture.Tests/Inno.Tooling.Architecture.Tests.csproj +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/Inno.Tooling.Architecture.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/IntegrationBoundaryTests.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/IntegrationBoundaryTests.cs index db074b964..379cfda2a 100644 --- a/tests/tooling/Inno.Tooling.Architecture.Tests/IntegrationBoundaryTests.cs +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/IntegrationBoundaryTests.cs @@ -3,7 +3,6 @@ using Xunit; namespace Inno.Tooling.Architecture.Tests; - [Collection("Architecture diagnostics")] public sealed class IntegrationBoundaryTests { @@ -18,6 +17,12 @@ public sealed class IntegrationBoundaryTests [InlineData("build/toolchains/Probe", "build/bindings/Inno.Build.Bindings", "neutral toolchains cannot depend", true)] [InlineData("build/bindings/Inno.Build.Bindings", "build/tasks/Inno.Build.Tasks", "binding generation cannot depend", true)] [InlineData("platforms/WindowsX64/build/Probe", "build/bindings/Inno.Build.Bindings", "platform modules must borrow", true)] + [InlineData("src/composition/editor/features/Inno.Editor.TestFeature", "src/composition/editor/presentation/Inno.Editor.ImGui", "Editor feature editing must be presentation-independent", true)] + [InlineData("src/composition/editor/features/Inno.Editor.TestFeature", "src/composition/editor/framework/Inno.Editor.Interactions", "Editor feature editing must be presentation-independent", false)] + [InlineData("src/composition/editor/panels/Inno.Editor.Panel.A", "src/composition/editor/panels/Inno.Editor.Panel.B", "Panels cannot depend on other Panels", true)] + [InlineData("src/composition/editor/panels/Inno.Editor.Panel.A", "src/composition/editor/presentation/Inno.Editor.Assets.ImGui", "Panels cannot depend on other Panels", false)] + [InlineData("src/composition/editor/panels/Inno.Editor.Panel.A", "backends/ImGui/native/Inno.Native.ImGui", "native UI access belongs", true)] + [InlineData("src/composition/editor/presentation/Inno.Editor.Graph.ImGui", "src/composition/editor/features/Inno.Editor.Shaders", "reusable Graph mechanisms cannot depend", true)] public void CliChecksActualPlatformIntegrationProjectReferences( string owner, string dependency, @@ -34,7 +39,6 @@ bool rejected (_, string report) = ArchitectureCommandFixture.Run(root); Assert.Equal(rejected, report.Contains(expected, StringComparison.Ordinal)); Assert.DoesNotContain("explicit project dependency is missing", report); - string CreateProject( string relative, string references @@ -52,4 +56,35 @@ string references Directory.Delete(root, recursive: true); } } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ScriptSdkReferencesPanelContractsOnlyWhenThePanelActuallyExportsThem(bool exportsContract) + { + string root = Path.Combine(Path.GetTempPath(), "InnoSdkOwnerTests", Guid.NewGuid().ToString("N")); + try + { + string host = Path.Combine(root, "build/cli/Inno.Build.Cli"); + string panel = Path.Combine(root, "src/composition/editor/panels/Inno.Editor.Panel.Probe"); + Directory.CreateDirectory(host); + Directory.CreateDirectory(Path.Combine(panel, "Properties")); + File.WriteAllText(Path.Combine(root, "InnoEngine.sln"), "Microsoft Visual Studio Solution File, Format Version 12.00"); + string panelProject = Path.Combine(panel, "Inno.Editor.Panel.Probe.csproj"); + File.WriteAllText(panelProject, "net10.0"); + string include = Path.GetRelativePath(host, panelProject).Replace('\\', '/'); + File.WriteAllText(Path.Combine(host, "Inno.Build.Cli.csproj"), + "net10.0"); + const string C_EXPORT_DECLARATION = "[assembly: Inno.Scripting.Api.ScriptingApiExport(typeof(System.String), Inno.Scripting.Api.ScriptingApiScope.Editor)]"; + File.WriteAllText(Path.Combine(panel, "Properties/ScriptingApi.cs"), exportsContract ? C_EXPORT_DECLARATION : "// " + C_EXPORT_DECLARATION); + (_, string report) = ArchitectureCommandFixture.Run(root); + Assert.Equal(!exportsContract, report.Contains("script SDK host must reference API owners", StringComparison.Ordinal)); + } + finally + { + if (Directory.Exists(root)) + Directory.Delete(root, recursive: true); + } + } } diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/RepositoryArchitectureTests.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/RepositoryArchitectureTests.cs index 5404e8855..1a605e83c 100644 --- a/tests/tooling/Inno.Tooling.Architecture.Tests/RepositoryArchitectureTests.cs +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/RepositoryArchitectureTests.cs @@ -1,4 +1,7 @@ using Inno.Testing.Source; +using Inno.Build.Toolchains; +using System; +using System.Collections.Generic; using System.Reflection; using Xunit; @@ -13,7 +16,12 @@ public void CurrentRepositoryPassesArchitectureRules() { string root = TestRepository.GetRoot(typeof(RepositoryArchitectureTests).Assembly); string configuration = typeof(RepositoryArchitectureTests).Assembly.GetCustomAttribute()!.Configuration; - (int exitCode, string output) = ArchitectureCommandFixture.Run(root, "--configuration", configuration); + string dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + string? artifacts = TestRepository.GetArtifactsPath(typeof(RepositoryArchitectureTests).Assembly); + var arguments = new List { "--configuration", configuration, "--dotnet", dotnet }; + if (!string.IsNullOrWhiteSpace(artifacts)) + arguments.AddRange(["--artifacts-path", artifacts]); + (int exitCode, string output) = ArchitectureCommandFixture.RunComplete(root, arguments.ToArray()); Assert.True(exitCode == 0, output); } } diff --git a/tests/tooling/Inno.Tooling.Architecture.Tests/TestRepositoryInputsTests.cs b/tests/tooling/Inno.Tooling.Architecture.Tests/TestRepositoryInputsTests.cs new file mode 100644 index 000000000..490d976de --- /dev/null +++ b/tests/tooling/Inno.Tooling.Architecture.Tests/TestRepositoryInputsTests.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Linq; +using System.Collections.Generic; +using System.Text.Json; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Inno.Build.Toolchains; +using Inno.Testing.Source; +using Xunit; + +namespace Inno.Tooling.Architecture.Tests; + +public sealed class TestRepositoryInputsTests +{ + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task SdkRecordsItsEvaluatedArtifactsOwner( + bool useArtifacts, + bool explicitOwner + ) { + string root = Path.Combine(Path.GetTempPath(), "InnoTestInputs", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + try + { + string repository = TestRepository.GetRoot(typeof(TestRepositoryInputsTests).Assembly); + string import = SecurityElement.Escape(Path.Combine(repository, "tests/RepositoryInput.targets")); + string project = Path.Combine(root, "Probe.csproj"); + await File.WriteAllTextAsync(project, $""" + + net10.0disable + + + """); + string dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? ToolchainEnvironment.ResolveExecutable("dotnet"); + var arguments = new List + { + "msbuild", project, "-nologo", "-nr:false", "-t:GetAssemblyAttributes", + "-getProperty:ArtifactsPath", "-getItem:AssemblyAttribute", + "-p:UseArtifactsOutput=" + useArtifacts.ToString().ToLowerInvariant() + }; + if (explicitOwner) + arguments.Add("-p:ArtifactsPath=" + Path.Combine(root, "independent")); + using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + string output = await ToolchainEnvironment.CaptureOutputAsync(dotnet, arguments, + root, timeout.Token, DotNetSdkEnvironment.Create(dotnet)); + using JsonDocument result = JsonDocument.Parse(output); + string? artifacts = result.RootElement.GetProperty("Properties").GetProperty("ArtifactsPath").GetString(); + JsonElement[] declarations = result.RootElement.GetProperty("Items").GetProperty("AssemblyAttribute").EnumerateArray() + .Where(item => item.TryGetProperty("_Parameter1", out var key) && key.GetString() == "Inno.Testing.ArtifactsPath").ToArray(); + if (useArtifacts) + { + Assert.False(string.IsNullOrWhiteSpace(artifacts)); + Assert.Equal(Path.GetFullPath(artifacts!), Path.TrimEndingDirectorySeparator( + Assert.Single(declarations).GetProperty("_Parameter2").GetString()!)); + } + else + { + Assert.Empty(declarations); + } + } + finally + { + Directory.Delete(root, recursive: true); + } + } + + [Fact] + public void AbsentArtifactsMetadataPreservesNormalSdkOutputSelection() + => Assert.Null(TestRepository.GetArtifactsPath(CreateAssembly())); + + [Fact] + public void ExplicitArtifactsMetadataPreservesTheRecordedAbsoluteOwner() + { + string owner = Path.Combine(Path.GetTempPath(), "owned-sdk-output"); + Assert.Equal(Path.GetFullPath(owner), TestRepository.GetArtifactsPath(CreateAssembly(owner))); + } + + [Fact] + public void RelativeEmptyOrDuplicateArtifactsDeclarationsAreRejected() + { + Assert.Throws(() => TestRepository.GetArtifactsPath(CreateAssembly("relative-owner"))); + Assert.Throws(() => TestRepository.GetArtifactsPath(CreateAssembly(string.Empty))); + string owner = Path.Combine(Path.GetTempPath(), "owned-sdk-output"); + Assert.Throws(() => TestRepository.GetArtifactsPath(CreateAssembly(owner, owner))); + Assert.Throws(() => TestRepository.GetArtifactsPath(null!)); + } + + private static Assembly CreateAssembly(params string[] owners) + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName("RepositoryInputs." + Guid.NewGuid().ToString("N")), AssemblyBuilderAccess.RunAndCollect); + ConstructorInfo constructor = typeof(AssemblyMetadataAttribute).GetConstructor([typeof(string), typeof(string)])!; + foreach (string owner in owners) + assembly.SetCustomAttribute(new CustomAttributeBuilder(constructor, ["Inno.Testing.ArtifactsPath", owner])); + return assembly; + } +} diff --git a/tools/Inno.Tooling.Architecture/ArchitectureRules.cs b/tools/Inno.Tooling.Architecture/ArchitectureRules.cs index e8cd30a3f..705fbf9e1 100644 --- a/tools/Inno.Tooling.Architecture/ArchitectureRules.cs +++ b/tools/Inno.Tooling.Architecture/ArchitectureRules.cs @@ -6,12 +6,10 @@ using System.Xml.Linq; namespace Inno.Tooling.Architecture; - internal static partial class ArchitectureRules { private static readonly string[] S_SCAN_ROOTS = ["entry", "src", "backends", "platforms", "build", "tests"]; - private static readonly string[] S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS = - [ + private static readonly string[] S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS = [ "Inno.Editor.", "Inno.Build", "Inno.Scripting.Compiler", @@ -25,16 +23,8 @@ internal static partial class ArchitectureRules "Inno.Adapter.Serialization.DotNet", "Toolchains" ]; - private static readonly string[] S_FORBIDDEN_IMPLEMENTATION_WORDS = - [ - "Legacy", - "Compatibility", - "Migration", - "Former", - "Deprecated" - ]; - private static readonly string[] S_REMOVED_PROJECT_NAMES = - [ + private static readonly string[] S_FORBIDDEN_IMPLEMENTATION_WORDS = ["Legacy", "Compatibility", "Migration", "Former", "Deprecated"]; + private static readonly string[] S_REMOVED_PROJECT_NAMES = [ "Inno.Core.Framework", "Inno.Core.Assemblies", "Inno.Core.Reflection", @@ -44,11 +34,16 @@ internal static partial class ArchitectureRules "Inno.Rendering.Core", "Inno.Native.Dll", "Inno.Adapter.Input.Sdl3", - "Inno.Editor.Application", "Inno.Player", "Inno.Build.Toolchains.Host", "Inno.Build.Toolchains.Browser", - "Inno.Build.SupportPacks", "Inno.Build.Platform.Windows", "Inno.Build.Platform.MacOS", "Inno.Build.Platform.Browser" + "Inno.Editor.Application", + "Inno.Player", + "Inno.Build.Toolchains.Host", + "Inno.Build.Toolchains.Browser", + "Inno.Build.SupportPacks", + "Inno.Build.Platform.Windows", + "Inno.Build.Platform.MacOS", + "Inno.Build.Platform.Browser" ]; - private static readonly string[] S_CONCRETE_ADAPTER_MARKERS = - [ + private static readonly string[] S_CONCRETE_ADAPTER_MARKERS = [ "Inno.Adapter.Platform.Sdl3", "Inno.Adapter.Storage.FileSystem", "Inno.Adapter.Rendering.Bgfx", @@ -60,12 +55,12 @@ internal static partial class ArchitectureRules "MiniAudioDevice", "PlatformImGuiContext" ]; - internal static void Validate( string repositoryRoot, ICollection failures ) { ValidateHostIsolation(repositoryRoot, failures); + EditorUiBoundaryValidator.Validate(repositoryRoot, failures); PlatformOwnershipValidator.Validate(repositoryRoot, failures); NativePlatformPolicyValidator.Validate(repositoryRoot, failures); string backendRoot = Path.Combine(repositoryRoot, "backends"); @@ -80,35 +75,69 @@ ICollection failures ValidateRemovedProjects(repositoryRoot, failures); } - internal static bool IsForbiddenPlayerDependency(string assemblyName) => S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS - .Any(fragment => assemblyName.Contains(fragment, StringComparison.Ordinal)) - || assemblyName.StartsWith("Inno.Integration.", StringComparison.Ordinal) - && assemblyName.EndsWith(".Bgfx", StringComparison.Ordinal); - + internal static bool IsForbiddenPlayerDependency(string assemblyName) => S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS.Any(fragment => assemblyName.Contains(fragment, StringComparison.Ordinal)) + || assemblyName.StartsWith( + "Inno.Integration.", + StringComparison.Ordinal + ) + && assemblyName.EndsWith( + ".Bgfx", + StringComparison.Ordinal + ); private static void ValidateHostIsolation( string repositoryRoot, ICollection failures ) { var executables = new HashSet(StringComparer.Ordinal) { - "InnoEngine.Editor", "InnoEngine.Build" + "InnoEngine.Editor", + "InnoEngine.Build" }; - foreach (string rootName in new[] { "entry", "src", "backends", "platforms", "build", "tools" }) + foreach (string rootName in new[] + { + "entry", + "src", + "backends", + "platforms", + "build", + "tools" + } + + ) { foreach (string project in EnumerateFiles(Path.Combine(repositoryRoot, rootName), "*.csproj")) { XDocument document = XDocument.Load(project); - bool test = Relative(repositoryRoot, project).Contains("/tests/", StringComparison.Ordinal) + bool test = Relative( + repositoryRoot, + project + ).Contains( + "/tests/", + StringComparison.Ordinal + ) && document.Descendants("IsTestProject").Any(static value => value.Value == "true"); - if (document.Descendants("OutputType").Any(static value => value.Value == "Exe") - && !test && !executables.Contains(Path.GetFileNameWithoutExtension(project))) + if (document.Descendants("OutputType").Any(static value => value.Value == "Exe") && !test && !executables.Contains(Path.GetFileNameWithoutExtension(project))) failures.Add($"{Relative(repositoryRoot, project)}: only the Editor and Build development entries may be production executable projects."); - if (Path.GetFileNameWithoutExtension(project).StartsWith("Inno.Native.", StringComparison.Ordinal) - && Path.GetFileNameWithoutExtension(project).EndsWith(".Browser", StringComparison.Ordinal)) + if (Path.GetFileNameWithoutExtension(project).StartsWith( + "Inno.Native.", + StringComparison.Ordinal + ) + && Path.GetFileNameWithoutExtension(project).EndsWith( + ".Browser", + StringComparison.Ordinal + )) failures.Add($"{Relative(repositoryRoot, project)}: native targets must use binding profiles, not duplicate projects."); } } - foreach (string rootName in new[] { "src/foundation", "src/composition/shell", "src/composition/player/Inno.Player.Runtime" }) + + foreach (string rootName in new[] + { + "src/foundation", + "src/composition/shell", + "src/composition/player/Inno.Player.Runtime" + } + + ) { string root = Path.Combine(repositoryRoot, rootName); if (!Directory.Exists(root)) @@ -119,9 +148,9 @@ ICollection failures failures.Add($"{Relative(repositoryRoot, sourcePath)}: shared code must receive host capabilities through contracts."); } } + string browserProject = Path.Combine(repositoryRoot, "platforms/BrowserWasm32/player/Inno.Player.BrowserWasm32/Inno.Player.BrowserWasm32.csproj"); - if (File.Exists(browserProject) && - XDocument.Load(browserProject).Descendants("Compile").Any(static item => item.Attribute("Link") is not null)) + if (File.Exists(browserProject) && XDocument.Load(browserProject).Descendants("Compile").Any(static item => item.Attribute("Link")is not null)) failures.Add("Inno.Player.BrowserWasm32: shared Player source must be consumed through Inno.Player.Runtime."); } @@ -160,6 +189,7 @@ ICollection failures if (fileName.Contains(word, StringComparison.OrdinalIgnoreCase)) failures.Add($"{relative}: implementation file names cannot contain '{word}'."); } + foreach (Match match in DeclaredTypePattern().Matches(source)) { string name = match.Groups[2].Value; @@ -178,72 +208,140 @@ ICollection failures ) { if (relative.StartsWith("tools/Inno.Tooling.Architecture/", StringComparison.Ordinal)) return; - if (relative.StartsWith("src/services/platform/", StringComparison.Ordinal) && - (source.Contains("PlatformNativeHandles", StringComparison.Ordinal) || - source.Contains("PlatformNativeHandleId", StringComparison.Ordinal) || - source.Contains("INativeWindowSurface", StringComparison.Ordinal))) + if (relative.StartsWith( + "src/services/platform/", + StringComparison.Ordinal + ) + && (source.Contains( + "PlatformNativeHandles", + StringComparison.Ordinal + ) + || source.Contains( + "PlatformNativeHandleId", + StringComparison.Ordinal + ) + || source.Contains( + "INativeWindowSurface", + StringComparison.Ordinal + ))) { failures.Add($"{relative}: native surface ABI belongs to platform adapters, not the shared platform service."); } - if (source.Contains(".With", StringComparison.Ordinal) && - !relative.EndsWith( - "src/content/assets/Inno.Assets/Serialization/AssetSerializationContext.cs", - StringComparison.Ordinal)) + + if (source.Contains( + ".With", + StringComparison.Ordinal + ) + && !relative.EndsWith( + "src/content/assets/Inno.Assets/Serialization/AssetSerializationContext.cs", + StringComparison.Ordinal + )) { - failures.Add( - $"{relative}: asset-aware serialization must use the owner-complete AssetSerializationContext factory."); + failures.Add($"{relative}: asset-aware serialization must use the owner-complete AssetSerializationContext factory."); } + if (StaticManagerDeclarationPattern().IsMatch(source)) failures.Add($"{relative}: process-wide static Manager ownership is forbidden."); if (StaticLogFacadeCallPattern().IsMatch(source)) failures.Add($"{relative}: engine implementation must use an explicitly owned Logger."); - if ((relative.StartsWith("src/composition/editor/", StringComparison.Ordinal) || - relative.StartsWith("src/composition/player/Inno.Player/", StringComparison.Ordinal)) && - source.Contains("_ = typeof(", StringComparison.Ordinal)) - { - failures.Add( - $"{relative}: composition roots must discover engine modules from their declared dependency closure, not typeof anchors."); - } - if ((relative.StartsWith("src/composition/player/Inno.Player.Runtime/", StringComparison.Ordinal) || - relative.StartsWith("src/composition/shell/", StringComparison.Ordinal)) && - S_CONCRETE_ADAPTER_MARKERS.Any(source.Contains)) - { - failures.Add( - $"{relative}: product composition must use Shell and backend-neutral adapter contracts instead of concrete backend types."); - } - if (relative.StartsWith("src/adapters/audio/Inno.Adapter.Audio.MiniAudio/", StringComparison.Ordinal) && - (source.Contains("AudioContentProvider", StringComparison.Ordinal) || - source.Contains("AudioMixerExtension", StringComparison.Ordinal) || - source.Contains("AudioMixerFeature", StringComparison.Ordinal) || - source.Contains("TypeCatalog", StringComparison.Ordinal) || - source.Contains("System.Reflection", StringComparison.Ordinal))) - { - failures.Add( - $"{relative}: the real-time MiniAudio adapter cannot retain managed extension-generation objects or reflection state."); - } - if (relative.StartsWith("src/services/audio/", StringComparison.Ordinal) && - relative.EndsWith("/Properties/ScriptingApi.cs", StringComparison.Ordinal) && - (source.Contains("typeof(IAudioDevice)", StringComparison.Ordinal) || - source.Contains("typeof(AudioDevice)", StringComparison.Ordinal) || - source.Contains("Inno.Native", StringComparison.Ordinal) || - source.Contains("typeof(MiniAudio", StringComparison.Ordinal))) + if ((relative.StartsWith( + "src/composition/editor/", + StringComparison.Ordinal + ) + || relative.StartsWith( + "src/composition/player/Inno.Player/", + StringComparison.Ordinal + )) + && source.Contains( + "_ = typeof(", + StringComparison.Ordinal + )) + { + failures.Add($"{relative}: composition roots must discover engine modules from their declared dependency closure, not typeof anchors."); + } + + if ((relative.StartsWith( + "src/composition/player/Inno.Player.Runtime/", + StringComparison.Ordinal + ) + || relative.StartsWith( + "src/composition/shell/", + StringComparison.Ordinal + )) + && S_CONCRETE_ADAPTER_MARKERS.Any(source.Contains)) + { + failures.Add($"{relative}: product composition must use Shell and backend-neutral adapter contracts instead of concrete backend types."); + } + + if (relative.StartsWith( + "src/adapters/audio/Inno.Adapter.Audio.MiniAudio/", + StringComparison.Ordinal + ) + && (source.Contains( + "AudioContentProvider", + StringComparison.Ordinal + ) + || source.Contains( + "AudioMixerExtension", + StringComparison.Ordinal + ) + || source.Contains( + "AudioMixerFeature", + StringComparison.Ordinal + ) + || source.Contains( + "TypeCatalog", + StringComparison.Ordinal + ) + || source.Contains( + "System.Reflection", + StringComparison.Ordinal + ))) + { + failures.Add($"{relative}: the real-time MiniAudio adapter cannot retain managed extension-generation objects or reflection state."); + } + + if (relative.StartsWith( + "src/services/audio/", + StringComparison.Ordinal + ) + && relative.EndsWith( + "/Properties/ScriptingApi.cs", + StringComparison.Ordinal + ) + && (source.Contains( + "typeof(IAudioDevice)", + StringComparison.Ordinal + ) + || source.Contains( + "typeof(AudioDevice)", + StringComparison.Ordinal + ) + || source.Contains( + "Inno.Native", + StringComparison.Ordinal + ) + || source.Contains( + "typeof(MiniAudio", + StringComparison.Ordinal + ))) { failures.Add($"{relative}: scripting API cannot export audio devices, native bindings, or backend adapters."); } + int lineNumber = 0; using var reader = new StringReader(source); string? line; - while ((line = reader.ReadLine()) is not null) + while ((line = reader.ReadLine())is not null) { lineNumber++; string trimmed = line.TrimStart(); - if (trimmed.StartsWith("///", StringComparison.Ordinal) || - trimmed.StartsWith("//", StringComparison.Ordinal)) + if (trimmed.StartsWith("///", StringComparison.Ordinal) || trimmed.StartsWith("//", StringComparison.Ordinal)) { continue; } - if (PublicNativeSignaturePattern().IsMatch(line) && - !relative.StartsWith("native/", StringComparison.Ordinal)) + + if (PublicNativeSignaturePattern().IsMatch(line) && !relative.StartsWith("native/", StringComparison.Ordinal)) { failures.Add($"{relative}:{lineNumber}: public or protected API leaks a native implementation type."); } @@ -269,18 +367,17 @@ ICollection failures foreach (string path in EnumerateFiles(repositoryRoot, "*.csproj")) { string relative = Relative(repositoryRoot, path); - if (relative.StartsWith("extern/", StringComparison.Ordinal) || - relative.StartsWith("demo/", StringComparison.Ordinal)) + if (relative.StartsWith("extern/", StringComparison.Ordinal) || relative.StartsWith("demo/", StringComparison.Ordinal)) { continue; } + XDocument document = XDocument.Load(path); - string name = document.Descendants("AssemblyName").Select(static value => value.Value.Trim()) - .FirstOrDefault(static value => value.Length > 0) - ?? Path.GetFileNameWithoutExtension(path); + string name = document.Descendants("AssemblyName").Select(static value => value.Value.Trim()).FirstOrDefault(static value => value.Length > 0) ?? Path.GetFileNameWithoutExtension(path); var node = new ProjectNode(path, relative, name); graph.Add(Path.GetFullPath(path), node); } + foreach (ProjectNode node in graph.Values) { XDocument document = XDocument.Load(node.path); @@ -288,7 +385,7 @@ ICollection failures PackageDependencyBoundaryValidator.Validate(node.relative, document, failures); foreach (XElement reference in document.Descendants("ProjectReference")) { - if (string.Equals((string?)reference.Attribute("ReferenceOutputAssembly"), "false", StringComparison.OrdinalIgnoreCase)) + if (string.Equals((string? )reference.Attribute("ReferenceOutputAssembly"), "false", StringComparison.OrdinalIgnoreCase)) continue; string? include = reference.Attribute("Include")?.Value; if (string.IsNullOrWhiteSpace(include)) @@ -296,24 +393,25 @@ ICollection failures // SDK evaluation validates property-based references; this static pass does not invent property values. if (include.Contains("$(", StringComparison.Ordinal)) continue; - string normalizedInclude = include - .Replace('\\', Path.DirectorySeparatorChar) - .Replace('/', Path.DirectorySeparatorChar); + string normalizedInclude = include.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); string targetPath = Path.GetFullPath(normalizedInclude, Path.GetDirectoryName(node.path)!); if (!graph.TryGetValue(targetPath, out ProjectNode? target)) { failures.Add($"{node.relative}: project reference '{include}' does not resolve to a repository project."); continue; } + if (node.references.Contains(target)) { failures.Add($"{node.relative}: project reference '{include}' is duplicated."); continue; } + node.references.Add(target); ValidateReferenceBoundary(node, target, failures); } } + return graph; } @@ -325,18 +423,25 @@ ICollection failures foreach (XElement noWarn in document.Descendants("NoWarn")) { string value = noWarn.Value; - if (value.Contains("CS1572", StringComparison.OrdinalIgnoreCase) || - value.Contains("CS1573", StringComparison.OrdinalIgnoreCase) || - value.Contains("CS1591", StringComparison.OrdinalIgnoreCase)) + if (value.Contains( + "CS1572", + StringComparison.OrdinalIgnoreCase + ) + || value.Contains( + "CS1573", + StringComparison.OrdinalIgnoreCase + ) + || value.Contains( + "CS1591", + StringComparison.OrdinalIgnoreCase + )) { failures.Add($"{project.relative}: public XML contract warnings cannot be suppressed."); } } } - private static bool IsNativeProject(string path) => path.StartsWith("backends/", StringComparison.Ordinal) - && path.Contains("/native/", StringComparison.Ordinal); - + private static bool IsNativeProject(string path) => path.StartsWith("backends/", StringComparison.Ordinal) && path.Contains("/native/", StringComparison.Ordinal); private static void ValidateReferenceBoundary( ProjectNode project, ProjectNode target, @@ -347,98 +452,184 @@ ICollection failures bool foundationTarget = targetPath.StartsWith("src/foundation/", StringComparison.Ordinal); if ((project.name is "Inno.Rendering" or "Inno.Content") && !foundationTarget) failures.Add($"{sourcePath}: neutral mechanisms may only reference Foundation contracts, not {targetPath}."); - if (project.name == "Inno.Rendering.Assets" && target.name is - "Inno.Rendering.Runtime" or "Inno.Rendering.Shaders" or "Inno.Rendering.Assets.Authoring" or "Inno.Assets.Pipeline") + if (project.name == "Inno.Rendering.Assets" + && target.name is "Inno.Rendering.Runtime" or "Inno.Rendering.Shaders" or "Inno.Rendering.Assets.Authoring" or "Inno.Assets.Pipeline") failures.Add($"{sourcePath}: runtime asset definitions cannot reference authoring or runtime owners in {targetPath}."); - if (project.name == "Inno.Rendering.Runtime" && target.name is - "Inno.Rendering.Assets.Authoring" or "Inno.Assets.Pipeline" or "Inno.Rendering.Shaders") + if (project.name == "Inno.Rendering.Runtime" && target.name is "Inno.Rendering.Assets.Authoring" or "Inno.Assets.Pipeline" or "Inno.Rendering.Shaders") failures.Add($"{sourcePath}: rendering runtime cannot include authoring implementation {targetPath}."); if (project.name == "Inno.Rendering.Shaders" && target.name == "Inno.Rendering.Runtime") failures.Add($"{sourcePath}: shader authoring cannot depend on the runtime owner {targetPath}."); - if (project.name == "Inno.Adapter.Input" && (IsNativeProject(targetPath) - || IsConcreteAdapter(target.name))) + if (project.name == "Inno.Adapter.Input" && (IsNativeProject(targetPath) || IsConcreteAdapter(target.name))) failures.Add($"{sourcePath}: shared event input cannot depend on native or platform adapters in {targetPath}."); - if (project.name == "Inno.Build" && (targetPath.StartsWith("build/composition/", StringComparison.Ordinal) - || targetPath.StartsWith("build/pipeline/Inno.Build.Platform.", StringComparison.Ordinal) + if (project.name == "Inno.Build" + && (targetPath.StartsWith( + "build/composition/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "build/pipeline/Inno.Build.Platform.", + StringComparison.Ordinal + ) || target.name == "Inno.Build.Managed.DotNet")) failures.Add($"{sourcePath}: the build pipeline must receive providers instead of referencing {targetPath}."); - if (IsNativeProject(sourcePath) && !IsNativeProject(targetPath) - && !(project.name == "Inno.Native.LibraryLoading" && target.name == "Inno.Core.IO")) + if (IsNativeProject(sourcePath) && !IsNativeProject(targetPath) && !(project.name == "Inno.Native.LibraryLoading" && target.name == "Inno.Core.IO")) { failures.Add($"{sourcePath}: Native cannot reference upper-layer project {targetPath}."); } - if (sourcePath.StartsWith("src/foundation/", StringComparison.Ordinal) && - !targetPath.StartsWith("src/foundation/", StringComparison.Ordinal)) + + if (sourcePath.StartsWith("src/foundation/", StringComparison.Ordinal) && !targetPath.StartsWith("src/foundation/", StringComparison.Ordinal)) { failures.Add($"{sourcePath}: Core cannot reference upper-layer project {targetPath}."); } - if (sourcePath.StartsWith("build/", StringComparison.Ordinal) && - !sourcePath.StartsWith("build/cli/", StringComparison.Ordinal) && - targetPath.StartsWith("src/composition/editor/", StringComparison.Ordinal)) + + if (sourcePath.StartsWith( + "build/", + StringComparison.Ordinal + ) + && !sourcePath.StartsWith( + "build/cli/", + StringComparison.Ordinal + ) + && targetPath.StartsWith( + "src/composition/editor/", + StringComparison.Ordinal + )) { failures.Add($"{sourcePath}: Build cannot reference Editor project {targetPath}."); } - if ((string.Equals(project.name, "Inno.Rendering", StringComparison.Ordinal) || - string.Equals(project.name, "Inno.Rendering.Runtime", StringComparison.Ordinal)) && - (targetPath.StartsWith("build/", StringComparison.Ordinal) || - targetPath.StartsWith("src/adapters/", StringComparison.Ordinal) || - IsNativeProject(targetPath))) + + if ((string.Equals( + project.name, + "Inno.Rendering", + StringComparison.Ordinal + ) + || string.Equals( + project.name, + "Inno.Rendering.Runtime", + StringComparison.Ordinal + )) + && (targetPath.StartsWith( + "build/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/adapters/", + StringComparison.Ordinal + ) + || IsNativeProject(targetPath))) { failures.Add($"{sourcePath}: backend-neutral Rendering cannot reference implementation project {targetPath}."); } - if (string.Equals(project.name, "Inno.Build", StringComparison.Ordinal) && - targetPath.StartsWith("build/support/", StringComparison.Ordinal)) + + if (string.Equals(project.name, "Inno.Build", StringComparison.Ordinal) && targetPath.StartsWith("build/support/", StringComparison.Ordinal)) { failures.Add($"{sourcePath}: Build pipeline must depend on the provisioner contract, not Support Pack implementations."); } - if (sourcePath.Contains("Inno.Rendering/", StringComparison.Ordinal) && - (targetPath.Contains("MaterialGraph", StringComparison.Ordinal) || - targetPath.StartsWith("src/content/scene/", StringComparison.Ordinal) || - targetPath.StartsWith("src/composition/editor/", StringComparison.Ordinal))) + + if (sourcePath.Contains( + "Inno.Rendering/", + StringComparison.Ordinal + ) + && (targetPath.Contains( + "MaterialGraph", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/content/scene/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/composition/editor/", + StringComparison.Ordinal + ))) { failures.Add($"{sourcePath}: backend-neutral Rendering cannot reference {targetPath}."); } - if (string.Equals(project.name, "Inno.Audio", StringComparison.Ordinal) && - (targetPath.StartsWith("src/services/audio/Inno.Audio.Runtime/", StringComparison.Ordinal) || - targetPath.StartsWith("src/adapters/audio/Inno.Adapter.Audio.MiniAudio/", StringComparison.Ordinal) || - targetPath.StartsWith("src/content/scene/", StringComparison.Ordinal) || - targetPath.StartsWith("src/runtime/", StringComparison.Ordinal) || - targetPath.StartsWith("src/composition/editor/", StringComparison.Ordinal) || - targetPath.StartsWith("src/services/platform/", StringComparison.Ordinal) || - IsNativeProject(targetPath))) + + if (string.Equals( + project.name, + "Inno.Audio", + StringComparison.Ordinal + ) + && (targetPath.StartsWith( + "src/services/audio/Inno.Audio.Runtime/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/adapters/audio/Inno.Adapter.Audio.MiniAudio/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/content/scene/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/runtime/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/composition/editor/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/services/platform/", + StringComparison.Ordinal + ) + || IsNativeProject(targetPath))) { failures.Add($"{sourcePath}: backend-neutral Audio cannot reference {targetPath}."); } - if (string.Equals(project.name, "Inno.Animation", StringComparison.Ordinal) && - (targetPath.StartsWith("src/runtime/", StringComparison.Ordinal) || - targetPath.StartsWith("src/content/scene/", StringComparison.Ordinal) || - targetPath.StartsWith("src/services/rendering/", StringComparison.Ordinal) || - targetPath.StartsWith("src/services/audio/", StringComparison.Ordinal) || - targetPath.StartsWith("src/composition/editor/", StringComparison.Ordinal) || - IsNativeProject(targetPath))) + + if (string.Equals( + project.name, + "Inno.Animation", + StringComparison.Ordinal + ) + && (targetPath.StartsWith( + "src/runtime/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/content/scene/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/services/rendering/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/services/audio/", + StringComparison.Ordinal + ) + || targetPath.StartsWith( + "src/composition/editor/", + StringComparison.Ordinal + ) + || IsNativeProject(targetPath))) { failures.Add($"{sourcePath}: backend-neutral Animation cannot reference {targetPath}."); } - if (target.name.Contains("Inno.Native.Bgfx", StringComparison.Ordinal) && - !IsAllowedBgfxConsumer(project.name)) + + if (target.name.Contains("Inno.Native.Bgfx", StringComparison.Ordinal) && !IsAllowedBgfxConsumer(project.name)) { failures.Add($"{sourcePath}: BGFX native code is restricted to BGFX adapters and toolchains."); } - if (target.name.Contains("Inno.Native.Sdl3", StringComparison.OrdinalIgnoreCase) && - !IsAllowedSdlConsumer(project.name)) + + if (target.name.Contains("Inno.Native.Sdl3", StringComparison.OrdinalIgnoreCase) && !IsAllowedSdlConsumer(project.name)) { failures.Add($"{sourcePath}: SDL3 native code is restricted to SDL3 adapters and toolchains."); } - if (target.name.Contains("Inno.Native.MiniAudio", StringComparison.Ordinal) && - !IsAllowedMiniAudioConsumer(project.name)) + + if (target.name.Contains("Inno.Native.MiniAudio", StringComparison.Ordinal) && !IsAllowedMiniAudioConsumer(project.name)) { failures.Add($"{sourcePath}: miniaudio native code is restricted to the MiniAudio adapter and toolchain."); } - if (target.name.Contains("Inno.Native.UI", StringComparison.Ordinal) && - !IsAllowedUiNativeConsumer(project.name)) + + if (target.name.Contains("Inno.Native.UI", StringComparison.Ordinal) && !IsAllowedUiNativeConsumer(project.name)) { failures.Add($"{sourcePath}: RmlUi native code is restricted to the RmlUi adapter and UI toolchain."); } + if (IsAdapterContract(project.name) && IsConcreteAdapter(target.name)) { failures.Add($"{sourcePath}: adapter contract projects cannot reference concrete implementation {targetPath}."); @@ -454,7 +645,6 @@ ICollection failures var stack = new List(); foreach (ProjectNode node in graph.Values) Visit(node); - void Visit(ProjectNode node) { if (states.TryGetValue(node, out VisitState state)) @@ -465,8 +655,10 @@ void Visit(ProjectNode node) string cycle = string.Join(" -> ", stack.Skip(start).Append(node).Select(static value => value.name)); failures.Add($"{Relative(repositoryRoot, node.path)}: project reference cycle detected: {cycle}."); } + return; } + states[node] = VisitState.Visiting; stack.Add(node); foreach (ProjectNode target in node.references) @@ -487,6 +679,7 @@ ICollection failures failures.Add("src/composition/player/Inno.Player: Player composition project is missing."); return; } + var visited = new HashSet(); var pending = new Stack(); pending.Push(player); @@ -495,12 +688,11 @@ ICollection failures ProjectNode current = pending.Pop(); if (!visited.Add(current)) continue; - if (!ReferenceEquals(current, player) && - S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS.Any(fragment => - current.name.Contains(fragment, StringComparison.OrdinalIgnoreCase))) + if (!ReferenceEquals(current, player) && S_FORBIDDEN_PLAYER_PROJECT_FRAGMENTS.Any(fragment => current.name.Contains(fragment, StringComparison.OrdinalIgnoreCase))) { failures.Add($"{Relative(repositoryRoot, player.path)}: Player dependency closure contains forbidden project {current.name}."); } + foreach (ProjectNode target in current.references) pending.Push(target); } @@ -517,13 +709,9 @@ ICollection failures failures.Add($"InnoEngine.sln: removed project '{removed}' remains in the solution."); foreach (string projectPath in EnumerateFiles(repositoryRoot, "*.csproj")) { - if (string.Equals( - Path.GetFileNameWithoutExtension(projectPath), - removed, - StringComparison.Ordinal)) + if (string.Equals(Path.GetFileNameWithoutExtension(projectPath), removed, StringComparison.Ordinal)) { - failures.Add( - $"{Relative(repositoryRoot, projectPath)}: removed project '{removed}' remains in the repository."); + failures.Add($"{Relative(repositoryRoot, projectPath)}: removed project '{removed}' remains in the repository."); } } } @@ -543,8 +731,7 @@ ICollection failures ConceptualLayer? targetLayer = ClassifyConceptualLayer(target); if (targetLayer is null || targetLayer <= sourceLayer) continue; - failures.Add( - $"{project.relative}: {sourceLayer} project cannot reference upper {targetLayer} project {target.relative}."); + failures.Add($"{project.relative}: {sourceLayer} project cannot reference upper {targetLayer} project {target.relative}."); } } } @@ -555,26 +742,23 @@ ICollection failures ) { ValidateHost("Inno.Player.Runtime"); ValidateHost("Inno.Editor.Hosting"); - void ValidateHost(string projectName) { - ProjectNode? host = graph.Values.SingleOrDefault(value => - string.Equals(value.name, projectName, StringComparison.Ordinal)); + ProjectNode? host = graph.Values.SingleOrDefault(value => string.Equals(value.name, projectName, StringComparison.Ordinal)); if (host is null) { failures.Add($"{projectName}: product composition project is missing."); return; } - if (!host.references.Any(static target => - string.Equals(target.name, "Inno.Shell", StringComparison.Ordinal))) + + if (!host.references.Any(static target => string.Equals(target.name, "Inno.Shell", StringComparison.Ordinal))) { failures.Add($"{host.relative}: product composition must inherit the common Inno.Shell lifecycle."); } - foreach (ProjectNode target in host.references.Where(target => - projectName == "Inno.Player.Runtime" && IsConcreteAdapter(target.name))) + + foreach (ProjectNode target in host.references.Where(target => projectName == "Inno.Player.Runtime" && IsConcreteAdapter(target.name))) { - failures.Add( - $"{host.relative}: product composition cannot reference concrete adapter project {target.relative}; use Inno.Adapter.Default and neutral contracts."); + failures.Add($"{host.relative}: product composition cannot reference concrete adapter project {target.relative}; use Inno.Adapter.Default and neutral contracts."); } } } @@ -587,100 +771,207 @@ void ValidateHost(string projectName) { return ConceptualLayer.Foundation; } + if (project.relative.StartsWith("src/composition/", StringComparison.Ordinal)) { return ConceptualLayer.Composition; } + if (project.name.StartsWith("Inno.Adapter", StringComparison.Ordinal)) { return ConceptualLayer.Adapter; } + return ConceptualLayer.EngineCapability; } - private static bool IsAdapterContract(string name) - => string.Equals(name, "Inno.Adapter", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Platform", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Input", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Storage", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Rendering", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Audio", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Text", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.UI", StringComparison.Ordinal) || - string.Equals(name, "Inno.Adapter.Presentation", StringComparison.Ordinal); - - private static bool IsConcreteAdapter(string name) - => name.StartsWith("Inno.Adapter.Content.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Platform.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Input.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Storage.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Rendering.", StringComparison.Ordinal) && - !string.Equals(name, "Inno.Adapter.Rendering.Authoring", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Audio.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Text.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.UI.", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Presentation.", StringComparison.Ordinal); - - private static bool IsAllowedBgfxConsumer(string name) - => name.StartsWith("Inno.Adapter.Rendering.Bgfx", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Presentation.ImGui.Bgfx", StringComparison.Ordinal) || - name.StartsWith("Inno.Build.Toolchains.Bgfx", StringComparison.Ordinal) || - name.StartsWith("Inno.Native.Bgfx", StringComparison.Ordinal) || - name.EndsWith(".Tests", StringComparison.Ordinal); - - private static bool IsAllowedSdlConsumer(string name) - => name.StartsWith("Inno.Adapter.Platform.Sdl3", StringComparison.Ordinal) || - name.StartsWith("Inno.Adapter.Presentation.ImGui.Sdl3", StringComparison.Ordinal) || - name.StartsWith("Inno.Build.Toolchains.Sdl3", StringComparison.Ordinal) || - name.StartsWith("Inno.Native.Sdl3", StringComparison.Ordinal) || - name is "Inno.Integration.WindowsX64.Sdl3" or "Inno.Integration.MacOSArm64.Sdl3" or "Inno.Integration.BrowserWasm32.Sdl3" || - name.EndsWith(".Tests", StringComparison.Ordinal); - - private static bool IsAllowedMiniAudioConsumer(string name) - => name.StartsWith("Inno.Adapter.Audio.MiniAudio", StringComparison.Ordinal) || - name.StartsWith("Inno.Build.Toolchains.MiniAudio", StringComparison.Ordinal) || - name.StartsWith("Inno.Native.MiniAudio", StringComparison.Ordinal); - - private static bool IsAllowedUiNativeConsumer(string name) - => name.StartsWith("Inno.Adapter.UI.RmlUi", StringComparison.Ordinal) || - name.StartsWith("Inno.Build.Toolchains.UI", StringComparison.Ordinal) || - name.StartsWith("Inno.Native.UI", StringComparison.Ordinal) || - name.EndsWith(".Tests", StringComparison.Ordinal); - + private static bool IsAdapterContract(string name) => string.Equals( + name, + "Inno.Adapter", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Platform", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Input", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Storage", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Rendering", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Audio", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Text", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.UI", + StringComparison.Ordinal + ) + || string.Equals( + name, + "Inno.Adapter.Presentation", + StringComparison.Ordinal + ); + private static bool IsConcreteAdapter(string name) => name.StartsWith( + "Inno.Adapter.Content.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Platform.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Input.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Storage.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Rendering.", + StringComparison.Ordinal + ) + && !string.Equals( + name, + "Inno.Adapter.Rendering.Authoring", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Audio.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Text.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.UI.", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Presentation.", + StringComparison.Ordinal + ); + private static bool IsAllowedBgfxConsumer(string name) => name.StartsWith( + "Inno.Adapter.Rendering.Bgfx", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Presentation.ImGui.Bgfx", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Build.Toolchains.Bgfx", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Native.Bgfx", + StringComparison.Ordinal + ) + || name.EndsWith( + ".Tests", + StringComparison.Ordinal + ); + private static bool IsAllowedSdlConsumer(string name) => name.StartsWith( + "Inno.Adapter.Platform.Sdl3", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Adapter.Presentation.ImGui.Sdl3", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Build.Toolchains.Sdl3", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Native.Sdl3", + StringComparison.Ordinal + ) + || name is "Inno.Integration.WindowsX64.Sdl3" or "Inno.Integration.MacOSArm64.Sdl3" or "Inno.Integration.BrowserWasm32.Sdl3" + || name.EndsWith( + ".Tests", + StringComparison.Ordinal + ); + private static bool IsAllowedMiniAudioConsumer(string name) => name.StartsWith( + "Inno.Adapter.Audio.MiniAudio", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Build.Toolchains.MiniAudio", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Native.MiniAudio", + StringComparison.Ordinal + ); + private static bool IsAllowedUiNativeConsumer(string name) => name.StartsWith( + "Inno.Adapter.UI.RmlUi", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Build.Toolchains.UI", + StringComparison.Ordinal + ) + || name.StartsWith( + "Inno.Native.UI", + StringComparison.Ordinal + ) + || name.EndsWith( + ".Tests", + StringComparison.Ordinal + ); private static IEnumerable EnumerateFiles( string root, string pattern ) => RepositorySourceInventory.Files(root, pattern); - - private static bool IsGenerated(string source) - => source.Contains("", StringComparison.OrdinalIgnoreCase) || - source.Contains("[GeneratedCode", StringComparison.Ordinal); - + private static bool IsGenerated(string source) => source.Contains( + "", + StringComparison.OrdinalIgnoreCase + ) + || source.Contains( + "[GeneratedCode", + StringComparison.Ordinal + ); private static string Relative( string repositoryRoot, string path ) => Path.GetRelativePath(repositoryRoot, path).Replace('\\', '/'); - [GeneratedRegex(@"\b(class|struct|interface|enum|record)\s+([A-Za-z_]\w*)")] private static partial Regex DeclaredTypePattern(); - [GeneratedRegex(@"\bstatic\s+class\s+(DiagnosticManager|LogManager|IdentityManager|JobSystemManager|AssemblyManager|TypeCacheManager|SerializationManager|ProjectSettingsManager|AssetManager|PluginManager|Shell)\b")] private static partial Regex StaticManagerDeclarationPattern(); - [GeneratedRegex(@"\bLog\.(Debug|Info|Warn|Error|Fatal)\s*\(")] private static partial Regex StaticLogFacadeCallPattern(); - [GeneratedRegex(@"\b(public|protected)\b[^\r\n;{]*(Inno\.Native\.|bgfx_|SDL[A-Z]|ImGuiPtr|Ma[A-Z]\w*)")] private static partial Regex PublicNativeSignaturePattern(); - [GeneratedRegex(@"BindingFlags\s*\.[^\r\n;]*(NonPublic|Private)|(GetField|GetMethod|GetProperty|GetConstructor)\s*\([^\r\n;]*BindingFlags\s*\.[^\r\n;]*(NonPublic|Private)")] private static partial Regex NonPublicReflectionPattern(); - private sealed class ProjectNode( string path, string relative, string name - ) { + ) + { internal string path { get; } = path; internal string relative { get; } = relative; internal string name { get; } = name; diff --git a/tools/Inno.Tooling.Architecture/ArchitectureValidator.cs b/tools/Inno.Tooling.Architecture/ArchitectureValidator.cs index 462690926..93a6b859b 100644 --- a/tools/Inno.Tooling.Architecture/ArchitectureValidator.cs +++ b/tools/Inno.Tooling.Architecture/ArchitectureValidator.cs @@ -74,6 +74,7 @@ public static int Execute( bool selectedConfiguration = false; string? dotnetHost = null; string? projectGraphPath = null; + string? artifactsPath = null; var paths = new List(); for (int index = 0; index < arguments.Length; index++) { @@ -96,22 +97,28 @@ public static int Execute( dotnetHost = arguments[++index]; else if (argument == "--project-graph" && projectGraphPath is null && index + 1 < arguments.Length) projectGraphPath = arguments[++index]; + else if (argument == "--artifacts-path" && artifactsPath is null && index + 1 < arguments.Length) + artifactsPath = Path.GetFullPath(arguments[++index]); else if (argument == "--configuration" && !selectedConfiguration && index + 1 < arguments.Length) { configuration = arguments[++index] switch { "Debug" => "Debug", "Release" => "Release", - _ => throw new ArgumentException("Architecture configuration must be Debug or Release.", nameof(arguments)) + "SourceDebug" => "SourceDebug", + _ => throw new ArgumentException("Architecture configuration must be Debug, SourceDebug or Release.", nameof(arguments)) }; selectedConfiguration = true; } else if (!argument.StartsWith("--", StringComparison.Ordinal) && paths.Count == 0) paths.Add(argument); else - throw new ArgumentException("Usage: verify [engine-root] [--configuration Debug|Release] [--expand-xml] [--dotnet SDK] [--project-graph output.json] [--wiki] [--wiki-target targetId] [--wiki-api inventory.json] [--wiki-examples examples.json] [--wiki-runtime-reference assembly.dll] [--wiki-editor-reference assembly.dll]", nameof(arguments)); + throw new ArgumentException( + "Usage: verify [engine-root] [--configuration Debug|SourceDebug|Release] [--expand-xml] [--dotnet SDK] [--artifacts-path directory] [--project-graph output.json] [--wiki] [--wiki-target targetId] [--wiki-api inventory.json] [--wiki-examples examples.json] [--wiki-runtime-reference assembly.dll] [--wiki-editor-reference assembly.dll]", + nameof(arguments) + ); } - if (projectGraphPath is not null && dotnetHost is null) + if ((projectGraphPath is not null || artifactsPath is not null) && dotnetHost is null) throw new ArgumentException("Evaluated project graph output requires an explicitly selected --dotnet SDK.", nameof(arguments)); if ((wikiInventoryPath is not null || wikiExamplesPath is not null || wikiRuntimeReference is not null || wikiEditorReference is not null || wikiTarget is not null) && !wiki) @@ -127,8 +134,8 @@ public static int Execute( } List failures = []; - WikiProjectReferences? evaluatedReferences = !wiki || dotnetHost is null ? null - : new WikiProjectReferences(repositoryRoot, configuration, dotnetHost, wikiTarget); + WikiProjectReferences? evaluatedReferences = dotnetHost is null ? null + : new WikiProjectReferences(repositoryRoot, configuration, dotnetHost, wikiTarget, artifactsPath); var documentationModels = new DocumentationSourceModels(repositoryRoot, configuration, evaluatedReferences, cancellationToken, failures); if (wiki) @@ -157,10 +164,11 @@ public static int Execute( ValidateProductionSolutionFolders(repositoryRoot, failures); ValidateTestSolutionFolders(repositoryRoot, failures); ArchitectureRules.Validate(repositoryRoot, failures); - PublicApiBoundaryValidator.Validate(repositoryRoot, configuration, failures); + IReadOnlyDictionary? outputs = null; if (dotnetHost is not null) - MSBuildProjectGraphValidator.ValidateAsync(repositoryRoot, configuration, dotnetHost, + outputs = MSBuildProjectGraphValidator.ValidateAsync(repositoryRoot, configuration, dotnetHost, artifactsPath, projectGraphPath, failures, cancellationToken).GetAwaiter().GetResult(); + PublicApiBoundaryValidator.Validate(repositoryRoot, configuration, failures, outputs); if (failures.Count == 0) { Console.WriteLine("InnoEngine architecture validation passed."); @@ -584,7 +592,7 @@ ICollection failures return "src/composition/editor/panels"; if (string.Equals(projectName, "Inno.Editor.Hosting", StringComparison.Ordinal)) return "src/composition/editor/hosting"; - if (string.Equals(projectName, "Inno.Editor.ImGui", StringComparison.Ordinal)) + if (projectName is "Inno.Editor.ImGui" or "Inno.Editor.Workbench" or "Inno.Editor.Assets.ImGui" or "Inno.Editor.Shaders.ImGui" or "Inno.Editor.Graph.ImGui") return "src/composition/editor/presentation"; if (string.Equals(projectName, "Inno.Editor.Core", StringComparison.Ordinal) || string.Equals(projectName, "Inno.Editor.Diagnostics", StringComparison.Ordinal) || diff --git a/tools/Inno.Tooling.Architecture/CSharpStyleValidator.cs b/tools/Inno.Tooling.Architecture/CSharpStyleValidator.cs index cf47171ac..670581609 100644 --- a/tools/Inno.Tooling.Architecture/CSharpStyleValidator.cs +++ b/tools/Inno.Tooling.Architecture/CSharpStyleValidator.cs @@ -18,6 +18,7 @@ ICollection failures ) { SyntaxTree tree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.CSharp14)); SourceText text = tree.GetText(); + EditorPresentationSourceValidator.Validate(relativePath, tree, failures); foreach (ParameterListSyntax parameters in tree.GetRoot().DescendantNodes().OfType()) { if (parameters.Parameters.Count <= 1) diff --git a/tools/Inno.Tooling.Architecture/DocumentationSourceModels.cs b/tools/Inno.Tooling.Architecture/DocumentationSourceModels.cs index 5d54a2532..f1c60977b 100644 --- a/tools/Inno.Tooling.Architecture/DocumentationSourceModels.cs +++ b/tools/Inno.Tooling.Architecture/DocumentationSourceModels.cs @@ -42,6 +42,12 @@ internal DocumentationSourceModels( .ToArray(); m_projectNames = projects.Select(Path.GetFileNameWithoutExtension) .OfType().ToHashSet(StringComparer.Ordinal); + if (m_projectReferences is not null) + { + m_references = []; + m_frameworkReferences = []; + return; + } var paths = new Dictionary(StringComparer.OrdinalIgnoreCase); string runtimeDirectory = RuntimeEnvironment.GetRuntimeDirectory().TrimEnd(Path.DirectorySeparatorChar); string frameworkReferences = Path.GetFullPath(Path.Combine(runtimeDirectory, "..", "..", "..", @@ -63,11 +69,6 @@ internal DocumentationSourceModels( path.StartsWith(frameworkReferences + Path.DirectorySeparatorChar, pathComparison) || path.StartsWith(runtimeDirectory + Path.DirectorySeparatorChar, pathComparison)) .Select(static path => (MetadataReference)MetadataReference.CreateFromFile(path)).ToArray(); - if (m_projectReferences is not null) - { - m_references = []; - return; - } var projectOutputs = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string project in projects) { @@ -107,7 +108,9 @@ internal DocumentationSourceModels( internal bool IsExcludedProduct(string projectDirectory) => m_projectReferences?.IsExcludedProduct(Directory.EnumerateFiles(projectDirectory, "*.csproj").Single()) == true; - internal IReadOnlyList frameworkReferences => m_frameworkReferences; + internal IReadOnlyList frameworkReferences => m_projectReferences is null + ? m_frameworkReferences + : m_projectReferences.GetFrameworkReferences(m_cancellation); internal SemanticModel? GetModel(string path) { diff --git a/tools/Inno.Tooling.Architecture/EditorPresentationSourceValidator.cs b/tools/Inno.Tooling.Architecture/EditorPresentationSourceValidator.cs new file mode 100644 index 000000000..7c92df433 --- /dev/null +++ b/tools/Inno.Tooling.Architecture/EditorPresentationSourceValidator.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Inno.Tooling.Architecture; + +internal static class EditorPresentationSourceValidator +{ + private const string C_FACADE = "Inno.Editor.ImGui.ImGui"; + + internal static void Validate( + string relativePath, + SyntaxTree tree, + ICollection failures + ) { + if (!relativePath.StartsWith("src/composition/editor/", StringComparison.Ordinal)) + return; + SyntaxNode root = tree.GetRoot(); + UsingDirectiveSyntax[] aliases = root.DescendantNodes().OfType() + .Where(static directive => directive.Alias is not null && directive.Name is not null).ToArray(); + foreach (IGrouping group in aliases.GroupBy( + static directive => directive.Name!.ToString(), StringComparer.Ordinal)) + { + if (group.Count() > 1) + Report(relativePath, tree, group.First(), "one type cannot have multiple presentation aliases in the same file", failures); + } + var facadeAliases = aliases.Where(static directive => directive.Name!.ToString() == C_FACADE) + .Select(static directive => directive.Alias!.Name.Identifier.ValueText).ToHashSet(StringComparer.Ordinal); + facadeAliases.Add(C_FACADE); + facadeAliases.Add("global::" + C_FACADE); + foreach (BlockSyntax block in root.DescendantNodes().OfType()) + { + if (block.Parent is not (ForStatementSyntax or ForEachStatementSyntax or ForEachVariableStatementSyntax or WhileStatementSyntax or DoStatementSyntax)) + continue; + ExpressionStatementSyntax[] statements = block.Statements.OfType().ToArray(); + foreach (ExpressionStatementSyntax push in statements.Where(statement => IsFacadeCall(statement, "PushId", facadeAliases))) + { + if (statements.Any(statement => statement.SpanStart > push.SpanStart && IsFacadeCall(statement, "PopId", facadeAliases))) + Report(relativePath, tree, push, "presentation loop ID scopes require local try/finally cleanup", failures); + } + } + foreach (DocumentationCommentTriviaSyntax comment in root.DescendantTrivia() + .Select(static trivia => trivia.GetStructure()).OfType()) + { + if (comment.Content.OfType().Count(static element => element.StartTag.Name.LocalName.ValueText == "summary") > 1) + Report(relativePath, tree, comment, "a declaration cannot retain multiple XML summary elements", failures); + } + } + + private static bool IsFacadeCall( + ExpressionStatementSyntax statement, + string method, + IReadOnlySet receivers + ) => statement.Expression is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax member } + && member.Name.Identifier.ValueText == method && receivers.Contains(member.Expression.ToString()); + + private static void Report( + string relativePath, + SyntaxTree tree, + SyntaxNode node, + string message, + ICollection failures + ) => failures.Add($"{relativePath}:{tree.GetLineSpan(node.Span).StartLinePosition.Line + 1}: {message}."); +} diff --git a/tools/Inno.Tooling.Architecture/EditorUiBoundaryValidator.cs b/tools/Inno.Tooling.Architecture/EditorUiBoundaryValidator.cs new file mode 100644 index 000000000..bd8ac4808 --- /dev/null +++ b/tools/Inno.Tooling.Architecture/EditorUiBoundaryValidator.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Inno.Tooling.Architecture; +internal static class EditorUiBoundaryValidator +{ + internal static void Validate( + string repositoryRoot, + ICollection failures + ) { + string editorRoot = Path.Combine(repositoryRoot, "src/composition/editor"); + var projects = new List(RepositorySourceInventory.Files(editorRoot, "*.csproj")); + string sdkHost = Path.Combine(repositoryRoot, "build/cli/Inno.Build.Cli/Inno.Build.Cli.csproj"); + if (File.Exists(sdkHost)) + projects.Add(sdkHost); + foreach (string path in projects) + { + string name = Path.GetFileNameWithoutExtension(path); + string relative = Path.GetRelativePath(repositoryRoot, path).Replace('\\', '/'); + bool feature = relative.StartsWith("src/composition/editor/features/", StringComparison.Ordinal); + bool panel = relative.StartsWith("src/composition/editor/panels/", StringComparison.Ordinal); + foreach (XElement reference in XDocument.Load(path).Descendants("ProjectReference")) + { + string dependency = Path.GetFileNameWithoutExtension(((string? )reference.Attribute("Include") ?? "").Replace('\\', '/')); + if (feature + && (dependency.Contains( + "ImGui", + StringComparison.Ordinal + ) + || dependency.StartsWith( + "Inno.Native.", + StringComparison.Ordinal + ) + || dependency.StartsWith( + "Inno.Editor.Panel.", + StringComparison.Ordinal + ) + || dependency is "Inno.Editor.Workbench" or "Inno.Editor.Hosting")) + failures.Add($"{relative}: Editor feature editing must be presentation-independent; remove {dependency}."); + if (panel && dependency.StartsWith("Inno.Editor.Panel.", StringComparison.Ordinal)) + failures.Add($"{relative}: Panels cannot depend on other Panels; move shared behavior to its feature owner ({dependency})."); + if (name == "Inno.Build.Cli" && dependency.StartsWith("Inno.Editor.Panel.", StringComparison.Ordinal)) + { + string include = (string?)reference.Attribute("Include") ?? string.Empty; + string referencedProject = Path.GetFullPath(include.Replace('\\', '/'), Path.GetDirectoryName(path)!); + if (!DeclaresScriptingApi(referencedProject)) + failures.Add($"{relative}: the script SDK host must reference API owners instead of transitive Panel carriers ({dependency})."); + } + if (name != "Inno.Editor.ImGui" && name != "Inno.Editor.Hosting" && dependency.StartsWith("Inno.Native.", StringComparison.Ordinal)) + failures.Add($"{relative}: native UI access belongs to the Editor ImGui adapter, not {name}."); + if (name is "Inno.Editor.Graph" or "Inno.Editor.Graph.ImGui" + && (dependency.Contains( + "Shader", + StringComparison.Ordinal + ) + || dependency.Contains( + "Rendering", + StringComparison.Ordinal + ))) + failures.Add($"{relative}: reusable Graph mechanisms cannot depend on Shader or Rendering semantics."); + } + } + } + + private static bool DeclaresScriptingApi(string project) + { + string manifest = Path.Combine(Path.GetDirectoryName(project)!, "Properties/ScriptingApi.cs"); + if (!File.Exists(manifest)) + return false; + var syntax = CSharpSyntaxTree.ParseText( + File.ReadAllText(manifest), + new CSharpParseOptions(LanguageVersion.CSharp14) + ).GetCompilationUnitRoot(); + foreach (AttributeListSyntax list in syntax.AttributeLists) + { + if (list.Target?.Identifier.ValueText != "assembly") + continue; + foreach (AttributeSyntax attribute in list.Attributes) + if (attribute.Name.GetLastToken().ValueText is "ScriptingApiExport" or "ScriptingApiExportAttribute") + return true; + } + return false; + } +} diff --git a/tools/Inno.Tooling.Architecture/MSBuildProjectGraphValidator.cs b/tools/Inno.Tooling.Architecture/MSBuildProjectGraphValidator.cs index aa1186a28..ba71a4589 100644 --- a/tools/Inno.Tooling.Architecture/MSBuildProjectGraphValidator.cs +++ b/tools/Inno.Tooling.Architecture/MSBuildProjectGraphValidator.cs @@ -17,10 +17,11 @@ internal static class MSBuildProjectGraphValidator "InnoEngine.Editor", "InnoEngine.Build" }; - internal static async Task ValidateAsync( + internal static async Task> ValidateAsync( string root, string configuration, string dotnetHost, + string? artifactsPath, string? outputPath, ICollection failures, CancellationToken cancellationToken @@ -38,7 +39,7 @@ CancellationToken cancellationToken try { EvaluatedProject snapshot = await EvaluateAsync(root, project, configuration, - dotnetHost, cancellationToken).ConfigureAwait(false); + dotnetHost, artifactsPath, cancellationToken).ConfigureAwait(false); evaluated.Add(snapshot); ValidateProject(root, snapshot, failures); } @@ -56,6 +57,8 @@ await File.WriteAllTextAsync(destination, JsonSerializer.Serialize(evaluated, new JsonSerializerOptions { WriteIndented = true }), cancellationToken).ConfigureAwait(false); } Console.WriteLine($"Evaluated MSBuild ownership for {evaluated.Count}/{projects.Length} projects without executing build targets."); + return evaluated.ToDictionary(project => Path.GetFullPath(project.path, root), + static project => project.targetPath, StringComparer.OrdinalIgnoreCase); } private static async Task EvaluateAsync( @@ -63,6 +66,7 @@ private static async Task EvaluateAsync( string project, string configuration, string dotnetHost, + string? artifactsPath, CancellationToken cancellationToken ) { var start = new ProcessStartInfo(dotnetHost) @@ -83,11 +87,13 @@ CancellationToken cancellationToken foreach (string argument in new[] { "msbuild", project, "-nologo", "-getItem:ProjectReference,Compile,Using", - "-getProperty:AssemblyName,OutputType,InnoProductId,InnoProductTarget,InnoNativeTarget,ImplicitUsings", + "-getProperty:AssemblyName,OutputType,InnoProductId,InnoProductTarget,InnoNativeTarget,ImplicitUsings,TargetPath", "-p:DesignTimeBuild=true", "-p:BuildProjectReferences=false", "-p:InnoProductRestore=false", "-p:Configuration=" + configuration }) start.ArgumentList.Add(argument); + if (artifactsPath is not null) + start.ArgumentList.Add("-p:ArtifactsPath=" + artifactsPath); using var process = new Process { StartInfo = start }; if (!process.Start()) throw new InvalidDataException("The selected SDK did not start."); @@ -125,7 +131,7 @@ CancellationToken cancellationToken return new(Relative(root, project), Value(properties, "AssemblyName"), Value(properties, "OutputType"), Value(properties, "InnoProductId"), Value(properties, "InnoProductTarget"), Value(properties, "InnoNativeTarget"), Value(properties, "ImplicitUsings"), - items.GetProperty("Using").GetArrayLength(), references, sources); + items.GetProperty("Using").GetArrayLength(), references, sources, Value(properties, "TargetPath")); } private static void ValidateProject( @@ -237,7 +243,8 @@ private sealed record EvaluatedProject( string implicitUsings, int usingItems, IReadOnlyList references, - IReadOnlyList sources + IReadOnlyList sources, + string targetPath ); private sealed record EvaluatedReference( diff --git a/tools/Inno.Tooling.Architecture/PublicApiBoundaryValidator.cs b/tools/Inno.Tooling.Architecture/PublicApiBoundaryValidator.cs index 007ca63cc..0eb5b67d1 100644 --- a/tools/Inno.Tooling.Architecture/PublicApiBoundaryValidator.cs +++ b/tools/Inno.Tooling.Architecture/PublicApiBoundaryValidator.cs @@ -13,7 +13,8 @@ internal static class PublicApiBoundaryValidator internal static void Validate( string root, string configuration, - ICollection failures + ICollection failures, + IReadOnlyDictionary? evaluatedOutputs = null ) { Project[] projects = new[] { "entry", "src", "backends", "platforms", "build" } .SelectMany(folder => RepositorySourceInventory.Files(Path.Combine(root, folder), "*.csproj")) @@ -22,7 +23,7 @@ ICollection failures .Where(path => !path.Split(Path.DirectorySeparatorChar).Contains("Bindings", StringComparer.Ordinal)) .Where(path => !path.Split(Path.DirectorySeparatorChar).Contains("tests", StringComparer.Ordinal)) .Where(path => !XDocument.Load(path).Descendants("OutputType").Any(static item => item.Value == "Exe")) - .Select(path => Project.Read(path, configuration)).ToArray(); + .Select(path => Project.Read(path, configuration, evaluatedOutputs)).ToArray(); var byName = projects.ToDictionary(project => project.name, StringComparer.Ordinal); var references = new Dictionary(StringComparer.OrdinalIgnoreCase); string[] platform = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? string.Empty).Split(Path.PathSeparator); @@ -201,14 +202,18 @@ bool product ) { internal static Project Read( string path, - string configuration + string configuration, + IReadOnlyDictionary? evaluatedOutputs ) { XDocument document = XDocument.Load(path); string name = document.Descendants("AssemblyName").FirstOrDefault()?.Value ?? Path.GetFileNameWithoutExtension(path); string framework = document.Descendants("TargetFramework").FirstOrDefault()?.Value ?? "net10.0"; string? target = document.Descendants("InnoProductTarget").FirstOrDefault()?.Value; - return new Project(path, name, Path.Combine(Path.GetDirectoryName(path)!, - target is null ? "bin" : Path.Combine("bin", target), configuration, framework, name + ".dll"), + string output = evaluatedOutputs is null + ? Path.Combine(Path.GetDirectoryName(path)!, target is null ? "bin" : Path.Combine("bin", target), + configuration, framework, name + ".dll") + : evaluatedOutputs.TryGetValue(Path.GetFullPath(path), out string? selected) ? selected : string.Empty; + return new Project(path, name, output, document.Descendants("ProjectReference").ToArray(), document.Descendants("InnoProductId").Any()); } } diff --git a/tools/Inno.Tooling.Architecture/WikiProjectReferences.cs b/tools/Inno.Tooling.Architecture/WikiProjectReferences.cs index 789418636..488cfe7d2 100644 --- a/tools/Inno.Tooling.Architecture/WikiProjectReferences.cs +++ b/tools/Inno.Tooling.Architecture/WikiProjectReferences.cs @@ -17,15 +17,18 @@ internal sealed class WikiProjectReferences private readonly string m_configuration; private readonly string m_dotnetHost; private readonly string? m_target; + private readonly string? m_artifactsPath; private readonly Dictionary m_compilations = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary m_sources = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary m_outputs = new(StringComparer.OrdinalIgnoreCase); + private IReadOnlyList? m_frameworkReferences; internal WikiProjectReferences( string root, string configuration, string dotnetHost, - string? target = null + string? target = null, + string? artifactsPath = null ) { if (!Path.IsPathFullyQualified(dotnetHost) || !File.Exists(dotnetHost)) throw new ArgumentException("Wiki SDK inspection requires an existing absolute dotnet executable.", nameof(dotnetHost)); @@ -33,6 +36,7 @@ internal WikiProjectReferences( m_configuration = configuration; m_dotnetHost = dotnetHost; m_target = target; + m_artifactsPath = artifactsPath; } internal bool IsExcludedProduct(string project) @@ -43,6 +47,17 @@ internal bool IsExcludedProduct(string project) return target is not null && !string.Equals(target, m_target, StringComparison.Ordinal); } + internal IReadOnlyList GetFrameworkReferences(CancellationToken cancellationToken) + { + if (m_frameworkReferences is not null) + return m_frameworkReferences; + string project = RepositorySourceInventory.Files(Path.Combine(m_root, "src", "foundation"), "*.csproj") + .Where(path => XDocument.Load(path).Descendants("TargetFramework").Any(static item => item.Value == "net10.0")) + .Order(StringComparer.Ordinal).First(); + _ = Evaluate(project, cancellationToken, frameworkOnly: true); + return m_frameworkReferences ?? throw new InvalidDataException("The selected SDK did not provide framework references."); + } + internal CSharpCompilation GetCompilation( string project, CancellationToken cancellationToken @@ -77,7 +92,8 @@ CancellationToken cancellationToken private CSharpCompilation Evaluate( string project, - CancellationToken cancellationToken + CancellationToken cancellationToken, + bool frameworkOnly = false ) { if (m_compilations.TryGetValue(project, out CSharpCompilation? compilation)) return compilation; @@ -88,10 +104,11 @@ CancellationToken cancellationToken { // Resolve compile references only. Design-time evaluation must never prepare native products. output = ToolchainEnvironment.CaptureOutputAsync(m_dotnetHost, - ["msbuild", project, "-nologo", "-m:1", "-nr:false", "-t:ResolveReferences", + ["msbuild", project, "-nologo", "-m:1", "-nr:false", "-t:ResolveReferences;AddImplicitDefineConstants", "-getItem:ReferencePath,Compile", "-getProperty:TargetPath,DefineConstants,LangVersion,Nullable,AllowUnsafeBlocks", "-p:DesignTimeBuild=true", "-p:BuildProjectReferences=false", "-p:InnoProductRestore=false", - "-p:Configuration=" + m_configuration], + "-p:Configuration=" + m_configuration, + .. m_artifactsPath is null ? Array.Empty() : new[] { "-p:ArtifactsPath=" + m_artifactsPath }], m_root, bounded.Token, DotNetSdkEnvironment.Create(m_dotnetHost)).GetAwaiter().GetResult(); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) @@ -104,6 +121,17 @@ CancellationToken cancellationToken string[] references = document.RootElement.GetProperty("Items").GetProperty("ReferencePath") .EnumerateArray().Select(static item => item.GetProperty("FullPath").GetString()!) .Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + if (m_frameworkReferences is null) + { + string[] framework = document.RootElement.GetProperty("Items").GetProperty("ReferencePath").EnumerateArray() + .Where(static item => item.TryGetProperty("FrameworkReferenceName", out JsonElement name) + && name.GetString() == "Microsoft.NETCore.App") + .Select(static item => item.GetProperty("FullPath").GetString()!).ToArray(); + if (framework.Length != 0) + m_frameworkReferences = framework.Select(static path => (MetadataReference)MetadataReference.CreateFromFile(path)).ToArray(); + } + if (frameworkOnly) + return CSharpCompilation.Create("WikiFramework", references: m_frameworkReferences); foreach (string reference in references) if (!File.Exists(reference)) throw new FileNotFoundException($"Wiki project '{project}' requires its evaluated compile reference: '{reference}'. Build this target/configuration before validation.", reference);